diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ab4f8cc9609e..4596b59200d7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,27 @@ { "name": "ESPHome Dev", "image": "ghcr.io/esphome/esphome-lint:dev", - "postCreateCommand": [ - "script/devcontainer-post-create" - ], + "postCreateCommand": ["script/devcontainer-post-create"], "containerEnv": { - "DEVCONTAINER": "1" + "DEVCONTAINER": "1", + "PIP_BREAK_SYSTEM_PACKAGES": "1", + "PIP_ROOT_USER_ACTION": "ignore" }, "runArgs": [ "--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1" + // uncomment and edit the path in order to pass though local USB serial to the conatiner + // , "--device=/dev/ttyACM0" ], "appPort": 6052, + // if you are using avahi in the host device, uncomment these to allow the + // devcontainer to find devices via mdns + //"mounts": [ + // "type=bind,source=/dev/bus/usb,target=/dev/bus/usb", + // "type=bind,source=/var/run/dbus,target=/var/run/dbus", + // "type=bind,source=/var/run/avahi-daemon/socket,target=/var/run/avahi-daemon/socket" + //], "customizations": { "vscode": { "extensions": [ @@ -24,7 +33,7 @@ // cpp "ms-vscode.cpptools", // editorconfig - "editorconfig.editorconfig", + "editorconfig.editorconfig" ], "settings": { "python.languageServer": "Pylance", @@ -41,6 +50,7 @@ "!secret scalar", "!lambda scalar", "!extend scalar", + "!remove scalar", "!include_dir_named scalar", "!include_dir_list scalar", "!include_dir_merge_list scalar", diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3221b8ac5c3d..3bf9c4e1f6f2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,6 +19,8 @@ - [ ] ESP32 IDF - [ ] ESP8266 - [ ] RP2040 +- [ ] BK72xx +- [ ] RTL87xx ## Example entry for `config.yaml`: API Reference --> Two-Wire Automotive Interface (TWAI) + +CAN_SPEEDS_ESP32 = { + "25KBPS": CanSpeed.CAN_25KBPS, "50KBPS": CanSpeed.CAN_50KBPS, "100KBPS": CanSpeed.CAN_100KBPS, "125KBPS": CanSpeed.CAN_125KBPS, "250KBPS": CanSpeed.CAN_250KBPS, "500KBPS": CanSpeed.CAN_500KBPS, + "800KBPS": CanSpeed.CAN_800KBPS, "1000KBPS": CanSpeed.CAN_1000KBPS, } +CAN_SPEEDS_ESP32_S2 = { + "1KBPS": CanSpeed.CAN_1KBPS, + "5KBPS": CanSpeed.CAN_5KBPS, + "10KBPS": CanSpeed.CAN_10KBPS, + "12K5BPS": CanSpeed.CAN_12K5BPS, + "16KBPS": CanSpeed.CAN_16KBPS, + "20KBPS": CanSpeed.CAN_20KBPS, + **CAN_SPEEDS_ESP32, +} + +CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_H2 = {**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_ESP32H2: CAN_SPEEDS_ESP32_H2, +} + + +def validate_bit_rate(value): + variant = get_esp32_variant() + if variant not in CAN_SPEEDS: + raise cv.Invalid(f"{variant} is not supported by component {esp32_can_ns}") + value = value.upper() + if value not in CAN_SPEEDS[variant]: + raise cv.Invalid(f"Bit rate {value} is not supported on {variant}") + return cv.enum(CAN_SPEEDS[variant])(value) + + CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(esp32_can), - cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), + cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, } diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index 3eb2d1f0354e..79e4b70f97f2 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -16,6 +16,30 @@ 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_ESP32H6) + case canbus::CAN_1KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); + return true; + case canbus::CAN_5KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_5KBITS(); + return true; + case canbus::CAN_10KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_10KBITS(); + return true; + case canbus::CAN_12K5BPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_12_5KBITS(); + return true; + case canbus::CAN_16KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_16KBITS(); + return true; + case canbus::CAN_20KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_20KBITS(); + return true; +#endif + case canbus::CAN_25KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_25KBITS(); + return true; case canbus::CAN_50KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_50KBITS(); return true; @@ -31,6 +55,9 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config case canbus::CAN_500KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_500KBITS(); return true; + case canbus::CAN_800KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_800KBITS(); + return true; case canbus::CAN_1000KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1MBITS(); return true; diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index ae7f0b642766..49d95d89e5c4 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -4,7 +4,7 @@ from esphome.const import CONF_ID -AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"] +AUTO_LOAD = ["esp32_ble_server"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_beacon"] DEPENDENCIES = ["wifi", "esp32"] @@ -36,6 +36,9 @@ cv.Optional( CONF_AUTHORIZED_DURATION, default="1min" ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_WIFI_TIMEOUT, default="1min" + ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA) @@ -53,6 +56,8 @@ async def to_code(config): cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) + cg.add(var.set_wifi_timeout(config[CONF_WIFI_TIMEOUT])) + if CONF_AUTHORIZER in config and config[CONF_AUTHORIZER] is not None: activator = await cg.get_variable(config[CONF_AUTHORIZER]) cg.add(var.set_authorizer(activator)) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 85013c006bca..d90eaac3b661 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -16,8 +16,16 @@ static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirec ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } void ESP32ImprovComponent::setup() { - this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); - this->setup_characteristics(); +#ifdef USE_BINARY_SENSOR + if (this->authorizer_ != nullptr) { + this->authorizer_->add_on_state_callback([this](bool state) { + if (state) { + this->authorized_start_ = millis(); + this->identify_start_ = 0; + } + }); + } +#endif } void ESP32ImprovComponent::setup_characteristics() { @@ -50,29 +58,42 @@ void ESP32ImprovComponent::setup_characteristics() { BLEDescriptor *capabilities_descriptor = new BLE2902(); this->capabilities_->add_descriptor(capabilities_descriptor); uint8_t capabilities = 0x00; +#ifdef USE_OUTPUT if (this->status_indicator_ != nullptr) capabilities |= improv::CAPABILITY_IDENTIFY; +#endif this->capabilities_->set_value(capabilities); this->setup_complete_ = true; } void ESP32ImprovComponent::loop() { + if (!global_ble_server->is_running()) { + this->state_ = improv::STATE_STOPPED; + this->incoming_data_.clear(); + return; + } + if (this->service_ == nullptr) { + // Setup the service + ESP_LOGD(TAG, "Creating Improv service"); + global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true); + this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID)); + this->setup_characteristics(); + } + if (!this->incoming_data_.empty()) this->process_incoming_data_(); uint32_t now = millis(); switch (this->state_) { case improv::STATE_STOPPED: - if (this->status_indicator_ != nullptr) - this->status_indicator_->turn_off(); + this->set_status_indicator_state_(false); if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_running()) { - esp32_ble::global_ble->get_advertising()->start(); + esp32_ble::global_ble->advertising_start(); this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); this->set_error_(improv::ERROR_NONE); - this->should_start_ = false; ESP_LOGD(TAG, "Service started!"); } else { this->service_->start(); @@ -80,18 +101,22 @@ void ESP32ImprovComponent::loop() { } break; case improv::STATE_AWAITING_AUTHORIZATION: { - if (this->authorizer_ == nullptr || this->authorizer_->state) { +#ifdef USE_BINARY_SENSOR + if (this->authorizer_ == nullptr || + (this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) { this->set_state_(improv::STATE_AUTHORIZED); - this->authorized_start_ = now; - } else { - if (this->status_indicator_ != nullptr) { - if (!this->check_identify_()) - this->status_indicator_->turn_on(); - } + } else +#else + this->set_state_(improv::STATE_AUTHORIZED); +#endif + { + if (!this->check_identify_()) + this->set_status_indicator_state_(true); } break; } case improv::STATE_AUTHORIZED: { +#ifdef USE_BINARY_SENSOR if (this->authorizer_ != nullptr) { if (now - this->authorized_start_ > this->authorized_duration_) { ESP_LOGD(TAG, "Authorization timeout"); @@ -99,25 +124,14 @@ void ESP32ImprovComponent::loop() { return; } } - if (this->status_indicator_ != nullptr) { - if (!this->check_identify_()) { - if ((now % 1000) < 500) { - this->status_indicator_->turn_on(); - } else { - this->status_indicator_->turn_off(); - } - } +#endif + if (!this->check_identify_()) { + this->set_status_indicator_state_((now % 1000) < 500); } break; } case improv::STATE_PROVISIONING: { - if (this->status_indicator_ != nullptr) { - if ((now % 200) < 100) { - this->status_indicator_->turn_on(); - } else { - this->status_indicator_->turn_off(); - } - } + this->set_status_indicator_state_((now % 200) < 100); if (wifi::global_wifi_component->is_connected()) { wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), this->connecting_sta_.get_password()); @@ -127,28 +141,43 @@ void ESP32ImprovComponent::loop() { std::vector urls = {ESPHOME_MY_LINK}; #ifdef USE_WEBSERVER - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); - urls.push_back(webserver_url); + for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { + if (ip.is_ip4()) { + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); + urls.push_back(webserver_url); + break; + } + } #endif std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); this->send_response_(data); - this->set_timeout("end-service", 1000, [this] { - this->service_->stop(); - this->set_state_(improv::STATE_STOPPED); - }); + this->stop(); } break; } case improv::STATE_PROVISIONED: { this->incoming_data_.clear(); - if (this->status_indicator_ != nullptr) - this->status_indicator_->turn_off(); + this->set_status_indicator_state_(false); break; } } } +void ESP32ImprovComponent::set_status_indicator_state_(bool state) { +#ifdef USE_OUTPUT + if (this->status_indicator_ == nullptr) + return; + if (this->status_indicator_state_ == state) + return; + this->status_indicator_state_ = state; + if (state) { + this->status_indicator_->turn_on(); + } else { + this->status_indicator_->turn_off(); + } +#endif +} + bool ESP32ImprovComponent::check_identify_() { uint32_t now = millis(); @@ -156,11 +185,7 @@ bool ESP32ImprovComponent::check_identify_() { if (identify) { uint32_t time = now % 1000; - if (time < 600 && time % 200 < 100) { - this->status_indicator_->turn_on(); - } else { - this->status_indicator_->turn_off(); - } + this->set_status_indicator_state_(time < 600 && time % 200 < 100); } return identify; } @@ -174,6 +199,24 @@ void ESP32ImprovComponent::set_state_(improv::State state) { if (state != improv::STATE_STOPPED) this->status_->notify(); } + std::vector service_data(8, 0); + service_data[0] = 0x77; // PR + service_data[1] = 0x46; // IM + service_data[2] = static_cast(state); + + uint8_t capabilities = 0x00; +#ifdef USE_OUTPUT + if (this->status_indicator_ != nullptr) + capabilities |= improv::CAPABILITY_IDENTIFY; +#endif + + service_data[3] = capabilities; + service_data[4] = 0x00; // Reserved + service_data[5] = 0x00; // Reserved + service_data[6] = 0x00; // Reserved + service_data[7] = 0x00; // Reserved + + esp32_ble::global_ble->advertising_set_service_data(service_data); } void ESP32ImprovComponent::set_error_(improv::Error error) { @@ -203,7 +246,10 @@ void ESP32ImprovComponent::start() { } void ESP32ImprovComponent::stop() { + this->should_start_ = false; this->set_timeout("end-service", 1000, [this] { + if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr) + return; this->service_->stop(); this->set_state_(improv::STATE_STOPPED); }); @@ -213,8 +259,12 @@ float ESP32ImprovComponent::get_setup_priority() const { return setup_priority:: void ESP32ImprovComponent::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Improv:"); +#ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_); +#endif +#ifdef USE_OUTPUT ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr)); +#endif } void ESP32ImprovComponent::process_incoming_data_() { @@ -243,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() { this->connecting_sta_ = sta; wifi::global_wifi_component->set_sta(sta); - wifi::global_wifi_component->start_scanning(); + wifi::global_wifi_component->start_connecting(sta, false); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); @@ -273,8 +323,10 @@ void ESP32ImprovComponent::process_incoming_data_() { void ESP32ImprovComponent::on_wifi_connect_timeout_() { this->set_error_(improv::ERROR_UNABLE_TO_CONNECT); this->set_state_(improv::STATE_AUTHORIZED); +#ifdef USE_BINARY_SENSOR if (this->authorizer_ != nullptr) this->authorized_start_ = millis(); +#endif ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network"); wifi::global_wifi_component->clear_sta(); } diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 1a142c94b6d1..3ed377a6ad7e 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,14 +1,22 @@ #pragma once -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/esp32_ble_server/ble_characteristic.h" -#include "esphome/components/esp32_ble_server/ble_server.h" -#include "esphome/components/output/binary_output.h" -#include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include "esphome/components/esp32_ble_server/ble_characteristic.h" +#include "esphome/components/esp32_ble_server/ble_server.h" +#include "esphome/components/wifi/wifi_component.h" + +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#ifdef USE_OUTPUT +#include "esphome/components/output/binary_output.h" +#endif + #include #ifdef USE_ESP32 @@ -34,11 +42,18 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { void stop() override; bool is_active() const { return this->state_ != improv::STATE_STOPPED; } +#ifdef USE_BINARY_SENSOR void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; } +#endif +#ifdef USE_OUTPUT void set_status_indicator(output::BinaryOutput *status_indicator) { this->status_indicator_ = status_indicator; } +#endif void set_identify_duration(uint32_t identify_duration) { this->identify_duration_ = identify_duration; } void set_authorized_duration(uint32_t authorized_duration) { this->authorized_duration_ = authorized_duration; } + void set_wifi_timeout(uint32_t wifi_timeout) { this->wifi_timeout_ = wifi_timeout; } + uint32_t get_wifi_timeout() const { return this->wifi_timeout_; } + protected: bool should_start_{false}; bool setup_complete_{false}; @@ -48,22 +63,31 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { uint32_t authorized_start_{0}; uint32_t authorized_duration_; + uint32_t wifi_timeout_{}; + std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - std::shared_ptr service_; + BLEService *service_ = nullptr; BLECharacteristic *status_; BLECharacteristic *error_; BLECharacteristic *rpc_; BLECharacteristic *rpc_response_; BLECharacteristic *capabilities_; +#ifdef USE_BINARY_SENSOR binary_sensor::BinarySensor *authorizer_{nullptr}; +#endif +#ifdef USE_OUTPUT output::BinaryOutput *status_indicator_{nullptr}; +#endif improv::State state_{improv::STATE_STOPPED}; improv::Error error_state_{improv::ERROR_NONE}; + bool status_indicator_state_{false}; + void set_status_indicator_state_(bool state); + void set_state_(improv::State state); void set_error_(improv::Error error); void send_response_(std::vector &response); diff --git a/esphome/components/esp32_rmt/__init__.py b/esphome/components/esp32_rmt/__init__.py new file mode 100644 index 000000000000..bda240680b75 --- /dev/null +++ b/esphome/components/esp32_rmt/__init__.py @@ -0,0 +1,55 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.components import esp32 + +CODEOWNERS = ["@jesserockz"] + +RMT_TX_CHANNELS = { + esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], + esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], + esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], + esp32.const.VARIANT_ESP32C3: [0, 1], + esp32.const.VARIANT_ESP32C6: [0, 1], + esp32.const.VARIANT_ESP32H2: [0, 1], +} + +RMT_RX_CHANNELS = { + esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], + esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], + esp32.const.VARIANT_ESP32S3: [4, 5, 6, 7], + esp32.const.VARIANT_ESP32C3: [2, 3], + esp32.const.VARIANT_ESP32C6: [2, 3], + esp32.const.VARIANT_ESP32H2: [2, 3], +} + +rmt_channel_t = cg.global_ns.enum("rmt_channel_t") +RMT_CHANNEL_ENUMS = { + 0: rmt_channel_t.RMT_CHANNEL_0, + 1: rmt_channel_t.RMT_CHANNEL_1, + 2: rmt_channel_t.RMT_CHANNEL_2, + 3: rmt_channel_t.RMT_CHANNEL_3, + 4: rmt_channel_t.RMT_CHANNEL_4, + 5: rmt_channel_t.RMT_CHANNEL_5, + 6: rmt_channel_t.RMT_CHANNEL_6, + 7: rmt_channel_t.RMT_CHANNEL_7, +} + + +def validate_rmt_channel(*, tx: bool): + + rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS + + def _validator(value): + cv.only_on_esp32(value) + value = cv.int_(value) + variant = esp32.get_esp32_variant() + if variant not in rmt_channels: + raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") + if value not in rmt_channels[variant]: + raise cv.Invalid( + f"RMT channel {value} does not support {'transmitting' if tx else 'receiving'} for ESP32 variant {variant}." + ) + return cv.enum(RMT_CHANNEL_ENUMS)(value) + + return _validator diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index df6ee2ce2f2f..7727b64f2951 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -13,6 +13,8 @@ namespace esp32_rmt_led_strip { static const char *const TAG = "esp32_rmt_led_strip"; +static const uint32_t RMT_CLK_FREQ = 80000000; + static const uint8_t RMT_CLK_DIV = 2; void ESP32RMTLEDStripLightOutput::setup() { @@ -65,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() { void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low) { - float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f; + float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; // 0-bit this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); @@ -158,11 +160,13 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index b = 0; break; } - uint8_t multiplier = this->is_rgbw_ ? 4 : 3; - return {this->buf_ + (index * multiplier) + r, - this->buf_ + (index * multiplier) + g, - this->buf_ + (index * multiplier) + b, - this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, + uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3; + uint8_t white = this->is_wrgb_ ? 0 : 3; + + return {this->buf_ + (index * multiplier) + r + this->is_wrgb_, + this->buf_ + (index * multiplier) + g + this->is_wrgb_, + this->buf_ + (index * multiplier) + b + this->is_wrgb_, + this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr, &this->effect_data_[index], &this->correction_}; } diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index 11d61b07e157..e9b19c939919 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { int32_t size() const override { return this->num_leds_; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - if (this->is_rgbw_) { + if (this->is_rgbw_ || this->is_wrgb_) { traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); } else { traits.set_supported_color_modes({light::ColorMode::RGB}); @@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { void set_pin(uint8_t pin) { this->pin_ = pin; } 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; } /// Set a maximum refresh rate in µs as some lights do not like being updated too often. void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } @@ -63,7 +64,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { protected: light::ESPColorView get_view_internal(int32_t index) const override; - size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } + size_t get_buffer_size_() const { return this->num_leds_ * (this->is_rgbw_ || this->is_wrgb_ ? 4 : 3); } uint8_t *buf_{nullptr}; uint8_t *effect_data_{nullptr}; @@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { uint8_t pin_; uint16_t num_leds_; bool is_rgbw_; + bool is_wrgb_; rmt_item32_t bit0_, bit1_; RGBOrder rgb_order_; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 5b65ecdabf63..1c15a468d99d 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -3,7 +3,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.components import esp32, light +from esphome.components import esp32_rmt, light from esphome.const import ( CONF_CHIPSET, CONF_MAX_REFRESH_RATE, @@ -11,6 +11,7 @@ CONF_OUTPUT_ID, CONF_PIN, CONF_RGB_ORDER, + CONF_RMT_CHANNEL, ) CODEOWNERS = ["@jesserockz"] @@ -47,35 +48,16 @@ class LEDStripTimings: "WS2812": LEDStripTimings(400, 1000, 1000, 400), "SK6812": LEDStripTimings(300, 900, 600, 600), "APA106": LEDStripTimings(350, 1360, 1360, 350), - "SM16703": LEDStripTimings(300, 900, 1360, 350), + "SM16703": LEDStripTimings(300, 900, 900, 300), } CONF_IS_RGBW = "is_rgbw" +CONF_IS_WRGB = "is_wrgb" CONF_BIT0_HIGH = "bit0_high" CONF_BIT0_LOW = "bit0_low" CONF_BIT1_HIGH = "bit1_high" CONF_BIT1_LOW = "bit1_low" -CONF_RMT_CHANNEL = "rmt_channel" - -RMT_CHANNELS = { - esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], - esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], - esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], - esp32.const.VARIANT_ESP32C3: [0, 1], - esp32.const.VARIANT_ESP32C6: [0, 1], -} - - -def _validate_rmt_channel(value): - variant = esp32.get_esp32_variant() - if variant not in RMT_CHANNELS: - raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") - if value not in RMT_CHANNELS[variant]: - raise cv.Invalid( - f"RMT channel {value} is not supported for ESP32 variant {variant}." - ) - return value CONFIG_SCHEMA = cv.All( @@ -85,26 +67,27 @@ def _validate_rmt_channel(value): cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), - cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel, + cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, + cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Inclusive( CONF_BIT0_HIGH, "custom", - ): cv.positive_time_period_microseconds, + ): cv.positive_time_period_nanoseconds, cv.Inclusive( CONF_BIT0_LOW, "custom", - ): cv.positive_time_period_microseconds, + ): cv.positive_time_period_nanoseconds, cv.Inclusive( CONF_BIT1_HIGH, "custom", - ): cv.positive_time_period_microseconds, + ): cv.positive_time_period_nanoseconds, cv.Inclusive( CONF_BIT1_LOW, "custom", - ): cv.positive_time_period_microseconds, + ): cv.positive_time_period_nanoseconds, } ), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), @@ -144,6 +127,7 @@ async def to_code(config): cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) + cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add( var.set_rmt_channel( diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 674f433d5235..64b127bda31b 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -11,6 +11,8 @@ KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_ESP8266, + CONF_PLATFORM_VERSION, ) from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv @@ -38,7 +40,7 @@ def set_core_data(config): CORE.data[KEY_ESP8266] = {} - CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP8266 CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( config[CONF_FRAMEWORK][CONF_VERSION] @@ -50,6 +52,17 @@ def set_core_data(config): return config +def get_download_types(storage_json): + return [ + { + "title": "Standard format", + "description": "For flashing ESP8266.", + "file": "firmware.bin", + "download": f"{storage_json.name}.bin", + }, + ] + + def _format_framework_arduino_version(ver: cv.Version) -> str: # format the given arduino (https://github.com/esp8266/Arduino/releases) version to # a PIO platformio/framework-arduinoespressif8266 value @@ -71,20 +84,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/esp8266/Arduino/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 2) # The platformio/espressif8266 version to use for arduino 2 framework versions # - https://github.com/platformio/platform-espressif8266/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) # for arduino 3 framework versions ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) +# for arduino 4 framework versions +ARDUINO_4_PLATFORM_VERSION = cv.Version(4, 2, 1) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), - "latest": (cv.Version(3, 0, 2), None), + "dev": (cv.Version(3, 1, 2), "https://github.com/esp8266/Arduino.git"), + "latest": (cv.Version(3, 1, 2), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } @@ -104,7 +119,9 @@ def _arduino_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION) if platform_version is None: - if version >= cv.Version(3, 0, 0): + if version >= cv.Version(3, 1, 0): + platform_version = _parse_platform_version(str(ARDUINO_4_PLATFORM_VERSION)) + elif version >= cv.Version(3, 0, 0): platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) @@ -130,7 +147,6 @@ def _parse_platform_version(value): return value -CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index e75578cc1652..c42bc9204f2b 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -12,6 +12,7 @@ CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, + PLATFORM_ESP8266, ) from esphome import pins from esphome.core import CORE, coroutine_with_priority @@ -21,10 +22,8 @@ from . import boards from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns - _LOGGER = logging.getLogger(__name__) - ESP8266GPIOPin = esp8266_ns.class_("ESP8266GPIOPin", cg.InternalGPIOPin) @@ -124,6 +123,8 @@ def validate_supports(value): (True, False, False, False, False), # OUTPUT (False, True, False, False, False), + # INPUT and OUTPUT, e.g. for i2c + (True, True, False, False, False), # INPUT_PULLUP (True, False, False, True, False), # INPUT_PULLDOWN_16 @@ -142,21 +143,11 @@ def validate_supports(value): ESP8266_PIN_SCHEMA = cv.All( - { - cv.GenerateID(): cv.declare_id(ESP8266GPIOPin), - cv.Required(CONF_NUMBER): validate_gpio_pin, - cv.Optional(CONF_MODE, default={}): cv.Schema( - { - cv.Optional(CONF_ANALOG, default=False): cv.boolean, - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, - } - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - }, + pins.gpio_base_schema( + ESP8266GPIOPin, + validate_gpio_pin, + modes=pins.GPIO_STANDARD_MODES + (CONF_ANALOG,), + ), validate_supports, ) @@ -167,7 +158,7 @@ class PinInitialState: level: int = 255 -@pins.PIN_SCHEMA_REGISTRY.register("esp8266", ESP8266_PIN_SCHEMA) +@pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_ESP8266, ESP8266_PIN_SCHEMA) async def esp8266_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 6f0f3741dd52..ade94cb9f5c8 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,6 +1,13 @@ from esphome import pins import esphome.config_validation as cv +import esphome.final_validate as fv import esphome.codegen as cg +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) from esphome.const import ( CONF_DOMAIN, CONF_ID, @@ -12,9 +19,17 @@ CONF_SUBNET, CONF_DNS1, CONF_DNS2, + CONF_CLK_PIN, + CONF_MISO_PIN, + CONF_MOSI_PIN, + CONF_CS_PIN, + CONF_INTERRUPT_PIN, + CONF_RESET_PIN, + CONF_SPI, ) from esphome.core import CORE, coroutine_with_priority from esphome.components.network import IPAddress +from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX CONFLICTS_WITH = ["wifi"] DEPENDENCIES = ["esp32"] @@ -27,6 +42,8 @@ CONF_CLK_MODE = "clk_mode" CONF_POWER_PIN = "power_pin" +CONF_CLOCK_SPEED = "clock_speed" + EthernetType = ethernet_ns.enum("EthernetType") ETHERNET_TYPES = { "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, @@ -36,8 +53,11 @@ "JL1101": EthernetType.ETHERNET_TYPE_JL1101, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, + "W5500": EthernetType.ETHERNET_TYPE_W5500, } +SPI_ETHERNET_TYPES = ["W5500"] + emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") CLK_MODES = { @@ -84,11 +104,22 @@ def _validate(config): return config -CONFIG_SCHEMA = cv.All( +BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(EthernetComponent), + 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." + ), + } +).extend(cv.COMPONENT_SCHEMA) + +RMII_SCHEMA = BASE_SCHEMA.extend( cv.Schema( { - cv.GenerateID(): cv.declare_id(EthernetComponent), - cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( @@ -96,19 +127,66 @@ def _validate(config): ), cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, - 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." + } + ) +) + +SPI_SCHEMA = BASE_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All( + cv.frequency, cv.int_range(int(8e6), int(80e6)) ), } - ).extend(cv.COMPONENT_SCHEMA), + ), +) + +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + { + "LAN8720": RMII_SCHEMA, + "RTL8201": RMII_SCHEMA, + "DP83848": RMII_SCHEMA, + "IP101": RMII_SCHEMA, + "JL1101": RMII_SCHEMA, + "KSZ8081": RMII_SCHEMA, + "KSZ8081RNA": RMII_SCHEMA, + "W5500": SPI_SCHEMA, + }, + upper=True, + ), _validate, ) +def _final_validate(config): + if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: + return + if spi_configs := fv.full_config.get().get(CONF_SPI): + variant = get_esp32_variant() + if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3): + spi_host = "SPI2_HOST" + else: + spi_host = "SPI3_HOST" + for spi_conf in spi_configs: + if (index := spi_conf.get(CONF_INTERFACE_INDEX)) is not None: + interface = get_spi_interface(index) + if interface == spi_host: + raise cv.Invalid( + f"`spi` component is using interface '{interface}'. " + f"To use {config[CONF_TYPE]}, you must change the `interface` on the `spi` component.", + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + def manual_ip(config): return cg.StructInitializer( ManualIP, @@ -125,16 +203,32 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) - cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) - cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) - cg.add(var.set_type(config[CONF_TYPE])) - cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) + if config[CONF_TYPE] == "W5500": + cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) + cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) + cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) + cg.add(var.set_cs_pin(config[CONF_CS_PIN])) + if CONF_INTERRUPT_PIN in config: + cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) + if CONF_RESET_PIN in config: + cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) + cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) + + cg.add_define("USE_ETHERNET_SPI") + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) + add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True) + else: + cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) + cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) + cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) + cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) + if CONF_POWER_PIN in config: + cg.add(var.set_power_pin(config[CONF_POWER_PIN])) + + cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) - if CONF_POWER_PIN in config: - cg.add(var.set_power_pin(config[CONF_POWER_PIN])) - if CONF_MANUAL_IP in config: cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d9004a913b8b..3af462d5939b 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -1,13 +1,19 @@ #include "ethernet_component.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/util.h" -#include "esphome/core/application.h" #ifdef USE_ESP32 #include +#include #include "esp_event.h" +#ifdef USE_ETHERNET_SPI +#include +#include +#endif + namespace esphome { namespace ethernet { @@ -32,6 +38,36 @@ void EthernetComponent::setup() { } esp_err_t err; + +#ifdef USE_ETHERNET_SPI + // Install GPIO ISR handler to be able to service SPI Eth modules interrupts + gpio_install_isr_service(0); + + spi_bus_config_t buscfg = { + .mosi_io_num = this->mosi_pin_, + .miso_io_num = this->miso_pin_, + .sclk_io_num = this->clk_pin_, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 0, + .flags = 0, + .intr_flags = 0, + }; + +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + auto host = SPI2_HOST; +#else + auto host = SPI3_HOST; +#endif + + err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); + ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); +#endif + err = esp_netif_init(); ESPHL_ERROR_CHECK(err, "ETH netif init error"); err = esp_event_loop_create_default(); @@ -42,10 +78,40 @@ void EthernetComponent::setup() { // Init MAC and PHY configs to default eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + +#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module + spi_device_interface_config_t devcfg = { + .command_bits = 16, // Actually it's the address phase in W5500 SPI frame + .address_bits = 8, // Actually it's the control phase in W5500 SPI frame + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = this->clock_speed_, + .input_delay_ns = 0, + .spics_io_num = this->cs_pin_, + .flags = 0, + .queue_size = 20, + .pre_cb = nullptr, + .post_cb = nullptr, + }; + + spi_device_handle_t spi_handle = nullptr; + err = spi_bus_add_device(host, &devcfg, &spi_handle); + ESPHL_ERROR_CHECK(err, "SPI bus add device error"); + + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); + w5500_config.int_gpio_num = this->interrupt_pin_; + phy_config.phy_addr = this->phy_addr_spi_; + phy_config.reset_gpio_num = this->reset_pin_; + + esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); +#else phy_config.phy_addr = this->phy_addr_; phy_config.reset_gpio_num = this->power_pin_; - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); #if ESP_IDF_VERSION_MAJOR >= 5 eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; @@ -61,9 +127,11 @@ void EthernetComponent::setup() { mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); +#endif #endif switch (this->type_) { +#if CONFIG_ETH_USE_ESP32_EMAC case ETHERNET_TYPE_LAN8720: { this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); break; @@ -93,6 +161,13 @@ void EthernetComponent::setup() { #endif break; } +#endif +#ifdef USE_ETHERNET_SPI + case ETHERNET_TYPE_W5500: { + this->phy_ = esp_eth_phy_new_w5500(&phy_config); + break; + } +#endif default: { this->mark_failed(); return; @@ -104,10 +179,22 @@ void EthernetComponent::setup() { err = esp_eth_driver_install(ð_config, &this->eth_handle_); ESPHL_ERROR_CHECK(err, "ETH driver install error"); +#ifndef USE_ETHERNET_SPI if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. this->ksz8081_set_clock_reference_(mac); } + if (this->type_ == ETHERNET_TYPE_RTL8201 && this->clk_mode_ == EMAC_CLK_EXT_IN) { + // Change in default behavior of RTL8201FI may require register setting to enable external clock + this->rtl8201_set_rmii_mode_(mac); + } +#endif + + // use ESP internal eth mac + uint8_t mac_addr[6]; + esp_read_mac(mac_addr, ESP_MAC_ETH); + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr); + ESPHL_ERROR_CHECK(err, "set mac address error"); /* attach Ethernet driver to TCP/IP stack */ err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); @@ -118,10 +205,10 @@ void EthernetComponent::setup() { ESPHL_ERROR_CHECK(err, "ETH event handler register error"); err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); - ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error"); -#endif /* LWIP_IPV6 */ + ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error"); +#endif /* USE_NETWORK_IPV6 */ /* start Ethernet driver state machine */ err = esp_eth_start(this->eth_handle_); @@ -164,20 +251,6 @@ void EthernetComponent::loop() { this->state_ = EthernetComponentState::CONNECTING; this->start_connect_(); } -#if LWIP_IPV6 - else if (this->got_ipv6_) { - esp_ip6_addr_t ip6_addr; - if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && - esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { - ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); - } else { - esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); - ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); - } - - this->got_ipv6_ = false; - } -#endif /* LWIP_IPV6 */ break; } } @@ -213,6 +286,10 @@ void EthernetComponent::dump_config() { eth_type = "KSZ8081RNA"; break; + case ETHERNET_TYPE_W5500: + eth_type = "W5500"; + break; + default: eth_type = "Unknown"; break; @@ -220,23 +297,56 @@ void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, "Ethernet:"); this->dump_connect_params_(); +#ifdef USE_ETHERNET_SPI + ESP_LOGCONFIG(TAG, " CLK Pin: %u", this->clk_pin_); + ESP_LOGCONFIG(TAG, " MISO Pin: %u", this->miso_pin_); + ESP_LOGCONFIG(TAG, " MOSI Pin: %u", this->mosi_pin_); + ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_); + ESP_LOGCONFIG(TAG, " IRQ Pin: %u", this->interrupt_pin_); + ESP_LOGCONFIG(TAG, " Reset Pin: %d", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Clock Speed: %d MHz", this->clock_speed_ / 1000000); +#else if (this->power_pin_ != -1) { ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); } ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", eth_type); ESP_LOGCONFIG(TAG, " PHY addr: %u", this->phy_addr_); +#endif + ESP_LOGCONFIG(TAG, " Type: %s", eth_type); } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } bool EthernetComponent::can_proceed() { return this->is_connected(); } -network::IPAddress EthernetComponent::get_ip_address() { +network::IPAddresses EthernetComponent::get_ip_addresses() { + network::IPAddresses addresses; esp_netif_ip_info_t ip; - esp_netif_get_ip_info(this->eth_netif_, &ip); - return {ip.ip.addr}; + esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + addresses[i + 1] = network::IPAddress(&if_ip6s[i]); + } +#endif /* USE_NETWORK_IPV6 */ + + return addresses; +} + +network::IPAddress EthernetComponent::get_dns_address(uint8_t num) { + const ip_addr_t *dns_ip = dns_getserver(num); + return dns_ip; } void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { @@ -268,20 +378,33 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + const esp_netif_ip_info_t *ip_info = &event->ip_info; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); + global_eth_component->got_ipv4_address_ = true; +#if USE_NETWORK_IPV6 + global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; +#else global_eth_component->connected_ = true; - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); +#endif /* USE_NETWORK_IPV6 */ } -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%d)", event_id); - global_eth_component->got_ipv6_ = true; + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); global_eth_component->ipv6_count_ += 1; + global_eth_component->connected_ = + global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); } -#endif /* LWIP_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ void EthernetComponent::start_connect_() { + global_eth_component->got_ipv4_address_ = false; +#if USE_NETWORK_IPV6 + global_eth_component->ipv6_count_ = 0; +#endif /* USE_NETWORK_IPV6 */ this->connect_begin_ = millis(); this->status_set_warning(); @@ -293,9 +416,9 @@ void EthernetComponent::start_connect_() { esp_netif_ip_info_t info; if (this->manual_ip_.has_value()) { - info.ip.addr = static_cast(this->manual_ip_->static_ip); - info.gw.addr = static_cast(this->manual_ip_->gateway); - info.netmask.addr = static_cast(this->manual_ip_->subnet); + info.ip = this->manual_ip_->static_ip; + info.gw = this->manual_ip_->gateway; + info.netmask = this->manual_ip_->subnet; } else { info.ip.addr = 0; info.gw.addr = 0; @@ -318,24 +441,14 @@ void EthernetComponent::start_connect_() { ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); if (this->manual_ip_.has_value()) { - if (uint32_t(this->manual_ip_->dns1) != 0) { + if (this->manual_ip_->dns1.is_set()) { ip_addr_t d; -#if LWIP_IPV6 - d.type = IPADDR_TYPE_V4; - d.u_addr.ip4.addr = static_cast(this->manual_ip_->dns1); -#else - d.addr = static_cast(this->manual_ip_->dns1); -#endif + d = this->manual_ip_->dns1; dns_setserver(0, &d); } - if (uint32_t(this->manual_ip_->dns2) != 0) { + if (this->manual_ip_->dns2.is_set()) { ip_addr_t d; -#if LWIP_IPV6 - d.type = IPADDR_TYPE_V4; - d.u_addr.ip4.addr = static_cast(this->manual_ip_->dns2); -#else - d.addr = static_cast(this->manual_ip_->dns2); -#endif + d = this->manual_ip_->dns2; dns_setserver(1, &d); } } else { @@ -343,12 +456,12 @@ void EthernetComponent::start_connect_() { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); } -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 err = esp_netif_create_ip6_linklocal(this->eth_netif_); if (err != ESP_OK) { - ESPHL_ERROR_CHECK(err, "IPv6 local failed"); + ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); } -#endif /* LWIP_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ } this->connect_begin_ = millis(); @@ -360,34 +473,26 @@ bool EthernetComponent::is_connected() { return this->state_ == EthernetComponen void EthernetComponent::dump_connect_params_() { esp_netif_ip_info_t ip; esp_netif_get_ip_info(this->eth_netif_, &ip); - ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(ip.ip.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(&ip.ip).str().c_str()); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(ip.netmask.addr).str().c_str()); - ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(ip.gw.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(&ip.netmask).str().c_str()); + ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(&ip.gw).str().c_str()); const ip_addr_t *dns_ip1 = dns_getserver(0); const ip_addr_t *dns_ip2 = dns_getserver(1); -#if LWIP_IPV6 - ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str()); -#else - ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->addr).str().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str()); -#endif - -#if LWIP_IPV6 - if (this->ipv6_count_ > 0) { - esp_ip6_addr_t ip6_addr; - esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); - ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); + ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1).str().c_str()); + ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str()); - if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && - esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { - ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); - } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i])); } -#endif /* LWIP_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ esp_err_t err; @@ -407,15 +512,25 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10); } +#ifdef USE_ETHERNET_SPI +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } +void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } +void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } +void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } +void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } +void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } +#else void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } -void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { this->clk_mode_ = clk_mode; this->clk_gpio_ = clk_gpio; } +#endif +void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } std::string EthernetComponent::get_use_address() const { @@ -442,9 +557,11 @@ bool EthernetComponent::powerdown() { return true; } -void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { -#define KSZ80XX_PC2R_REG_ADDR (0x1F) +#ifndef USE_ETHERNET_SPI + +constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F; +void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { esp_err_t err; uint32_t phy_control_2; @@ -469,10 +586,49 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { 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()); } +} +constexpr uint8_t RTL8201_RMSR_REG_ADDR = 0x10; +void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) { + esp_err_t err; + uint32_t phy_rmii_mode; + err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x07); + ESPHL_ERROR_CHECK(err, "Setting Page 7 failed"); -#undef KSZ80XX_PC2R_REG_ADDR + /* + * RTL8201 RMII Mode Setting Register (RMSR) + * Page 7 Register 16 + * + * bit 0 Reserved 0 + * bit 1 Rg_rmii_rxdsel 1 (default) + * bit 2 Rg_rmii_rxdv_sel: 0 (default) + * bit 3 RMII Mode: 1 (RMII Mode) + * bit 4~7 Rg_rmii_rx_offset: 1111 (default) + * bit 8~11 Rg_rmii_tx_offset: 1111 (default) + * bit 12 Rg_rmii_clkdir: 1 (Input) + * bit 13~15 Reserved 000 + * + * Binary: 0001 1111 1111 1010 + * Hex: 0x1FFA + * + */ + + err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); + ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); + ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode); + + err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA); + ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed"); + + err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); + ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); + ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode); + + err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0); + ESPHL_ERROR_CHECK(err, "Setting Page 0 failed"); } +#endif + } // namespace ethernet } // namespace esphome diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 1bd4786b4451..6276885fd1bb 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/components/network/ip_address.h" @@ -22,6 +23,7 @@ enum EthernetType { ETHERNET_TYPE_JL1101, ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081RNA, + ETHERNET_TYPE_W5500, }; struct ManualIP { @@ -49,15 +51,26 @@ class EthernetComponent : public Component { void on_shutdown() override { powerdown(); } bool is_connected(); +#ifdef USE_ETHERNET_SPI + void set_clk_pin(uint8_t clk_pin); + void set_miso_pin(uint8_t miso_pin); + void set_mosi_pin(uint8_t mosi_pin); + void set_cs_pin(uint8_t cs_pin); + void set_interrupt_pin(uint8_t interrupt_pin); + void set_reset_pin(uint8_t reset_pin); + void set_clock_speed(int clock_speed); +#else void set_phy_addr(uint8_t phy_addr); void set_power_pin(int power_pin); void set_mdc_pin(uint8_t mdc_pin); void set_mdio_pin(uint8_t mdio_pin); - void set_type(EthernetType type); void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); +#endif + void set_type(EthernetType type); void set_manual_ip(const ManualIP &manual_ip); - network::IPAddress get_ip_address(); + network::IPAddresses get_ip_addresses(); + network::IPAddress get_dns_address(uint8_t num); std::string get_use_address() const; void set_use_address(const std::string &use_address); bool powerdown(); @@ -73,21 +86,34 @@ class EthernetComponent : public Component { void dump_connect_params_(); /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); + /// @brief Set `RMII Mode Setting Register` for RTL8201. + void rtl8201_set_rmii_mode_(esp_eth_mac_t *mac); std::string use_address_; +#ifdef USE_ETHERNET_SPI + uint8_t clk_pin_; + uint8_t miso_pin_; + uint8_t mosi_pin_; + uint8_t cs_pin_; + uint8_t interrupt_pin_; + int reset_pin_{-1}; + int phy_addr_spi_{-1}; + int clock_speed_; +#else uint8_t phy_addr_{0}; int power_pin_{-1}; uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; - EthernetType type_{ETHERNET_TYPE_UNKNOWN}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; +#endif + EthernetType type_{ETHERNET_TYPE_UNKNOWN}; optional manual_ip_{}; bool started_{false}; bool connected_{false}; + bool got_ipv4_address_{false}; #if LWIP_IPV6 - bool got_ipv6_{false}; uint8_t ipv6_count_{0}; #endif /* LWIP_IPV6 */ EthernetComponentState state_{EthernetComponentState::STOPPED}; diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index f84187539648..c8b2b5885b60 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -9,6 +9,7 @@ namespace ethernet_info { static const char *const TAG = "ethernet_info"; void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); } +void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); } } // namespace ethernet_info } // namespace esphome diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 2d46fe18ebb6..82a7dcf56e68 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -12,19 +12,51 @@ namespace ethernet_info { class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - auto ip = ethernet::global_eth_component->get_ip_address(); - if (ip != this->last_ip_) { - this->last_ip_ = ip; - this->publish_state(network::IPAddress(ip).str()); + auto ips = ethernet::global_eth_component->get_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::ETHERNET; } std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } void dump_config() override; + void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } protected: - network::IPAddress last_ip_; + network::IPAddresses last_ips_; + std::array ip_sensors_; +}; + +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); + + 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::ETHERNET; } + std::string unique_id() override { return get_mac_address() + "-ethernetinfo-dns"; } + void dump_config() override; + + protected: + std::string last_results_; }; } // namespace ethernet_info diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index 7cb9944c929d..292673c1821c 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -3,6 +3,7 @@ from esphome.components import text_sensor from esphome.const import ( CONF_IP_ADDRESS, + CONF_DNS_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC, ) @@ -10,25 +11,43 @@ ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info") -IPAddressEsthernetInfo = ethernet_info_ns.class_( +IPAddressEthernetInfo = ethernet_info_ns.class_( "IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent ) +DNSAddressEthernetInfo = ethernet_info_ns.class_( + "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent +) + CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( - IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")) + IPAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ) + .extend(cv.polling_component_schema("1s")) + .extend( + { + cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + for x in range(5) + } + ), + cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( + DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ).extend(cv.polling_component_schema("1s")), } ) -async def setup_conf(config, key): - if key in config: - conf = config[key] - var = await text_sensor.new_text_sensor(conf) - await cg.register_component(var, conf) - - async def to_code(config): - await setup_conf(config, CONF_IP_ADDRESS) + if conf := config.get(CONF_IP_ADDRESS): + ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) + await cg.register_component(ip_info, config[CONF_IP_ADDRESS]) + for x in range(5): + if sensor_conf := conf.get(f"address_{x}"): + sens = await text_sensor.new_text_sensor(sensor_conf) + cg.add(ip_info.add_ip_sensors(x, sens)) + if conf := config.get(CONF_DNS_ADDRESS): + dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS]) + await cg.register_component(dns_info, config[CONF_DNS_ADDRESS]) diff --git a/esphome/components/event/__init__.py b/esphome/components/event/__init__.py new file mode 100644 index 000000000000..241e8843868b --- /dev/null +++ b/esphome/components/event/__init__.py @@ -0,0 +1,134 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import mqtt +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_ON_EVENT, + CONF_TRIGGER_ID, + CONF_MQTT_ID, + CONF_EVENT_TYPE, + DEVICE_CLASS_BUTTON, + DEVICE_CLASS_DOORBELL, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_MOTION, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity +from esphome.cpp_generator import MockObjClass + +CODEOWNERS = ["@nohat"] +IS_PLATFORM_COMPONENT = True + +DEVICE_CLASSES = [ + DEVICE_CLASS_BUTTON, + DEVICE_CLASS_DOORBELL, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_MOTION, +] + +event_ns = cg.esphome_ns.namespace("event") +Event = event_ns.class_("Event", cg.EntityBase) +EventPtr = Event.operator("ptr") + +TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action) + +EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template()) + +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + +EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent), + cv.GenerateID(): cv.declare_id(Event), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + cv.Optional(CONF_ON_EVENT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger), + } + ), + } +) + +_UNDEF = object() + + +def event_schema( + class_: MockObjClass = _UNDEF, + *, + icon: str = _UNDEF, + entity_category: str = _UNDEF, + device_class: str = _UNDEF, +) -> cv.Schema: + schema = {} + + if class_ is not _UNDEF: + 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), + (CONF_DEVICE_CLASS, device_class, validate_device_class), + ]: + if default is not _UNDEF: + schema[cv.Optional(key, default=default)] = validator + + return EVENT_SCHEMA.extend(schema) + + +async def setup_event_core_(var, config, *, event_types: list[str]): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_EVENT, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.std_string, "event_type")], conf + ) + + cg.add(var.set_event_types(event_types)) + + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: + cg.add(var.set_device_class(device_class)) + + if mqtt_id := config.get(CONF_MQTT_ID): + mqtt_ = cg.new_Pvariable(mqtt_id, var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_event(var, config, *, event_types: list[str]): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_event(var)) + await setup_event_core_(var, config, event_types=event_types) + + +async def new_event(config, *, event_types: list[str]): + var = cg.new_Pvariable(config[CONF_ID]) + await register_event(var, config, event_types=event_types) + return var + + +TRIGGER_EVENT_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Event), + cv.Required(CONF_EVENT_TYPE): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action("event.trigger", TriggerEventAction, TRIGGER_EVENT_SCHEMA) +async def event_fire_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + templ = await cg.templatable(config[CONF_EVENT_TYPE], args, cg.std_string) + cg.add(var.set_event_type(templ)) + return var + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_define("USE_EVENT") + cg.add_global(event_ns.using) diff --git a/esphome/components/event/automation.h b/esphome/components/event/automation.h new file mode 100644 index 000000000000..9ebcb654a047 --- /dev/null +++ b/esphome/components/event/automation.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/components/event/event.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace event { + +template class TriggerEventAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(std::string, event_type) + + void play(Ts... x) override { this->parent_->trigger(this->event_type_.value(x...)); } +}; + +class EventTrigger : public Trigger { + public: + EventTrigger(Event *event) { + event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); }); + } +}; + +} // namespace event +} // namespace esphome diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp new file mode 100644 index 000000000000..061afcb02664 --- /dev/null +++ b/esphome/components/event/event.cpp @@ -0,0 +1,24 @@ +#include "event.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace event { + +static const char *const TAG = "event"; + +void Event::trigger(const std::string &event_type) { + if (types_.find(event_type) == types_.end()) { + ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); + return; + } + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str()); + this->event_callback_.call(event_type); +} + +void Event::add_on_event_callback(std::function &&callback) { + this->event_callback_.add(std::move(callback)); +} + +} // namespace event +} // namespace esphome diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h new file mode 100644 index 000000000000..067a867360ed --- /dev/null +++ b/esphome/components/event/event.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace event { + +#define LOG_EVENT(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ + } + +class Event : public EntityBase, public EntityBase_DeviceClass { + public: + void trigger(const std::string &event_type); + void set_event_types(const std::set &event_types) { this->types_ = event_types; } + std::set get_event_types() const { return this->types_; } + void add_on_event_callback(std::function &&callback); + + protected: + CallbackManager event_callback_; + std::set types_; +}; + +} // namespace event +} // namespace esphome diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index bbb703dc5cee..f4432a036266 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -49,7 +49,16 @@ def _process_git_config(config: dict, refresh) -> str: password=config.get(CONF_PASSWORD), ) - if (repo_dir / "esphome" / "components").is_dir(): + if path := config.get(CONF_PATH): + if (repo_dir / path).is_dir(): + components_dir = repo_dir / path + else: + raise cv.Invalid( + "Could not find components folder for source. Please check the source contains a '" + + path + + "' folder" + ) + elif (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" elif (repo_dir / "components").is_dir(): components_dir = repo_dir / "components" diff --git a/esphome/components/ezo_pmp/__init__.py b/esphome/components/ezo_pmp/__init__.py index e65fcf74cae9..87cda41f8987 100644 --- a/esphome/components/ezo_pmp/__init__.py +++ b/esphome/components/ezo_pmp/__init__.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c -from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION +from esphome.const import ( + CONF_ADDRESS, + CONF_COMMAND, + CONF_ID, + CONF_DURATION, + CONF_VOLUME, +) from esphome import automation from esphome.automation import maybe_simple_id @@ -9,7 +15,6 @@ DEPENDENCIES = ["i2c"] MULTI_CONF = True -CONF_VOLUME = "volume" CONF_VOLUME_PER_MINUTE = "volume_per_minute" ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp") diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 9a05bff3a077..14cf6cc9c906 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -14,9 +14,14 @@ CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, + CONF_OFF_SPEED_CYCLE, + CONF_ON_DIRECTION_SET, + CONF_ON_OSCILLATING_SET, CONF_ON_SPEED_SET, + CONF_ON_STATE, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, + CONF_ON_PRESET_SET, CONF_TRIGGER_ID, CONF_DIRECTION, CONF_RESTORE_MODE, @@ -53,9 +58,23 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) CycleSpeedAction = fan_ns.class_("CycleSpeedAction", automation.Action) +FanStateTrigger = fan_ns.class_( + "FanStateTrigger", automation.Trigger.template(Fan.operator("ptr")) +) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) -FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template()) +FanDirectionSetTrigger = fan_ns.class_( + "FanDirectionSetTrigger", automation.Trigger.template(FanDirection) +) +FanOscillatingSetTrigger = fan_ns.class_( + "FanOscillatingSetTrigger", automation.Trigger.template(cg.bool_) +) +FanSpeedSetTrigger = fan_ns.class_( + "FanSpeedSetTrigger", automation.Trigger.template(cg.int_) +) +FanPresetSetTrigger = fan_ns.class_( + "FanPresetSetTrigger", automation.Trigger.template(cg.std_string) +) FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) @@ -85,6 +104,11 @@ cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger), + } + ), cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger), @@ -95,64 +119,117 @@ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), } ), + cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger), + } + ), + cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger), + } + ), cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), } ), + cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger), + } + ), } ) +_PRESET_MODES_SCHEMA = cv.All( + cv.ensure_list(cv.string_strict), + cv.Length(min=1), +) + + +def validate_preset_modes(value): + # Check against defined schema + value = _PRESET_MODES_SCHEMA(value) + + # Ensure preset names are unique + errors = [] + presets = set() + for i, preset in enumerate(value): + # If name does not exist yet add it + if preset not in presets: + presets.add(preset) + continue + + # Otherwise it's an error + errors.append( + cv.Invalid( + f"Found duplicate preset name '{preset}'. Presets must have unique names.", + [i], + ) + ) + + if errors: + raise cv.MultipleInvalid(errors) + + return value + async def setup_fan_core_(var, config): await setup_entity(var, config) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) - if CONF_OSCILLATION_STATE_TOPIC in config: - cg.add( - mqtt_.set_custom_oscillation_state_topic( - config[CONF_OSCILLATION_STATE_TOPIC] - ) - ) - if CONF_OSCILLATION_COMMAND_TOPIC in config: + if ( + oscillation_state_topic := config.get(CONF_OSCILLATION_STATE_TOPIC) + ) is not None: + cg.add(mqtt_.set_custom_oscillation_state_topic(oscillation_state_topic)) + if ( + oscillation_command_topic := config.get(CONF_OSCILLATION_COMMAND_TOPIC) + ) is not None: cg.add( - mqtt_.set_custom_oscillation_command_topic( - config[CONF_OSCILLATION_COMMAND_TOPIC] - ) + mqtt_.set_custom_oscillation_command_topic(oscillation_command_topic) ) - if CONF_SPEED_LEVEL_STATE_TOPIC in config: + if ( + speed_level_state_topic := config.get(CONF_SPEED_LEVEL_STATE_TOPIC) + ) is not None: + cg.add(mqtt_.set_custom_speed_level_state_topic(speed_level_state_topic)) + if ( + speed_level_command_topic := config.get(CONF_SPEED_LEVEL_COMMAND_TOPIC) + ) is not None: cg.add( - mqtt_.set_custom_speed_level_state_topic( - config[CONF_SPEED_LEVEL_STATE_TOPIC] - ) - ) - if CONF_SPEED_LEVEL_COMMAND_TOPIC in config: - cg.add( - mqtt_.set_custom_speed_level_command_topic( - config[CONF_SPEED_LEVEL_COMMAND_TOPIC] - ) - ) - if CONF_SPEED_STATE_TOPIC in config: - cg.add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC])) - if CONF_SPEED_COMMAND_TOPIC in config: - cg.add( - mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]) + mqtt_.set_custom_speed_level_command_topic(speed_level_command_topic) ) + if (speed_state_topic := config.get(CONF_SPEED_STATE_TOPIC)) is not None: + cg.add(mqtt_.set_custom_speed_state_topic(speed_state_topic)) + if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None: + cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic)) + for conf in config.get(CONF_ON_STATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf) for conf in config.get(CONF_ON_TURN_ON, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_DIRECTION_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(FanDirection, "x")], conf) + for conf in config.get(CONF_ON_OSCILLATING_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.bool_, "x")], conf) for conf in config.get(CONF_ON_SPEED_SET, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation(trigger, [(cg.int_, "x")], conf) + for conf in config.get(CONF_ON_PRESET_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) async def register_fan(var, config): @@ -205,22 +282,34 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): async def fan_turn_on_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) - if CONF_OSCILLATING in config: - template_ = await cg.templatable(config[CONF_OSCILLATING], args, bool) + if (oscillating := config.get(CONF_OSCILLATING)) is not None: + template_ = await cg.templatable(oscillating, args, bool) cg.add(var.set_oscillating(template_)) - if CONF_SPEED in config: - template_ = await cg.templatable(config[CONF_SPEED], args, int) + if (speed := config.get(CONF_SPEED)) is not None: + template_ = await cg.templatable(speed, args, int) cg.add(var.set_speed(template_)) - if CONF_DIRECTION in config: - template_ = await cg.templatable(config[CONF_DIRECTION], args, FanDirection) + if (direction := config.get(CONF_DIRECTION)) is not None: + template_ = await cg.templatable(direction, args, FanDirection) cg.add(var.set_direction(template_)) return var -@automation.register_action("fan.cycle_speed", CycleSpeedAction, FAN_ACTION_SCHEMA) +@automation.register_action( + "fan.cycle_speed", + CycleSpeedAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Fan), + cv.Optional(CONF_OFF_SPEED_CYCLE, default=True): cv.boolean, + } + ), +) async def fan_cycle_speed_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) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_OFF_SPEED_CYCLE], args, bool) + cg.add(var.set_no_off_cycle(template_)) + return var @automation.register_condition( diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 23fb70a95b1c..d480a2ef44a8 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -54,18 +54,26 @@ template class CycleSpeedAction : public Action { public: explicit CycleSpeedAction(Fan *state) : state_(state) {} + TEMPLATABLE_VALUE(bool, no_off_cycle) + void play(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) { int speed = this->state_->speed + 1; int supported_speed_count = this->state_->get_traits().supported_speed_count(); - if (speed > supported_speed_count) { - // was running at max speed, so turn off + bool off_speed_cycle = no_off_cycle_.value(x...); + if (speed > supported_speed_count && off_speed_cycle) { + // was running at max speed, off speed cycle enabled, so turn off speed = 1; auto call = this->state_->turn_off(); call.set_speed(speed); call.perform(); + } else if (speed > supported_speed_count && !off_speed_cycle) { + // was running at max speed, off speed cycle disabled, so set to lowest speed + auto call = this->state_->turn_on(); + call.set_speed(1); + call.perform(); } else { auto call = this->state_->turn_on(); call.set_speed(speed); @@ -103,6 +111,13 @@ template class FanIsOffCondition : public Condition { Fan *state_; }; +class FanStateTrigger : public Trigger { + public: + FanStateTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { this->trigger(state); }); + } +}; + class FanTurnOnTrigger : public Trigger<> { public: FanTurnOnTrigger(Fan *state) { @@ -139,15 +154,51 @@ class FanTurnOffTrigger : public Trigger<> { bool last_on_; }; -class FanSpeedSetTrigger : public Trigger<> { +class FanDirectionSetTrigger : public Trigger { + public: + FanDirectionSetTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { + auto direction = state->direction; + auto should_trigger = direction != this->last_direction_; + this->last_direction_ = direction; + if (should_trigger) { + this->trigger(direction); + } + }); + this->last_direction_ = state->direction; + } + + protected: + FanDirection last_direction_; +}; + +class FanOscillatingSetTrigger : public Trigger { + public: + FanOscillatingSetTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { + auto oscillating = state->oscillating; + auto should_trigger = oscillating != this->last_oscillating_; + this->last_oscillating_ = oscillating; + if (should_trigger) { + this->trigger(oscillating); + } + }); + this->last_oscillating_ = state->oscillating; + } + + protected: + bool last_oscillating_; +}; + +class FanSpeedSetTrigger : public Trigger { public: FanSpeedSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto speed = state->speed; - auto should_trigger = speed != !this->last_speed_; + auto should_trigger = speed != this->last_speed_; this->last_speed_ = speed; if (should_trigger) { - this->trigger(); + this->trigger(speed); } }); this->last_speed_ = state->speed; @@ -157,5 +208,23 @@ class FanSpeedSetTrigger : public Trigger<> { int last_speed_; }; +class FanPresetSetTrigger : public Trigger { + public: + FanPresetSetTrigger(Fan *state) { + state->add_on_state_callback([this, state]() { + auto preset_mode = state->preset_mode; + auto should_trigger = preset_mode != this->last_preset_mode_; + this->last_preset_mode_ = preset_mode; + if (should_trigger) { + this->trigger(preset_mode); + } + }); + this->last_preset_mode_ = state->preset_mode; + } + + protected: + std::string last_preset_mode_; +}; + } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 87566bad4a1e..95e3ae07583d 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -32,9 +32,12 @@ 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()); + } this->parent_.control(*this); } + void FanCall::validate_() { auto traits = this->parent_.get_traits(); @@ -62,6 +65,15 @@ void FanCall::validate_() { ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str()); this->direction_.reset(); } + + if (!this->preset_mode_.empty()) { + const auto &preset_modes = traits.supported_preset_modes(); + if (preset_modes.find(this->preset_mode_) == preset_modes.end()) { + ESP_LOGW(TAG, "'%s' - This fan does not support preset mode '%s'!", this->parent_.get_name().c_str(), + this->preset_mode_.c_str()); + this->preset_mode_.clear(); + } + } } FanCall FanRestoreState::to_call(Fan &fan) { @@ -70,6 +82,14 @@ FanCall FanRestoreState::to_call(Fan &fan) { call.set_oscillating(this->oscillating); call.set_speed(this->speed); call.set_direction(this->direction); + + if (fan.get_traits().supports_preset_modes()) { + // Use stored preset index to get preset name + const auto &preset_modes = fan.get_traits().supported_preset_modes(); + if (this->preset_mode < preset_modes.size()) { + call.set_preset_mode(*std::next(preset_modes.begin(), this->preset_mode)); + } + } return call; } void FanRestoreState::apply(Fan &fan) { @@ -77,6 +97,14 @@ void FanRestoreState::apply(Fan &fan) { fan.oscillating = this->oscillating; fan.speed = this->speed; fan.direction = this->direction; + + if (fan.get_traits().supports_preset_modes()) { + // Use stored preset index to get preset name + const auto &preset_modes = fan.get_traits().supported_preset_modes(); + if (this->preset_mode < preset_modes.size()) { + fan.preset_mode = *std::next(preset_modes.begin(), this->preset_mode); + } + } fan.publish_state(); } @@ -100,7 +128,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()); + } this->state_callback_.call(); this->save_state_(); } @@ -143,20 +173,36 @@ void Fan::save_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(); + // 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); + } + this->rtc_.save(&state); } void Fan::dump_traits_(const char *tag, const char *prefix) { - if (this->get_traits().supports_speed()) { + auto traits = this->get_traits(); + + if (traits.supports_speed()) { ESP_LOGCONFIG(tag, "%s Speed: YES", prefix); - ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count()); + ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, traits.supported_speed_count()); } - if (this->get_traits().supports_oscillation()) { + if (traits.supports_oscillation()) { ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix); } - if (this->get_traits().supports_direction()) { + if (traits.supports_direction()) { ESP_LOGCONFIG(tag, "%s Direction: YES", 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()); + } } } // namespace fan diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index f9d317e6759c..b74187eb4a00 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -72,6 +72,11 @@ 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_; } void perform(); @@ -83,6 +88,7 @@ class FanCall { optional oscillating_; optional speed_; optional direction_{}; + std::string preset_mode_{}; }; struct FanRestoreState { @@ -90,6 +96,7 @@ struct FanRestoreState { int speed; bool oscillating; FanDirection direction; + uint8_t preset_mode; /// Convert this struct to a fan call that can be performed. FanCall to_call(Fan &fan); @@ -107,6 +114,8 @@ 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(); diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index e69d8e2e53b6..2ef6f8b7cc56 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -1,3 +1,6 @@ +#include +#include + #pragma once namespace esphome { @@ -25,12 +28,19 @@ class FanTraits { bool supports_direction() const { return this->direction_; } /// 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; } + /// Return if preset modes are supported + bool supports_preset_modes() const { return !this->preset_modes_.empty(); } protected: bool oscillation_{false}; bool speed_{false}; bool direction_{false}; int speed_count_{}; + std::set preset_modes_{}; }; } // namespace fan diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index ecbbc3d477d5..23651bd0490b 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -13,8 +13,11 @@ CONF_ON_ENROLLMENT_DONE, CONF_ON_ENROLLMENT_FAILED, CONF_ON_ENROLLMENT_SCAN, + CONF_ON_FINGER_SCAN_START, CONF_ON_FINGER_SCAN_MATCHED, CONF_ON_FINGER_SCAN_UNMATCHED, + CONF_ON_FINGER_SCAN_MISPLACED, + CONF_ON_FINGER_SCAN_INVALID, CONF_PASSWORD, CONF_SENSING_PIN, CONF_SPEED, @@ -22,18 +25,24 @@ CONF_TRIGGER_ID, ) -CODEOWNERS = ["@OnFreund", "@loongyh"] +CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor", "sensor"] MULTI_CONF = True CONF_FINGERPRINT_GROW_ID = "fingerprint_grow_id" +CONF_SENSOR_POWER_PIN = "sensor_power_pin" +CONF_IDLE_PERIOD_TO_SLEEP = "idle_period_to_sleep" fingerprint_grow_ns = cg.esphome_ns.namespace("fingerprint_grow") FingerprintGrowComponent = fingerprint_grow_ns.class_( "FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice ) +FingerScanStartTrigger = fingerprint_grow_ns.class_( + "FingerScanStartTrigger", automation.Trigger.template() +) + FingerScanMatchedTrigger = fingerprint_grow_ns.class_( "FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16) ) @@ -42,6 +51,14 @@ "FingerScanUnmatchedTrigger", automation.Trigger.template() ) +FingerScanMisplacedTrigger = fingerprint_grow_ns.class_( + "FingerScanMisplacedTrigger", automation.Trigger.template() +) + +FingerScanInvalidTrigger = fingerprint_grow_ns.class_( + "FingerScanInvalidTrigger", automation.Trigger.template() +) + EnrollmentScanTrigger = fingerprint_grow_ns.class_( "EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16) ) @@ -87,13 +104,35 @@ } validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True) -CONFIG_SCHEMA = ( + +def validate(config): + if CONF_SENSOR_POWER_PIN in config and CONF_SENSING_PIN not in config: + raise cv.Invalid("You cannot use the Sensor Power Pin without a Sensing Pin") + if CONF_IDLE_PERIOD_TO_SLEEP in config and CONF_SENSOR_POWER_PIN not in config: + raise cv.Invalid( + "You cannot have an Idle Period to Sleep without a Sensor Power Pin" + ) + return config + + +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(FingerprintGrowComponent), cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_SENSOR_POWER_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_IDLE_PERIOD_TO_SLEEP + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_PASSWORD): cv.uint32_t, cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t, + cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanStartTrigger + ), + } + ), cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -108,6 +147,20 @@ ), } ), + cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanMisplacedTrigger + ), + } + ), + cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanInvalidTrigger + ), + } + ), cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -132,7 +185,8 @@ } ) .extend(cv.polling_component_schema("500ms")) - .extend(uart.UART_DEVICE_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), + validate, ) @@ -152,6 +206,18 @@ async def to_code(config): sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN]) cg.add(var.set_sensing_pin(sensing_pin)) + if CONF_SENSOR_POWER_PIN in config: + sensor_power_pin = await cg.gpio_pin_expression(config[CONF_SENSOR_POWER_PIN]) + cg.add(var.set_sensor_power_pin(sensor_power_pin)) + + if CONF_IDLE_PERIOD_TO_SLEEP in config: + idle_period_to_sleep_ms = config[CONF_IDLE_PERIOD_TO_SLEEP] + cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms)) + + for conf in config.get(CONF_ON_FINGER_SCAN_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( @@ -162,6 +228,14 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 4043f32dcb86..bd0817350a08 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -1,5 +1,6 @@ #include "fingerprint_grow.h" #include "esphome/core/log.h" +#include namespace esphome { namespace fingerprint_grow { @@ -14,16 +15,23 @@ void FingerprintGrowComponent::update() { return; } - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { + // A finger touch results in a low level (digital_read() == false) if (this->sensing_pin_->digital_read()) { ESP_LOGV(TAG, "No touch sensing"); this->waiting_removal_ = false; + if ((this->enrollment_image_ == 0) && // Not in enrolment process + (millis() - this->last_transfer_ms_ > this->idle_period_to_sleep_ms_) && (this->is_sensor_awake_)) { + this->sensor_sleep_(); + } return; + } else if (!this->waiting_removal_) { + this->finger_scan_start_callback_.call(); } } if (this->waiting_removal_) { - if (this->scan_image_(1) == NO_FINGER) { + if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) { ESP_LOGD(TAG, "Finger removed"); this->waiting_removal_ = false; } @@ -50,6 +58,29 @@ void FingerprintGrowComponent::update() { void FingerprintGrowComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); + + this->has_sensing_pin_ = (this->sensing_pin_ != nullptr); + this->has_power_pin_ = (this->sensor_power_pin_ != nullptr); + + // Call pins setup, so we effectively apply the config generated from the yaml file. + if (this->has_sensing_pin_) { + this->sensing_pin_->setup(); + } + if (this->has_power_pin_) { + // Starts with output low (disabling power) to avoid glitches in the sensor + this->sensor_power_pin_->digital_write(false); + this->sensor_power_pin_->setup(); + + // If the user didn't specify an idle period to sleep, applies the default. + if (this->idle_period_to_sleep_ms_ == UINT32_MAX) { + this->idle_period_to_sleep_ms_ = DEFAULT_IDLE_PERIOD_TO_SLEEP_MS; + } + } + + // Place the sensor in a known (sleep/off) state and sync internal var state. + this->sensor_sleep_(); + delay(20); // This delay guarantees the sensor will in fact be powered power. + if (this->check_password_()) { if (this->new_password_ != -1) { if (this->set_password_()) @@ -90,7 +121,7 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { } void FingerprintGrowComponent::scan_and_match_() { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Scan and match"); } else { ESP_LOGV(TAG, "Scan and match"); @@ -121,43 +152,52 @@ void FingerprintGrowComponent::scan_and_match_() { } uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Getting image %d", buffer); } else { ESP_LOGV(TAG, "Getting image %d", buffer); } this->data_ = {GET_IMAGE}; - switch (this->send_command_()) { + uint8_t send_result = this->send_command_(); + switch (send_result) { case OK: break; case NO_FINGER: - if (this->sensing_pin_ != nullptr) { - ESP_LOGD(TAG, "No finger"); + if (this->has_sensing_pin_) { + this->waiting_removal_ = true; + ESP_LOGD(TAG, "Finger Misplaced"); + this->finger_scan_misplaced_callback_.call(); } else { ESP_LOGV(TAG, "No finger"); } - return this->data_[0]; + return send_result; case IMAGE_FAIL: ESP_LOGE(TAG, "Imaging error"); + this->finger_scan_invalid_callback_.call(); + return send_result; default: - return this->data_[0]; + ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result); + return send_result; } ESP_LOGD(TAG, "Processing image %d", buffer); this->data_ = {IMAGE_2_TZ, buffer}; - switch (this->send_command_()) { + send_result = this->send_command_(); + switch (send_result) { case OK: ESP_LOGI(TAG, "Processed image %d", buffer); break; case IMAGE_MESS: ESP_LOGE(TAG, "Image too messy"); + this->finger_scan_invalid_callback_.call(); break; case FEATURE_FAIL: case INVALID_IMAGE: ESP_LOGE(TAG, "Could not find fingerprint features"); + this->finger_scan_invalid_callback_.call(); break; } - return this->data_[0]; + return send_result; } uint8_t FingerprintGrowComponent::save_fingerprint_() { @@ -204,7 +244,7 @@ bool FingerprintGrowComponent::check_password_() { } bool FingerprintGrowComponent::set_password_() { - ESP_LOGI(TAG, "Setting new password: %d", this->new_password_); + ESP_LOGI(TAG, "Setting new password: %" PRIu32, this->new_password_); this->data_ = {SET_PASSWORD, (uint8_t) (this->new_password_ >> 24), (uint8_t) (this->new_password_ >> 16), (uint8_t) (this->new_password_ >> 8), (uint8_t) (this->new_password_ & 0xFF)}; if (this->send_command_() == OK) { @@ -220,10 +260,11 @@ bool FingerprintGrowComponent::get_parameters_() { ESP_LOGD(TAG, "Getting parameters"); this->data_ = {READ_SYS_PARAM}; if (this->send_command_() == OK) { - ESP_LOGD(TAG, "Got parameters"); - if (this->status_sensor_ != nullptr) { + ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status, + if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1] this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]); } + this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4]; this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6]; if (this->capacity_sensor_ != nullptr) { this->capacity_sensor_->publish_state(this->capacity_); @@ -321,7 +362,9 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui } } -uint8_t FingerprintGrowComponent::send_command_() { +uint8_t FingerprintGrowComponent::transfer_(std::vector *p_data_buffer) { + while (this->available()) + this->read(); this->write((uint8_t) (START_CODE >> 8)); this->write((uint8_t) (START_CODE & 0xFF)); this->write(this->address_[0]); @@ -330,12 +373,12 @@ uint8_t FingerprintGrowComponent::send_command_() { this->write(this->address_[3]); this->write(COMMAND); - uint16_t wire_length = this->data_.size() + 2; + uint16_t wire_length = p_data_buffer->size() + 2; this->write((uint8_t) (wire_length >> 8)); this->write((uint8_t) (wire_length & 0xFF)); uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND; - for (auto data : this->data_) { + for (auto data : *p_data_buffer) { this->write(data); sum += data; } @@ -343,7 +386,7 @@ uint8_t FingerprintGrowComponent::send_command_() { this->write((uint8_t) (sum >> 8)); this->write((uint8_t) (sum & 0xFF)); - this->data_.clear(); + p_data_buffer->clear(); uint8_t byte; uint16_t idx = 0, length = 0; @@ -353,7 +396,9 @@ uint8_t FingerprintGrowComponent::send_command_() { delay(1); continue; } + byte = this->read(); + switch (idx) { case 0: if (byte != (uint8_t) (START_CODE >> 8)) @@ -387,9 +432,9 @@ uint8_t FingerprintGrowComponent::send_command_() { length |= byte; break; default: - this->data_.push_back(byte); + p_data_buffer->push_back(byte); if ((idx - 8) == length) { - switch (this->data_[0]) { + switch ((*p_data_buffer)[0]) { case OK: case NO_FINGER: case IMAGE_FAIL: @@ -409,29 +454,122 @@ uint8_t FingerprintGrowComponent::send_command_() { ESP_LOGE(TAG, "Reader failed to process request"); break; default: - ESP_LOGE(TAG, "Unknown response received from reader: %d", this->data_[0]); + ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", (*p_data_buffer)[0]); break; } - return this->data_[0]; + this->last_transfer_ms_ = millis(); + return (*p_data_buffer)[0]; } break; } idx++; } ESP_LOGE(TAG, "No response received from reader"); - this->data_[0] = TIMEOUT; + (*p_data_buffer)[0] = TIMEOUT; + this->last_transfer_ms_ = millis(); return TIMEOUT; } +uint8_t FingerprintGrowComponent::send_command_() { + this->sensor_wakeup_(); + return this->transfer_(&this->data_); +} + +void FingerprintGrowComponent::sensor_wakeup_() { + // Immediately return if there is no power pin or the sensor is already on + if ((!this->has_power_pin_) || (this->is_sensor_awake_)) + return; + + this->sensor_power_pin_->digital_write(true); + this->is_sensor_awake_ = true; + + uint8_t byte = TIMEOUT; + + // Wait for the byte HANDSHAKE_SIGN from the sensor meaning it is operational. + for (uint16_t timer = 0; timer < WAIT_FOR_WAKE_UP_MS; timer++) { + if (this->available() > 0) { + byte = this->read(); + + /* If the received byte is zero, the UART probably misinterpreted a raising edge on + * the RX pin due the power up as byte "zero" - I verified this behaviour using + * the esp32-arduino lib. So here we just ignore this fake byte. + */ + if (byte != 0) + break; + } + delay(1); + } + + /* Lets check if the received by is a HANDSHAKE_SIGN, otherwise log an error + * message and try to continue on the best effort. + */ + if (byte == HANDSHAKE_SIGN) { + ESP_LOGD(TAG, "Sensor has woken up!"); + } else if (byte == TIMEOUT) { + ESP_LOGE(TAG, "Timed out waiting for sensor wake-up"); + } else { + ESP_LOGE(TAG, "Received wrong byte from the sensor during wake-up: 0x%.2X", byte); + } + + /* Next step, we must authenticate with the password. We cannot call check_password_ here + * neither use data_ to store the command because it might be already in use by the caller + * of send_command_() + */ + std::vector buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16), + (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)}; + + if (this->transfer_(&buffer) != OK) { + ESP_LOGE(TAG, "Wrong password"); + } +} + +void FingerprintGrowComponent::sensor_sleep_() { + // Immediately return if the power pin feature is not implemented + if (!this->has_power_pin_) + return; + + this->sensor_power_pin_->digital_write(false); + this->is_sensor_awake_ = false; + ESP_LOGD(TAG, "Fingerprint sensor is now in sleep mode."); +} + void FingerprintGrowComponent::dump_config() { ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:"); + ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_); + ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s", + this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None"); + ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s", + this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None"); + if (this->idle_period_to_sleep_ms_ < UINT32_MAX) { + ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_); + } else { + ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never"); + } LOG_UPDATE_INTERVAL(this); - LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); - LOG_SENSOR(" ", "Status", this->status_sensor_); - LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); - LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); - LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); - LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); + if (this->fingerprint_count_sensor_) { + LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state()); + } + if (this->status_sensor_) { + LOG_SENSOR(" ", "Status", this->status_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state()); + } + if (this->capacity_sensor_) { + LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state()); + } + if (this->security_level_sensor_) { + LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state()); + } + if (this->last_finger_id_sensor_) { + LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state()); + } + if (this->last_confidence_sensor_) { + LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state()); + } } } // namespace fingerprint_grow diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index f414146e64ef..20ff60997bc6 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -15,6 +15,11 @@ static const uint16_t START_CODE = 0xEF01; static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF; +// The datasheet says a max wake up time of of 200ms. +static const uint8_t WAIT_FOR_WAKE_UP_MS = 200; + +static const uint32_t DEFAULT_IDLE_PERIOD_TO_SLEEP_MS = 5000; + enum GrowPacketType { COMMAND = 0x01, DATA = 0x02, @@ -63,6 +68,7 @@ enum GrowResponse { INVALID_IMAGE = 0x15, FLASH_ERR = 0x18, INVALID_REG = 0x1A, + HANDSHAKE_SIGN = 0x55, BAD_PACKET = 0xFE, TIMEOUT = 0xFF, }; @@ -99,8 +105,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic this->address_[3] = (uint8_t) (address & 0xFF); } void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_pin; } + void set_sensor_power_pin(GPIOPin *sensor_power_pin) { this->sensor_power_pin_ = sensor_power_pin; } void set_password(uint32_t password) { this->password_ = password; } void set_new_password(uint32_t new_password) { this->new_password_ = new_password; } + void set_idle_period_to_sleep_ms(uint32_t period_ms) { this->idle_period_to_sleep_ms_ = period_ms; } void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) { this->fingerprint_count_sensor_ = fingerprint_count_sensor; } @@ -118,12 +126,21 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { this->enrolling_binary_sensor_ = enrolling_binary_sensor; } + void add_on_finger_scan_start_callback(std::function callback) { + this->finger_scan_start_callback_.add(std::move(callback)); + } void add_on_finger_scan_matched_callback(std::function callback) { this->finger_scan_matched_callback_.add(std::move(callback)); } void add_on_finger_scan_unmatched_callback(std::function callback) { this->finger_scan_unmatched_callback_.add(std::move(callback)); } + void add_on_finger_scan_misplaced_callback(std::function callback) { + this->finger_scan_misplaced_callback_.add(std::move(callback)); + } + void add_on_finger_scan_invalid_callback(std::function callback) { + this->finger_scan_invalid_callback_.add(std::move(callback)); + } void add_on_enrollment_scan_callback(std::function callback) { this->enrollment_scan_callback_.add(std::move(callback)); } @@ -151,7 +168,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic bool set_password_(); bool get_parameters_(); void get_fingerprint_count_(); + uint8_t transfer_(std::vector *p_data_buffer); uint8_t send_command_(); + void sensor_wakeup_(); + void sensor_sleep_(); std::vector data_ = {}; uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; @@ -159,12 +179,19 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint32_t password_ = 0x0; uint32_t new_password_ = -1; GPIOPin *sensing_pin_{nullptr}; + GPIOPin *sensor_power_pin_{nullptr}; uint8_t enrollment_image_ = 0; uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; uint8_t enrollment_buffers_ = 5; bool waiting_removal_ = false; + bool has_sensing_pin_ = false; + bool has_power_pin_ = false; + bool is_sensor_awake_ = false; + uint32_t last_transfer_ms_ = 0; uint32_t last_aura_led_control_ = 0; uint16_t last_aura_led_duration_ = 0; + uint16_t system_identifier_code_ = 0; + uint32_t idle_period_to_sleep_ms_ = UINT32_MAX; sensor::Sensor *fingerprint_count_sensor_{nullptr}; sensor::Sensor *status_sensor_{nullptr}; sensor::Sensor *capacity_sensor_{nullptr}; @@ -172,13 +199,23 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic sensor::Sensor *last_finger_id_sensor_{nullptr}; sensor::Sensor *last_confidence_sensor_{nullptr}; binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr}; + CallbackManager finger_scan_invalid_callback_; + CallbackManager finger_scan_start_callback_; CallbackManager finger_scan_matched_callback_; CallbackManager finger_scan_unmatched_callback_; + CallbackManager finger_scan_misplaced_callback_; CallbackManager enrollment_scan_callback_; CallbackManager enrollment_done_callback_; CallbackManager enrollment_failed_callback_; }; +class FingerScanStartTrigger : public Trigger<> { + public: + explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_start_callback([this]() { this->trigger(); }); + } +}; + class FingerScanMatchedTrigger : public Trigger { public: explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) { @@ -194,6 +231,20 @@ class FingerScanUnmatchedTrigger : public Trigger<> { } }; +class FingerScanMisplacedTrigger : public Trigger<> { + public: + explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); }); + } +}; + +class FingerScanInvalidTrigger : public Trigger<> { + public: + explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); }); + } +}; + class EnrollmentScanTrigger : public Trigger { public: explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) { diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 52f877d986b9..b3a5beb19930 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,64 +1,104 @@ +import hashlib +import logging + import functools from pathlib import Path -import hashlib import os import re from packaging import version - import requests from esphome import core +from esphome import external_files import esphome.config_validation as cv import esphome.codegen as cg -from esphome.helpers import copy_file_if_changed +from esphome.helpers import ( + copy_file_if_changed, + cpp_string_escape, +) from esphome.const import ( + __version__, CONF_FAMILY, CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE, + CONF_REFRESH, CONF_SIZE, CONF_PATH, CONF_WEIGHT, + CONF_URL, +) +from esphome.core import ( + CORE, + HexInt, ) -from esphome.core import CORE, HexInt +_LOGGER = logging.getLogger(__name__) DOMAIN = "font" DEPENDENCIES = ["display"] MULTI_CONF = True +CODEOWNERS = ["@esphome/core", "@clydebarrow"] + 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" +CONF_FONTS = "fonts" + + +def glyph_comparator(x, y): + x_ = x.encode("utf-8") + y_ = y.encode("utf-8") + + for c in range(min(len(x_), len(y_))): + if x_[c] < y_[c]: + return -1 + if x_[c] > y_[c]: + return 1 + + if len(x_) < len(y_): + return -1 + if len(x_) > len(y_): + return 1 + raise cv.Invalid(f"Found duplicate glyph {x}") + def validate_glyphs(value): if isinstance(value, list): value = cv.Schema([cv.string])(value) value = cv.Schema([cv.string])(list(value)) - def comparator(x, y): - x_ = x.encode("utf-8") - y_ = y.encode("utf-8") + value.sort(key=functools.cmp_to_key(glyph_comparator)) + return value - for c in range(min(len(x_), len(y_))): - if x_[c] < y_[c]: - return -1 - if x_[c] > y_[c]: - return 1 - if len(x_) < len(y_): - return -1 - if len(x_) > len(y_): - return 1 - raise cv.Invalid(f"Found duplicate glyph {x}") +font_map = {} - value.sort(key=functools.cmp_to_key(comparator)) - return value + +def merge_glyphs(config): + glyphs = [] + glyphs.extend(config[CONF_GLYPHS]) + font_list = [(EFont(config[CONF_FILE], config[CONF_SIZE], config[CONF_GLYPHS]))] + if extras := config.get(CONF_EXTRAS): + extra_fonts = list( + map( + lambda x: EFont(x[CONF_FILE], config[CONF_SIZE], x[CONF_GLYPHS]), extras + ) + ) + font_list.extend(extra_fonts) + for extra in extras: + glyphs.extend(extra[CONF_GLYPHS]) + validate_glyphs(glyphs) + font_map[config[CONF_ID]] = font_list + return config def validate_pillow_installed(value): @@ -67,51 +107,35 @@ def validate_pillow_installed(value): except ImportError as err: raise cv.Invalid( "Please install the pillow python package to use this feature. " - '(pip install pillow">4.0.0,<10.0.0")' + '(pip install "pillow==10.2.0")' ) from err - if version.parse(PIL.__version__) < version.parse("4.0.0"): - raise cv.Invalid( - "Please update your pillow installation to at least 4.0.x. " - '(pip install pillow">4.0.0,<10.0.0")' - ) - if version.parse(PIL.__version__) >= version.parse("10.0.0"): + if version.parse(PIL.__version__) != version.parse("10.2.0"): raise cv.Invalid( - "Please downgrade your pillow installation to below 10.0.0. " - '(pip install pillow">4.0.0,<10.0.0")' + "Please update your pillow installation to 10.2.0. " + '(pip install "pillow==10.2.0")' ) return value +FONT_EXTENSIONS = (".ttf", ".woff", ".otf") + + def validate_truetype_file(value): - if value.endswith(".zip"): # for Google Fonts downloads + if value.lower().endswith(".zip"): # for Google Fonts downloads raise cv.Invalid( f"Please unzip the font archive '{value}' first and then use the .ttf files inside." ) - if not value.endswith(".ttf"): - raise cv.Invalid( - "Only truetype (.ttf) files are supported. Please make sure you're " - "using the correct format or rename the extension to .ttf" - ) + if not any(map(value.lower().endswith, FONT_EXTENSIONS)): + raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.") return cv.file_(value) -def _compute_local_font_dir(name) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN - h = hashlib.new("sha256") - h.update(name.encode()) - return base_dir / h.hexdigest()[:8] - - -def _compute_gfonts_local_path(value) -> Path: - name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" - return _compute_local_font_dir(name) / "font.ttf" - - TYPE_LOCAL = "local" TYPE_LOCAL_BITMAP = "local_bitmap" TYPE_GFONTS = "gfonts" +TYPE_WEB = "web" LOCAL_SCHEMA = cv.Schema( { cv.Required(CONF_PATH): validate_truetype_file, @@ -142,22 +166,64 @@ def validate_weight_name(value): return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)] -def download_gfonts(value): - wght = value[CONF_WEIGHT] - if value[CONF_ITALIC]: - wght = f"1,{wght}" - name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}" - url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}" +def _compute_local_font_path(value: dict) -> Path: + url = value[CONF_URL] + h = hashlib.new("sha256") + h.update(url.encode()) + key = h.hexdigest()[:8] + base_dir = external_files.compute_local_file_dir(DOMAIN) + _LOGGER.debug("_compute_local_font_path: base_dir=%s", base_dir / key) + return base_dir / key + + +def get_font_path(value, type) -> Path: + if type == TYPE_GFONTS: + name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" + return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf" + if type == TYPE_WEB: + return _compute_local_font_path(value) / "font.ttf" + return None + + +def download_content(url: str, path: Path) -> None: + if not external_files.has_remote_file_changed(url, path): + _LOGGER.debug("Remote file has not changed %s", url) + return + + _LOGGER.debug( + "Remote file has changed, downloading from %s to %s", + url, + path, + ) + + try: + req = requests.get( + url, + timeout=external_files.NETWORK_TIMEOUT, + headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, + ) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download from {url}: {e}") + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(req.content) + + +def download_gfont(value): + name = ( + f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}" + ) + url = f"https://fonts.googleapis.com/css2?family={name}" + path = get_font_path(value, TYPE_GFONTS) + _LOGGER.debug("download_gfont: path=%s", path) - path = _compute_gfonts_local_path(value) - if path.is_file(): - return value try: - req = requests.get(url, timeout=30) + req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid( - f"Could not download font for {name}, please check the fonts exists " + f"Could not download font at {url}, please check the fonts exists " f"at google fonts ({e})" ) match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text) @@ -168,26 +234,48 @@ def download_gfonts(value): ) ttf_url = match.group(1) - try: - req = requests.get(ttf_url, timeout=30) - req.raise_for_status() - except requests.exceptions.RequestException as e: - raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") + _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) - path.parent.mkdir(exist_ok=True, parents=True) - path.write_bytes(req.content) + download_content(ttf_url, path) return value -GFONTS_SCHEMA = cv.All( +def download_web_font(value): + url = value[CONF_URL] + path = get_font_path(value, TYPE_WEB) + + download_content(url, path) + _LOGGER.debug("download_web_font: path=%s", path) + return value + + +EXTERNAL_FONT_SCHEMA = cv.Schema( { - cv.Required(CONF_FAMILY): cv.string_strict, cv.Optional(CONF_WEIGHT, default="regular"): cv.Any( cv.int_, validate_weight_name ), cv.Optional(CONF_ITALIC, default=False): cv.boolean, - }, - download_gfonts, + cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), + } +) + + +GFONTS_SCHEMA = cv.All( + EXTERNAL_FONT_SCHEMA.extend( + { + cv.Required(CONF_FAMILY): cv.string_strict, + } + ), + download_gfont, +) + +WEB_FONT_SCHEMA = cv.All( + EXTERNAL_FONT_SCHEMA.extend( + { + cv.Required(CONF_URL): cv.string_strict, + } + ), + download_web_font, ) @@ -207,6 +295,14 @@ def validate_file_shorthand(value): data[CONF_WEIGHT] = weight[1:] return FILE_SCHEMA(data) + if value.startswith("http://") or value.startswith("https://"): + return FILE_SCHEMA( + { + CONF_TYPE: TYPE_WEB, + CONF_URL: value, + } + ) + if value.endswith(".pcf") or value.endswith(".bdf"): return FILE_SCHEMA( { @@ -228,6 +324,7 @@ def validate_file_shorthand(value): TYPE_LOCAL: LOCAL_SCHEMA, TYPE_GFONTS: GFONTS_SCHEMA, TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, + TYPE_WEB: WEB_FONT_SCHEMA, } ) @@ -238,11 +335,10 @@ def _file_schema(value): return TYPED_FILE_SCHEMA(value) -FILE_SCHEMA = cv.Schema(_file_schema) - +FILE_SCHEMA = cv.All(_file_schema) DEFAULT_GLYPHS = ( - ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' + ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_GLYPH_ID = "raw_glyph_id" @@ -252,12 +348,22 @@ def _file_schema(value): cv.Required(CONF_FILE): FILE_SCHEMA, cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), + cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8), + cv.Optional(CONF_EXTRAS): cv.ensure_list( + cv.Schema( + { + cv.Required(CONF_FILE): FILE_SCHEMA, + cv.Required(CONF_GLYPHS): validate_glyphs, + } + ) + ), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData), - } + }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) +CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, merge_glyphs) + # PIL doesn't provide a consistent interface for both TrueType and bitmap # fonts. So, we use our own wrappers to give us the consistency that we need. @@ -294,18 +400,41 @@ def getmetrics(self, glyphs): for glyph in glyphs: mask = self.getmask(glyph, mode="1") _, height = mask.size - if height > max_height: - max_height = height + max_height = max(max_height, height) return (max_height, 0) -def convert_bitmap_to_pillow_font(filepath): - from PIL import PcfFontFile, BdfFontFile +class EFont: + def __init__(self, file, size, glyphs): + self.glyphs = glyphs + ftype = file[CONF_TYPE] + if ftype == TYPE_LOCAL_BITMAP: + font = load_bitmap_font(CORE.relative_config_path(file[CONF_PATH])) + elif ftype == TYPE_LOCAL: + path = CORE.relative_config_path(file[CONF_PATH]) + font = load_ttf_font(path, size) + elif ftype in (TYPE_GFONTS, TYPE_WEB): + path = get_font_path(file, ftype) + font = load_ttf_font(path, size) + else: + raise cv.Invalid(f"Could not load font: unknown type: {ftype}") + self.font = font + self.ascent, self.descent = font.getmetrics(glyphs) + + def has_glyph(self, glyph): + return glyph in self.glyphs + - local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename( - filepath +def convert_bitmap_to_pillow_font(filepath): + from PIL import ( + PcfFontFile, + BdfFontFile, ) + local_bitmap_font_file = external_files.compute_local_file_dir( + DOMAIN, + ) / os.path.basename(filepath) + copy_file_if_changed(filepath, local_bitmap_font_file) with open(local_bitmap_font_file, "rb") as fp: @@ -354,60 +483,82 @@ def load_ttf_font(path, size): return TrueTypeFontWrapper(font) -async def to_code(config): - conf = config[CONF_FILE] - if conf[CONF_TYPE] == TYPE_LOCAL_BITMAP: - font = load_bitmap_font(CORE.relative_config_path(conf[CONF_PATH])) - elif conf[CONF_TYPE] == TYPE_LOCAL: - path = CORE.relative_config_path(conf[CONF_PATH]) - font = load_ttf_font(path, config[CONF_SIZE]) - elif conf[CONF_TYPE] == TYPE_GFONTS: - path = _compute_gfonts_local_path(conf) - font = load_ttf_font(path, config[CONF_SIZE]) - else: - raise core.EsphomeError(f"Could not load font: unknown type: {conf[CONF_TYPE]}") +class GlyphInfo: + def __init__(self, data_len, offset_x, offset_y, width, height): + self.data_len = data_len + self.offset_x = offset_x + self.offset_y = offset_y + self.width = width + self.height = height - ascent, descent = font.getmetrics(config[CONF_GLYPHS]) +async def to_code(config): + glyph_to_font_map = {} + font_list = font_map[config[CONF_ID]] + glyphs = [] + for font in font_list: + glyphs.extend(font.glyphs) + for glyph in font.glyphs: + glyph_to_font_map[glyph] = font + glyphs.sort(key=functools.cmp_to_key(glyph_comparator)) glyph_args = {} data = [] - for glyph in config[CONF_GLYPHS]: - mask = font.getmask(glyph, mode="1") + bpp = config[CONF_BPP] + if bpp == 1: + mode = "1" + scale = 1 + else: + mode = "L" + scale = 256 // (1 << bpp) + for glyph in glyphs: + font = glyph_to_font_map[glyph].font + mask = font.getmask(glyph, mode=mode) offset_x, offset_y = font.getoffset(glyph) width, height = mask.size - width8 = ((width + 7) // 8) * 8 - glyph_data = [0] * (height * width8 // 8) + glyph_data = [0] * ((height * width * bpp + 7) // 8) + pos = 0 for y in range(height): for x in range(width): - if not mask.getpixel((x, y)): - continue - pos = x + y * width8 - glyph_data[pos // 8] |= 0x80 >> (pos % 8) - glyph_args[glyph] = (len(data), offset_x, offset_y, width, height) + pixel = mask.getpixel((x, y)) // scale + for bit_num in range(bpp): + if pixel & (1 << (bpp - bit_num - 1)): + glyph_data[pos // 8] |= 0x80 >> (pos % 8) + pos += 1 + glyph_args[glyph] = GlyphInfo(len(data), offset_x, offset_y, width, height) data += glyph_data rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) glyph_initializer = [] - for glyph in config[CONF_GLYPHS]: + for glyph in glyphs: glyph_initializer.append( cg.StructInitializer( GlyphData, - ("a_char", glyph), + ( + "a_char", + cg.RawExpression(f"(const uint8_t *){cpp_string_escape(glyph)}"), + ), ( "data", - cg.RawExpression(f"{str(prog_arr)} + {str(glyph_args[glyph][0])}"), + cg.RawExpression( + f"{str(prog_arr)} + {str(glyph_args[glyph].data_len)}" + ), ), - ("offset_x", glyph_args[glyph][1]), - ("offset_y", glyph_args[glyph][2]), - ("width", glyph_args[glyph][3]), - ("height", glyph_args[glyph][4]), + ("offset_x", glyph_args[glyph].offset_x), + ("offset_y", glyph_args[glyph].offset_y), + ("width", glyph_args[glyph].width), + ("height", glyph_args[glyph].height), ) ) glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) cg.new_Pvariable( - config[CONF_ID], glyphs, len(glyph_initializer), ascent, ascent + descent + config[CONF_ID], + glyphs, + len(glyph_initializer), + font_list[0].ascent, + font_list[0].ascent + font_list[0].descent, + bpp, ) diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index ef5b2b788df8..3b62b8ca6693 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -10,29 +10,10 @@ namespace font { static const char *const TAG = "font"; -void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const { - int scan_x1, scan_y1, scan_width, scan_height; - this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - - const unsigned char *data = this->glyph_data_->data; - const int max_x = x_at + scan_x1 + scan_width; - const int max_y = y_start + scan_y1 + scan_height; - - 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; data++, glyph_x += 8) { - uint8_t pixel_data = progmem_read_byte(data); - const int pixel_max_x = std::min(max_x, glyph_x + 8); - - for (int pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) { - if (pixel_data & 0x80) { - display->draw_pixel_at(pixel_x, glyph_y, color); - } - } - } - } -} -const char *Glyph::get_char() const { return this->glyph_data_->a_char; } -bool Glyph::compare_to(const char *str) const { +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++) { @@ -48,7 +29,7 @@ bool Glyph::compare_to(const char *str) const { // this should not happen return false; } -int Glyph::match_length(const char *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') return i; @@ -65,12 +46,13 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { *height = this->glyph_data_->height; } -Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { +Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp) + : baseline_(baseline), height_(height), 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 char *str, int *match_length) { +int Font::match_next_glyph(const uint8_t *str, int *match_length) { int lo = 0; int hi = this->glyphs_.size() - 1; while (lo != hi) { @@ -95,7 +77,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in int x = 0; while (str[i] != '\0') { int match_length; - int glyph_n = this->match_next_glyph(str + i, &match_length); + int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length); if (glyph_n < 0) { // Unknown char, skip if (!this->get_glyphs().empty()) @@ -118,12 +100,13 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *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) { +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(text + i, &match_length); + int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length); if (glyph_n < 0) { // Unknown char, skip ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); @@ -138,7 +121,44 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo } const Glyph &glyph = this->get_glyphs()[glyph_n]; - glyph.draw(x_at, y_start, display, color); + glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); + + const uint8_t *data = glyph.glyph_data_->data; + const int max_x = x_at + scan_x1 + scan_width; + const int max_y = y_start + scan_y1 + scan_height; + + uint8_t bitmask = 0; + uint8_t pixel_data = 0; + uint8_t bpp_max = (1 << this->bpp_) - 1; + auto diff_r = (float) color.r - (float) background.r; + auto diff_g = (float) color.g - (float) background.g; + auto diff_b = (float) color.b - (float) background.b; + auto b_r = (float) background.r; + auto b_g = (float) background.g; + auto b_b = (float) background.g; + 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++) { + uint8_t pixel = 0; + for (int bit_num = 0; bit_num != this->bpp_; bit_num++) { + if (bitmask == 0) { + pixel_data = progmem_read_byte(data++); + bitmask = 0x80; + } + pixel <<= 1; + if ((pixel_data & bitmask) != 0) + pixel |= 1; + bitmask >>= 1; + } + if (pixel == bpp_max) { + display->draw_pixel_at(glyph_x, glyph_y, color); + } else if (pixel != 0) { + auto on = (float) pixel / (float) bpp_max; + auto blended = + Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b)); + display->draw_pixel_at(glyph_x, glyph_y, blended); + } + } + } x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 03171a61263e..57002cf510cd 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -10,7 +10,7 @@ namespace font { class Font; struct GlyphData { - const char *a_char; + const uint8_t *a_char; const uint8_t *data; int offset_x; int offset_y; @@ -22,16 +22,16 @@ class Glyph { public: Glyph(const GlyphData *data) : glyph_data_(data) {} - void draw(int x, int y, display::Display *display, Color color) const; + const uint8_t *get_char() const; - const char *get_char() const; + bool compare_to(const uint8_t *str) const; - bool compare_to(const char *str) const; - - int match_length(const char *str) const; + int match_length(const uint8_t *str) const; void scan_area(int *x1, int *y1, int *width, int *height) const; + const GlyphData *get_glyph_data() const { return this->glyph_data_; } + protected: friend Font; @@ -46,14 +46,16 @@ class Font : public display::BaseFont { * @param baseline The y-offset from the top of the text to the baseline. * @param bottom The y-offset from the top of the text to the bottom (i.e. height). */ - Font(const GlyphData *data, int data_nr, int baseline, int height); + Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1); - int match_next_glyph(const char *str, int *match_length); + int match_next_glyph(const uint8_t *str, int *match_length); - void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override; + void print(int x_start, int y_start, display::Display *display, Color color, const char *text, + Color background) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } + inline int get_bpp() { return this->bpp_; } const std::vector> &get_glyphs() const { return glyphs_; } @@ -61,6 +63,7 @@ class Font : public display::BaseFont { std::vector> glyphs_; int baseline_; int height_; + uint8_t bpp_; // bits per pixel }; } // namespace font diff --git a/esphome/components/ft5x06/__init__.py b/esphome/components/ft5x06/__init__.py new file mode 100644 index 000000000000..dceea71dd073 --- /dev/null +++ b/esphome/components/ft5x06/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["i2c"] + +ft5x06_ns = cg.esphome_ns.namespace("ft5x06") diff --git a/esphome/components/ft5x06/touchscreen/__init__.py b/esphome/components/ft5x06/touchscreen/__init__.py new file mode 100644 index 000000000000..adeeac0d1a44 --- /dev/null +++ b/esphome/components/ft5x06/touchscreen/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID +from .. import ft5x06_ns + +FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener") +FT5x06Touchscreen = ft5x06_ns.class_( + "FT5x06Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(FT5x06Touchscreen), + } +).extend(i2c.i2c_device_schema(0x48)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await i2c.register_i2c_device(var, config) + await touchscreen.register_touchscreen(var, config) diff --git a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h new file mode 100644 index 000000000000..7ddd2e44d72e --- /dev/null +++ b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ft5x06 { + +static const char *const TAG = "ft5x06.touchscreen"; + +enum VendorId { + FT5X06_ID_UNKNOWN = 0, + FT5X06_ID_1 = 0x51, + FT5X06_ID_2 = 0x11, + FT5X06_ID_3 = 0xCD, +}; + +enum FTCmd : uint8_t { + FT5X06_MODE_REG = 0x00, + FT5X06_ORIGIN_REG = 0x08, + FT5X06_RESOLUTION_REG = 0x0C, + FT5X06_VENDOR_ID_REG = 0xA8, + FT5X06_TD_STATUS = 0x02, + FT5X06_TOUCH_DATA = 0x03, + FT5X06_I_MODE = 0xA4, + FT5X06_TOUCH_MAX = 0x4C, +}; + +enum FTMode : uint8_t { + FT5X06_OP_MODE = 0, + FT5X06_SYSINFO_MODE = 0x10, + FT5X06_TEST_MODE = 0x40, +}; + +static const size_t MAX_TOUCHES = 5; // max number of possible touches reported + +class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override { + esph_log_config(TAG, "Setting up FT5x06 Touchscreen..."); + // wait 200ms after reset. + this->set_timeout(200, [this] { this->continue_setup_(); }); + } + + void continue_setup_(void) { + uint8_t data[4]; + if (!this->set_mode_(FT5X06_OP_MODE)) + return; + + if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID")) + return; + switch (data[0]) { + case FT5X06_ID_1: + case FT5X06_ID_2: + case FT5X06_ID_3: + this->vendor_id_ = (VendorId) data[0]; + esph_log_d(TAG, "Read vendor ID 0x%X", data[0]); + break; + + default: + esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]); + this->mark_failed(); + return; + } + // reading the chip registers to get max x/y does not seem to work. + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = this->display_->get_native_height(); + } + } + esph_log_config(TAG, "FT5x06 Touchscreen setup complete"); + } + + void update_touches() override { + uint8_t touch_cnt; + uint8_t data[MAX_TOUCHES][6]; + + if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) { + esph_log_w(TAG, "Failed to read status"); + return; + } + if (touch_cnt == 0) + return; + + if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) { + esph_log_w(TAG, "Failed to read touch data"); + return; + } + for (uint8_t i = 0; i != touch_cnt; i++) { + uint8_t status = data[i][0] >> 6; + uint8_t id = data[i][2] >> 3; + uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]); + uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]); + + esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y); + if (status == 0 || status == 2) { + this->add_raw_touch_position_(id, x, y); + } + } + } + + void dump_config() override { + esph_log_config(TAG, "FT5x06 Touchscreen:"); + esph_log_config(TAG, " Address: 0x%02X", this->address_); + esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_); + } + + protected: + bool err_check_(i2c::ErrorCode err, const char *msg) { + if (err != i2c::ERROR_OK) { + this->mark_failed(); + esph_log_e(TAG, "%s failed - err 0x%X", msg, err); + return false; + } + return true; + } + bool set_mode_(FTMode mode) { + return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode"); + } + VendorId vendor_id_{FT5X06_ID_UNKNOWN}; +}; + +} // namespace ft5x06 +} // namespace esphome diff --git a/esphome/components/ft63x6/__init__.py b/esphome/components/ft63x6/__init__.py new file mode 100644 index 000000000000..b6d7d3580ed1 --- /dev/null +++ b/esphome/components/ft63x6/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@gpambrozio"] diff --git a/esphome/components/ft63x6/ft63x6.cpp b/esphome/components/ft63x6/ft63x6.cpp new file mode 100644 index 000000000000..e5f761390116 --- /dev/null +++ b/esphome/components/ft63x6/ft63x6.cpp @@ -0,0 +1,134 @@ +/**************************************************************************/ +/*! + Author: Gustavo Ambrozio + Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U) +*/ +/**************************************************************************/ + +#include "ft63x6.h" +#include "esphome/core/log.h" + +// Registers +// Reference: https://focuslcds.com/content/FT6236.pdf +namespace esphome { +namespace ft63x6 { +static const uint8_t FT6X36_ADDR_DEVICE_MODE = 0x00; + +static const uint8_t FT63X6_ADDR_TD_STATUS = 0x02; +static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03; +static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03; +static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; +static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05; +static const uint8_t FT63X6_ADDR_TOUCH1_WEIGHT = 0x07; +static const uint8_t FT63X6_ADDR_TOUCH1_MISC = 0x08; +static const uint8_t FT6X36_ADDR_THRESHHOLD = 0x80; +static const uint8_t FT6X36_ADDR_TOUCHRATE_ACTIVE = 0x88; +static const uint8_t FT63X6_ADDR_CHIP_ID = 0xA3; + +static const char *const TAG = "FT63X6"; + +void FT63X6Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up FT63X6 Touchscreen..."); + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_ANY_EDGE); + } + + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->hard_reset_(); + } + + // Get touch resolution + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = 320; + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = 480; + } + uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID); + if (chip_id != 0) { + ESP_LOGI(TAG, "FT6336U touch driver started chipid: %d", chip_id); + } else { + ESP_LOGE(TAG, "FT6336U touch driver failed to start"); + } + this->write_byte(FT6X36_ADDR_DEVICE_MODE, 0x00); + this->write_byte(FT6X36_ADDR_THRESHHOLD, this->threshold_); + this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E); +} + +void FT63X6Touchscreen::hard_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + } +} + +void FT63X6Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void FT63X6Touchscreen::update_touches() { + uint16_t touch_id, x, y; + + uint8_t touches = this->read_touch_number_(); + ESP_LOGV(TAG, "Touches found: %d", touches); + if ((touches == 0x00) || (touches == 0xff)) { + // ESP_LOGD(TAG, "No touches detected"); + return; + } + + for (auto point = 0; point < touches; point++) { + if (((this->read_touch_event_(point)) & 0x01) == 0) { // checking event flag bit 6 if it is null + touch_id = this->read_touch_id_(point); // id1 = 0 or 1 + x = this->read_touch_x_(point); + y = this->read_touch_y_(point); + if ((x == 0) && (y == 0)) { + ESP_LOGW(TAG, "Reporting a (0,0) touch on %d", touch_id); + } + this->add_raw_touch_position_(touch_id, x, y, this->read_touch_weight_(point)); + } + } +} + +uint8_t FT63X6Touchscreen::read_touch_number_() { return this->read_byte_(FT63X6_ADDR_TD_STATUS) & 0x0F; } +// Touch 1 functions +uint16_t FT63X6Touchscreen::read_touch_x_(uint8_t touch) { + uint8_t read_buf[2]; + read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)); + read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + 1 + (touch * 6)); + return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; +} +uint16_t FT63X6Touchscreen::read_touch_y_(uint8_t touch) { + uint8_t read_buf[2]; + read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + (touch * 6)); + read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + 1 + (touch * 6)); + return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; +} +uint8_t FT63X6Touchscreen::read_touch_event_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)) >> 6; +} +uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_ID + (touch * 6)) >> 4; +} +uint8_t FT63X6Touchscreen::read_touch_weight_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_WEIGHT + (touch * 6)); +} +uint8_t FT63X6Touchscreen::read_touch_misc_(uint8_t touch) { + return this->read_byte_(FT63X6_ADDR_TOUCH1_MISC + (touch * 6)) >> 4; +} + +uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) { + uint8_t byte = 0; + this->read_byte(addr, &byte); + return byte; +} + +} // namespace ft63x6 +} // namespace esphome diff --git a/esphome/components/ft63x6/ft63x6.h b/esphome/components/ft63x6/ft63x6.h new file mode 100644 index 000000000000..8000894294ee --- /dev/null +++ b/esphome/components/ft63x6/ft63x6.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/*! + Author: Gustavo Ambrozio + Based on work by: Atsushi Sasaki (https://github.com/aselectroworks/Arduino-FT6336U) +*/ +/**************************************************************************/ + +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace ft63x6 { + +using namespace touchscreen; + +static const uint8_t FT6X36_DEFAULT_THRESHOLD = 22; + +class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + void set_threshold(uint8_t threshold) { this->threshold_ = threshold; } + + protected: + void hard_reset_(); + void update_touches() override; + + InternalGPIOPin *interrupt_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + uint8_t threshold_{FT6X36_DEFAULT_THRESHOLD}; + + uint8_t read_touch_number_(); + + uint16_t read_touch_x_(uint8_t touch); + uint16_t read_touch_y_(uint8_t touch); + uint8_t read_touch_event_(uint8_t touch); + uint8_t read_touch_id_(uint8_t touch); + uint8_t read_touch_weight_(uint8_t touch); + uint8_t read_touch_misc_(uint8_t touch); + + uint8_t read_byte_(uint8_t addr); +}; + +} // namespace ft63x6 +} // namespace esphome diff --git a/esphome/components/ft63x6/touchscreen.py b/esphome/components/ft63x6/touchscreen.py new file mode 100644 index 000000000000..95fa3714338e --- /dev/null +++ b/esphome/components/ft63x6/touchscreen.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_THRESHOLD + +CODEOWNERS = ["@gpambrozio"] +DEPENDENCIES = ["i2c"] + +ft6336u_ns = cg.esphome_ns.namespace("ft63x6") +FT63X6Touchscreen = ft6336u_ns.class_( + "FT63X6Touchscreen", + touchscreen.Touchscreen, + i2c.I2CDevice, +) + +CONF_FT63X6_ID = "ft63x6_id" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(FT63X6Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_THRESHOLD): cv.uint8_t, + } + ).extend(i2c.i2c_device_schema(0x38)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + + if interrupt_pin_config := config.get(CONF_INTERRUPT_PIN): + interrupt_pin = await cg.gpio_pin_expression(interrupt_pin_config) + cg.add(var.set_interrupt_pin(interrupt_pin)) + if reset_pin_config := config.get(CONF_RESET_PIN): + reset_pin = await cg.gpio_pin_expression(reset_pin_config) + cg.add(var.set_reset_pin(reset_pin)) diff --git a/esphome/components/gdk101/__init__.py b/esphome/components/gdk101/__init__.py new file mode 100644 index 000000000000..0d902579643d --- /dev/null +++ b/esphome/components/gdk101/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@Szewcson"] + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +CONF_GDK101_ID = "gdk101_id" + +gdk101_ns = cg.esphome_ns.namespace("gdk101") +GDK101Component = gdk101_ns.class_( + "GDK101Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GDK101Component), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x18)) +) + + +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) diff --git a/esphome/components/gdk101/binary_sensor.py b/esphome/components/gdk101/binary_sensor.py new file mode 100644 index 000000000000..2a3d6f07eb94 --- /dev/null +++ b/esphome/components/gdk101/binary_sensor.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_VIBRATIONS, + DEVICE_CLASS_VIBRATION, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_VIBRATE, +) +from . import CONF_GDK101_ID, GDK101Component + +DEPENDENCIES = ["gdk101"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component), + cv.Required(CONF_VIBRATIONS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_VIBRATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_VIBRATE, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_GDK101_ID]) + var = await binary_sensor.new_binary_sensor(config[CONF_VIBRATIONS]) + cg.add(hub.set_vibration_binary_sensor(var)) diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp new file mode 100644 index 000000000000..93f3c20fa877 --- /dev/null +++ b/esphome/components/gdk101/gdk101.cpp @@ -0,0 +1,189 @@ +#include "gdk101.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gdk101 { + +static const char *const TAG = "gdk101"; +static const uint8_t NUMBER_OF_READ_RETRIES = 5; + +void GDK101Component::update() { + uint8_t data[2]; + if (!this->read_dose_1m_(data)) { + this->status_set_warning("Failed to read dose 1m"); + return; + } + + if (!this->read_dose_10m_(data)) { + this->status_set_warning("Failed to read dose 10m"); + return; + } + + if (!this->read_status_(data)) { + this->status_set_warning("Failed to read status"); + return; + } + + if (!this->read_measurement_duration_(data)) { + this->status_set_warning("Failed to read measurement duration"); + return; + } + this->status_clear_warning(); +} + +void GDK101Component::setup() { + uint8_t data[2]; + ESP_LOGCONFIG(TAG, "Setting up GDK101..."); + // first, reset the sensor + if (!this->reset_sensor_(data)) { + this->status_set_error("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->mark_failed(); + return; + } + delay(10); + // read firmware version + if (!this->read_fw_version_(data)) { + this->status_set_error("Failed to read firmware version"); + this->mark_failed(); + return; + } +} + +void GDK101Component::dump_config() { + ESP_LOGCONFIG(TAG, "GDK101:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with GDK101 failed!"); + } +#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_); + LOG_SENSOR(" ", "Measurement Duration", this->measurement_duration_sensor_); +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_); +#endif // USE_BINARY_SENSOR +} + +float GDK101Component::get_setup_priority() const { return setup_priority::DATA; } + +bool GDK101Component::read_bytes_with_retry_(uint8_t a_register, uint8_t *data, uint8_t len) { + uint8_t retry = NUMBER_OF_READ_RETRIES; + bool status = false; + while (!status && retry) { + status = this->read_bytes(a_register, data, len); + retry--; + } + return status; +} + +bool GDK101Component::reset_sensor_(uint8_t *data) { + // It looks like reset is not so well designed in that sensor + // After sending reset command it looks that sensor start performing reset and is unresponsible during read + // after a while we can send another reset command and read "0x01" as confirmation + // Documentation not going in to such details unfortunately + if (!this->read_bytes_with_retry_(GDK101_REG_RESET, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + return true; +} + +bool GDK101Component::read_dose_1m_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->rad_1m_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_1MIN_AVG, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float dose = data[0] + (data[1] / 100.0f); + + this->rad_1m_sensor_->publish_state(dose); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_dose_10m_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->rad_10m_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_10MIN_AVG, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float dose = data[0] + (data[1] / 100.0f); + + this->rad_10m_sensor_->publish_state(dose); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_status_(uint8_t *data) { + if (!this->read_bytes(GDK101_REG_READ_STATUS, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + +#ifdef USE_SENSOR + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(data[0]); + } +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR + if (this->vibration_binary_sensor_ != nullptr) { + this->vibration_binary_sensor_->publish_state(data[1]); + } +#endif // USE_BINARY_SENSOR + + return true; +} + +bool GDK101Component::read_fw_version_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->fw_version_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); + + this->fw_version_sensor_->publish_state(fw_version); + } +#endif // USE_SENSOR + return true; +} + +bool GDK101Component::read_measurement_duration_(uint8_t *data) { +#ifdef USE_SENSOR + if (this->measurement_duration_sensor_ != nullptr) { + if (!this->read_bytes(GDK101_REG_READ_MEASURING_TIME, data, 2)) { + ESP_LOGE(TAG, "Updating GDK101 failed!"); + return false; + } + + const float meas_time = (data[0] * 60) + data[1]; + + this->measurement_duration_sensor_->publish_state(meas_time); + } +#endif // USE_SENSOR + return true; +} + +} // namespace gdk101 +} // namespace esphome diff --git a/esphome/components/gdk101/gdk101.h b/esphome/components/gdk101/gdk101.h new file mode 100644 index 000000000000..460e72ac89ef --- /dev/null +++ b/esphome/components/gdk101/gdk101.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif // USE_SENSOR +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif // USE_BINARY_SENSOR +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace gdk101 { + +static const uint8_t GDK101_REG_READ_FIRMWARE = 0xB4; // Firmware version +static const uint8_t GDK101_REG_RESET = 0xA0; // Reset register - reading its value triggers reset +static const uint8_t GDK101_REG_READ_STATUS = 0xB0; // Status register +static const uint8_t GDK101_REG_READ_MEASURING_TIME = 0xB1; // Mesuring time +static const uint8_t GDK101_REG_READ_10MIN_AVG = 0xB2; // Average radiation dose per 10 min +static const uint8_t GDK101_REG_READ_1MIN_AVG = 0xB3; // Average radiation dose per 1 min + +class GDK101Component : public PollingComponent, public i2c::I2CDevice { +#ifdef USE_SENSOR + 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 + + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + bool read_bytes_with_retry_(uint8_t a_register, uint8_t *data, uint8_t len); + bool reset_sensor_(uint8_t *data); + bool read_dose_1m_(uint8_t *data); + bool read_dose_10m_(uint8_t *data); + bool read_status_(uint8_t *data); + bool read_fw_version_(uint8_t *data); + bool read_measurement_duration_(uint8_t *data); +}; + +} // namespace gdk101 +} // namespace esphome diff --git a/esphome/components/gdk101/sensor.py b/esphome/components/gdk101/sensor.py new file mode 100644 index 000000000000..f7822646150d --- /dev/null +++ b/esphome/components/gdk101/sensor.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_DURATION, + DEVICE_CLASS_EMPTY, + ENTITY_CATEGORY_DIAGNOSTIC, + CONF_MEASUREMENT_DURATION, + CONF_STATUS, + CONF_VERSION, + ICON_RADIOACTIVE, + ICON_TIMER, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_MICROSILVERTS_PER_HOUR, + UNIT_SECOND, +) +from . import CONF_GDK101_ID, GDK101Component + +CONF_RADIATION_DOSE_PER_1M = "radiation_dose_per_1m" +CONF_RADIATION_DOSE_PER_10M = "radiation_dose_per_10m" + +DEPENDENCIES = ["gdk101"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component), + cv.Optional(CONF_RADIATION_DOSE_PER_1M): sensor.sensor_schema( + icon=ICON_RADIOACTIVE, + unit_of_measurement=UNIT_MICROSILVERTS_PER_HOUR, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADIATION_DOSE_PER_10M): sensor.sensor_schema( + icon=ICON_RADIOACTIVE, + unit_of_measurement=UNIT_MICROSILVERTS_PER_HOUR, + accuracy_decimals=2, + 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_STATUS): sensor.sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + accuracy_decimals=0, + ), + cv.Optional(CONF_MEASUREMENT_DURATION): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=DEVICE_CLASS_DURATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_GDK101_ID]) + + if radiation_dose_per_1m := config.get(CONF_RADIATION_DOSE_PER_1M): + sens = await sensor.new_sensor(radiation_dose_per_1m) + cg.add(hub.set_rad_1m_sensor(sens)) + + if radiation_dose_per_10m := config.get(CONF_RADIATION_DOSE_PER_10M): + 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)) + + if measurement_duration_config := config.get(CONF_MEASUREMENT_DURATION): + sens = await sensor.new_sensor(measurement_duration_config) + cg.add(hub.set_measurement_duration_sensor(sens)) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 97a7ba3d543f..8defa4ac24e2 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -15,8 +15,14 @@ globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) +RestoringGlobalStringComponent = globals_ns.class_( + "RestoringGlobalStringComponent", cg.Component +) GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) +CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" + + MULTI_CONF = True CONFIG_SCHEMA = cv.Schema( { @@ -24,6 +30,7 @@ cv.Required(CONF_TYPE): cv.string_strict, cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, + cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254), } ).extend(cv.COMPONENT_SCHEMA) @@ -32,12 +39,19 @@ @coroutine_with_priority(-100.0) async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) - template_args = cg.TemplateArguments(type_) restore = config[CONF_RESTORE_VALUE] - type = RestoringGlobalsComponent if restore else GlobalsComponent - res_type = type.template(template_args) + # Special casing the strings to their own class with a different save/restore mechanism + if str(type_) == "std::string" and restore: + template_args = cg.TemplateArguments( + type_, config.get(CONF_MAX_RESTORE_DATA_LENGTH, 63) + 1 + ) + type = RestoringGlobalStringComponent + else: + template_args = cg.TemplateArguments(type_) + type = RestoringGlobalsComponent if restore else GlobalsComponent + res_type = type.template(template_args) initial_value = None if CONF_INITIAL_VALUE in config: initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index 101adeb31132..78808436afcd 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -65,6 +65,64 @@ template class RestoringGlobalsComponent : public Component { ESPPreferenceObject rtc_; }; +// Use with string or subclasses of strings +template class RestoringGlobalStringComponent : public Component { + public: + using value_type = T; + explicit RestoringGlobalStringComponent() = default; + explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; } + explicit RestoringGlobalStringComponent( + std::array::type, std::extent::value> initial_value) { + memcpy(this->value_, initial_value.data(), sizeof(T)); + } + + T &value() { return this->value_; } + + void setup() override { + char temp[SZ]; + this->rtc_ = global_preferences->make_preference(1944399030U ^ this->name_hash_); + bool hasdata = this->rtc_.load(&temp); + if (hasdata) { + this->value_.assign(temp + 1, temp[0]); + } + this->prev_value_.assign(this->value_); + } + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + void loop() override { store_value_(); } + + void on_shutdown() override { store_value_(); } + + void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; } + + protected: + void store_value_() { + int diff = this->value_.compare(this->prev_value_); + if (diff != 0) { + // Make it into a length prefixed thing + unsigned char temp[SZ]; + + // If string is bigger than the allocation, do not save it. + // We don't need to waste ram setting prev_value either. + int size = this->value_.size(); + // Less than, not less than or equal, SZ includes the length byte. + if (size < SZ) { + memcpy(temp + 1, this->value_.c_str(), size); + // SZ should be pre checked at the schema level, it can't go past the char range. + temp[0] = ((unsigned char) size); + this->rtc_.save(&temp); + this->prev_value_.assign(this->value_); + } + } + } + + T value_{}; + T prev_value_{}; + uint32_t name_hash_{}; + ESPPreferenceObject rtc_; +}; + template class GlobalVarSetAction : public Action { public: explicit GlobalVarSetAction(C *parent) : parent_(parent) {} @@ -81,6 +139,7 @@ template class GlobalVarSetAction : public Action T &id(GlobalsComponent *value) { return value->value(); } template T &id(RestoringGlobalsComponent *value) { return value->value(); } +template T &id(RestoringGlobalStringComponent *value) { return value->value(); } } // namespace globals } // namespace esphome diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index 046f59ca1ad5..0b83b71fe49f 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -61,6 +61,7 @@ "BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW, } +CONF_CONTINUOUS = "continuous" GRAPH_TRACE_SCHEMA = cv.Schema( { @@ -70,6 +71,7 @@ cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_CONTINUOUS): cv.boolean, } ) @@ -186,6 +188,8 @@ async def to_code(config): if CONF_COLOR in trace: c = await cg.get_variable(trace[CONF_COLOR]) cg.add(tr.set_line_color(c)) + if CONF_CONTINUOUS in trace: + cg.add(tr.set_continuous(trace[CONF_CONTINUOUS])) cg.add(var.add_trace(tr)) # Add legend if CONF_LEGEND in config: diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 294e16dbb199..09f75577147f 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -164,18 +164,47 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo ESP_LOGV(TAG, "Updating graph. ymin %f, ymax %f", ymin, ymax); for (auto *trace : traces_) { Color c = trace->get_line_color(); - uint16_t thick = trace->get_line_thickness(); + int16_t thick = trace->get_line_thickness(); + bool continuous = trace->get_continuous(); + bool has_prev = false; + bool prev_b = false; + int16_t prev_y = 0; for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; if (!std::isnan(v) && (thick > 0)) { - int16_t x = this->width_ - 1 - i; - uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; - if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { - int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (uint16_t t = 0; t < thick; t++) { - buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); + int16_t x = this->width_ - 1 - i + x_offset; + uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick); + bool b = (trace->get_line_type() & bit) == bit; + if (b) { + int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; + auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) { + if (y >= y_offset && y < y_offset + this->height_) + buff->draw_pixel_at(x, y, c); + }; + if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { + for (int16_t t = 0; t < thick; t++) { + draw_pixel_at(x, y + t); + } + } else { + int16_t mid_y = (y + prev_y + thick) / 2; + if (y > prev_y) { + for (int16_t t = prev_y + thick; t <= mid_y; t++) + draw_pixel_at(x + 1, t); + for (int16_t t = mid_y + 1; t < y + thick; t++) + draw_pixel_at(x, t); + } else { + for (int16_t t = prev_y - 1; t >= mid_y; t--) + draw_pixel_at(x + 1, t); + for (int16_t t = mid_y - 1; t >= y; t--) + draw_pixel_at(x, t); + } } + prev_y = y; } + prev_b = b; + has_prev = true; + } else { + has_prev = false; } } } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 339a6f6d94e0..34accb7d3a79 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -116,6 +116,8 @@ class GraphTrace { void set_line_type(enum LineType val) { this->line_type_ = val; } Color get_line_color() { return this->line_color_; } void set_line_color(Color val) { this->line_color_ = val; } + bool get_continuous() { return this->continuous_; } + void set_continuous(bool continuous) { this->continuous_ = continuous; } std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } @@ -125,6 +127,7 @@ class GraphTrace { uint8_t line_thickness_{3}; enum LineType line_type_ { LINE_TYPE_SOLID }; Color line_color_{COLOR_ON}; + bool continuous_{false}; HistoryData data_; friend Graph; diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py new file mode 100644 index 000000000000..1b3ed7f8cdfe --- /dev/null +++ b/esphome/components/graphical_display_menu/__init__.py @@ -0,0 +1,95 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display, font, color +from esphome.const import CONF_DISPLAY, CONF_ID, CONF_TRIGGER_ID +from esphome import automation, core + +from esphome.components.display_menu_base import ( + DISPLAY_MENU_BASE_SCHEMA, + DisplayMenuComponent, + display_menu_to_code, +) + +CONF_FONT = "font" +CONF_MENU_ITEM_VALUE = "menu_item_value" +CONF_FOREGROUND_COLOR = "foreground_color" +CONF_BACKGROUND_COLOR = "background_color" +CONF_ON_REDRAW = "on_redraw" + +graphical_display_menu_ns = cg.esphome_ns.namespace("graphical_display_menu") +GraphicalDisplayMenu = graphical_display_menu_ns.class_( + "GraphicalDisplayMenu", DisplayMenuComponent +) +GraphicalDisplayMenuConstPtr = GraphicalDisplayMenu.operator("ptr").operator("const") +MenuItemValueArguments = graphical_display_menu_ns.struct("MenuItemValueArguments") +MenuItemValueArgumentsConstPtr = MenuItemValueArguments.operator("ptr").operator( + "const" +) +GraphicalDisplayMenuOnRedrawTrigger = graphical_display_menu_ns.class_( + "GraphicalDisplayMenuOnRedrawTrigger", automation.Trigger +) + +CODEOWNERS = ["@MrMDavidson"] + +AUTO_LOAD = ["display_menu_base"] + +CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GraphicalDisplayMenu), + cv.Optional(CONF_DISPLAY): cv.use_id(display.Display), + cv.Required(CONF_FONT): cv.use_id(font.Font), + cv.Optional(CONF_MENU_ITEM_VALUE): cv.templatable(cv.string), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_ON_REDRAW): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + GraphicalDisplayMenuOnRedrawTrigger + ) + } + ), + } + ) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if display_config := config.get(CONF_DISPLAY): + drawing_display = await cg.get_variable(display_config) + cg.add(var.set_display(drawing_display)) + + menu_font = await cg.get_variable(config[CONF_FONT]) + cg.add(var.set_font(menu_font)) + + if (menu_item_value_config := config.get(CONF_MENU_ITEM_VALUE, None)) is not None: + if isinstance(menu_item_value_config, core.Lambda): + template_ = await cg.templatable( + menu_item_value_config, + [(MenuItemValueArgumentsConstPtr, "it")], + cg.std_string, + ) + cg.add(var.set_menu_item_value(template_)) + else: + cg.add(var.set_menu_item_value(menu_item_value_config)) + + if foreground_color_config := config.get(CONF_FOREGROUND_COLOR): + foreground_color = await cg.get_variable(foreground_color_config) + cg.add(var.set_foreground_color(foreground_color)) + + if background_color_config := config.get(CONF_BACKGROUND_COLOR): + background_color = await cg.get_variable(background_color_config) + cg.add(var.set_background_color(background_color)) + + for conf in config.get(CONF_ON_REDRAW, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(GraphicalDisplayMenuConstPtr, "it")], conf + ) + + await display_menu_to_code(var, config) + + cg.add_define("USE_GRAPHICAL_DISPLAY_MENU") diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.cpp b/esphome/components/graphical_display_menu/graphical_display_menu.cpp new file mode 100644 index 000000000000..4a4e5190090a --- /dev/null +++ b/esphome/components/graphical_display_menu/graphical_display_menu.cpp @@ -0,0 +1,245 @@ +#include "graphical_display_menu.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include +#include "esphome/components/display/display.h" + +namespace esphome { +namespace graphical_display_menu { + +static const char *const TAG = "graphical_display_menu"; + +void GraphicalDisplayMenu::setup() { + if (this->display_ != nullptr) { + display::display_writer_t writer = [this](display::Display &it) { this->draw_menu(); }; + this->display_page_ = make_unique(writer); + } + + if (!this->menu_item_value_.has_value()) { + this->menu_item_value_ = [](const MenuItemValueArguments *it) { + std::string label = " "; + if (it->is_item_selected && it->is_menu_editing) { + label.append(">"); + label.append(it->item->get_value_text()); + label.append("<"); + } else { + label.append("("); + label.append(it->item->get_value_text()); + label.append(")"); + } + return label; + }; + } + + display_menu_base::DisplayMenuComponent::setup(); +} + +void GraphicalDisplayMenu::dump_config() { + ESP_LOGCONFIG(TAG, "Graphical Display Menu"); + ESP_LOGCONFIG(TAG, "Has Display: %s", YESNO(this->display_ != nullptr)); + ESP_LOGCONFIG(TAG, "Popup Mode: %s", YESNO(this->display_ != nullptr)); + ESP_LOGCONFIG(TAG, "Advanced Drawing Mode: %s", YESNO(this->display_ == nullptr)); + ESP_LOGCONFIG(TAG, "Has Font: %s", YESNO(this->font_ != nullptr)); + ESP_LOGCONFIG(TAG, "Mode: %s", this->mode_ == display_menu_base::MENU_MODE_ROTARY ? "Rotary" : "Joystick"); + ESP_LOGCONFIG(TAG, "Active: %s", YESNO(this->active_)); + ESP_LOGCONFIG(TAG, "Menu items:"); + for (size_t i = 0; i < this->displayed_item_->items_size(); i++) { + auto *item = this->displayed_item_->get_item(i); + ESP_LOGCONFIG(TAG, " %i: %s (Type: %s, Immediate Edit: %s)", i, item->get_text().c_str(), + LOG_STR_ARG(display_menu_base::menu_item_type_to_string(item->get_type())), + YESNO(item->get_immediate_edit())); + } +} + +void GraphicalDisplayMenu::set_display(display::Display *display) { this->display_ = display; } + +void GraphicalDisplayMenu::set_font(display::BaseFont *font) { this->font_ = font; } + +void GraphicalDisplayMenu::set_foreground_color(Color foreground_color) { this->foreground_color_ = foreground_color; } +void GraphicalDisplayMenu::set_background_color(Color background_color) { this->background_color_ = background_color; } + +void GraphicalDisplayMenu::on_before_show() { + if (this->display_ != nullptr) { + this->previous_display_page_ = this->display_->get_active_page(); + this->display_->show_page(this->display_page_.get()); + this->display_->clear(); + } else { + this->update(); + } +} + +void GraphicalDisplayMenu::on_before_hide() { + if (this->previous_display_page_ != nullptr) { + this->display_->show_page((display::DisplayPage *) this->previous_display_page_); + this->display_->clear(); + this->update(); + this->previous_display_page_ = nullptr; + } else { + this->update(); + } +} + +void GraphicalDisplayMenu::draw_and_update() { + this->update(); + + // If we're in advanced drawing mode we won't have a display and will instead require the update callback to do + // our drawing + if (this->display_ != nullptr) { + draw_menu(); + } +} + +void GraphicalDisplayMenu::draw_menu() { + if (this->display_ == nullptr) { + ESP_LOGE(TAG, "draw_menu() called without a display_. This is only available when using the menu in pop up mode"); + return; + } + display::Rect bounds(0, 0, this->display_->get_width(), this->display_->get_height()); + this->draw_menu_internal_(this->display_, &bounds); +} + +void GraphicalDisplayMenu::draw(display::Display *display, const display::Rect *bounds) { + this->draw_menu_internal_(display, bounds); +} + +void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const display::Rect *bounds) { + int16_t total_height = 0; + int16_t max_width = 0; + int y_padding = 2; + bool scroll_menu_items = false; + std::vector menu_dimensions; + int number_items_fit_to_screen = 0; + const int max_item_index = this->displayed_item_->items_size() - 1; + + for (size_t i = 0; i <= max_item_index; i++) { + const auto *item = this->displayed_item_->get_item(i); + const bool selected = i == this->cursor_index_; + const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected); + + menu_dimensions.push_back(item_dimensions); + total_height += item_dimensions.h + (i == 0 ? 0 : y_padding); + max_width = std::max(max_width, item_dimensions.w); + + if (total_height <= bounds->h) { + number_items_fit_to_screen++; + } else { + // Scroll the display if the selected item or the item immediately after it overflows + if ((selected) || (i == this->cursor_index_ + 1)) { + scroll_menu_items = true; + } + } + } + + // Determine what items to draw + int first_item_index = 0; + int last_item_index = max_item_index; + + if (number_items_fit_to_screen <= 1) { + // If only one item can fit to the bounds draw the current cursor item + last_item_index = std::min(last_item_index, this->cursor_index_ + 1); + first_item_index = this->cursor_index_; + } else { + if (scroll_menu_items) { + // Attempt to draw the item after the current item (+1 for equality check in the draw loop) + last_item_index = std::min(last_item_index, this->cursor_index_ + 1); + + // Go back through the measurements to determine how many prior items we can fit + int height_left_to_use = bounds->h; + for (int i = last_item_index; i >= 0; i--) { + const display::Rect item_dimensions = menu_dimensions[i]; + height_left_to_use -= (item_dimensions.h + y_padding); + + if (height_left_to_use <= 0) { + // Ran out of space - this is our first item to draw + first_item_index = i; + break; + } + } + const int items_to_draw = last_item_index - first_item_index; + // Dont't draw last item partially if it is the selected item + if ((this->cursor_index_ == last_item_index) && (number_items_fit_to_screen <= items_to_draw) && + (first_item_index < max_item_index)) { + first_item_index++; + } + } + } + + // Render the items into the view port + display->start_clipping(*bounds); + + display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_); + auto y_offset = bounds->y; + for (size_t i = first_item_index; i <= last_item_index; i++) { + const auto *item = this->displayed_item_->get_item(i); + const bool selected = i == this->cursor_index_; + display::Rect dimensions = menu_dimensions[i]; + + dimensions.y = y_offset; + dimensions.x = bounds->x; + this->draw_item(display, item, &dimensions, selected); + + y_offset += dimensions.h + y_padding; + } + + display->end_clipping(); +} + +display::Rect GraphicalDisplayMenu::measure_item(display::Display *display, const display_menu_base::MenuItem *item, + const display::Rect *bounds, const bool selected) { + display::Rect dimensions(0, 0, 0, 0); + + if (selected) { + // TODO: Support selection glyph + dimensions.w += 0; + dimensions.h += 0; + } + + std::string label = item->get_text(); + if (item->has_value()) { + // Append to label + MenuItemValueArguments args(item, selected, this->editing_); + label.append(this->menu_item_value_.value(&args)); + } + + int x1; + int y1; + int width; + int height; + display->get_text_bounds(0, 0, label.c_str(), this->font_, display::TextAlign::TOP_LEFT, &x1, &y1, &width, &height); + + dimensions.w = std::min((int16_t) width, bounds->w); + dimensions.h = std::min((int16_t) height, bounds->h); + + return dimensions; +} + +inline void GraphicalDisplayMenu::draw_item(display::Display *display, const display_menu_base::MenuItem *item, + const display::Rect *bounds, const bool selected) { + const auto background_color = selected ? this->foreground_color_ : this->background_color_; + const auto foreground_color = selected ? this->background_color_ : this->foreground_color_; + + // int background_width = std::max(bounds->width, available_width); + int background_width = bounds->w; + + display->filled_rectangle(bounds->x, bounds->y, background_width, bounds->h, background_color); + + std::string label = item->get_text(); + if (item->has_value()) { + MenuItemValueArguments args(item, selected, this->editing_); + label.append(this->menu_item_value_.value(&args)); + } + + display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str(), + background_color); +} + +void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) { + ESP_LOGE(TAG, "draw_item(MenuItem *item, uint8_t row, bool selected) called. The graphical_display_menu specific " + "draw_item should be called."); +} + +void GraphicalDisplayMenu::update() { this->on_redraw_callbacks_.call(); } + +} // namespace graphical_display_menu +} // namespace esphome diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.h b/esphome/components/graphical_display_menu/graphical_display_menu.h new file mode 100644 index 000000000000..96f2bd79fd53 --- /dev/null +++ b/esphome/components/graphical_display_menu/graphical_display_menu.h @@ -0,0 +1,84 @@ +#pragma once + +#include "esphome/core/color.h" +#include "esphome/components/display_menu_base/display_menu_base.h" +#include "esphome/components/display_menu_base/menu_item.h" +#include "esphome/core/automation.h" +#include + +namespace esphome { + +// forward declare from display namespace +namespace display { +class Display; +class DisplayPage; +class BaseFont; +class Rect; +} // namespace display + +namespace graphical_display_menu { + +const Color COLOR_ON(255, 255, 255, 255); +const Color COLOR_OFF(0, 0, 0, 0); + +struct MenuItemValueArguments { + MenuItemValueArguments(const display_menu_base::MenuItem *item, bool is_item_selected, bool is_menu_editing) { + this->item = item; + this->is_item_selected = is_item_selected; + this->is_menu_editing = is_menu_editing; + } + + const display_menu_base::MenuItem *item; + bool is_item_selected; + bool is_menu_editing; +}; + +class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent { + public: + void setup() override; + void dump_config() override; + + void set_display(display::Display *display); + void set_font(display::BaseFont *font); + template void set_menu_item_value(V menu_item_value) { this->menu_item_value_ = menu_item_value; } + void set_foreground_color(Color foreground_color); + void set_background_color(Color background_color); + + void add_on_redraw_callback(std::function &&cb) { this->on_redraw_callbacks_.add(std::move(cb)); } + + void draw(display::Display *display, const display::Rect *bounds); + + protected: + void draw_and_update() override; + void draw_menu() override; + void draw_menu_internal_(display::Display *display, const display::Rect *bounds); + void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override; + virtual display::Rect measure_item(display::Display *display, const display_menu_base::MenuItem *item, + const display::Rect *bounds, bool selected); + virtual void draw_item(display::Display *display, const display_menu_base::MenuItem *item, + const display::Rect *bounds, bool selected); + void update() override; + + void on_before_show() override; + void on_before_hide() override; + + std::unique_ptr display_page_{nullptr}; + const display::DisplayPage *previous_display_page_{nullptr}; + display::Display *display_{nullptr}; + display::BaseFont *font_{nullptr}; + TemplatableValue menu_item_value_; + Color foreground_color_{COLOR_ON}; + Color background_color_{COLOR_OFF}; + + CallbackManager on_redraw_callbacks_{}; +}; + +class GraphicalDisplayMenuOnRedrawTrigger : public Trigger { + public: + explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) { + parent->add_on_redraw_callback([this, parent]() { this->trigger(parent); }); + } +}; + +} // namespace graphical_display_menu +} // namespace esphome diff --git a/esphome/components/gree/__init__.py b/esphome/components/gree/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py new file mode 100644 index 000000000000..02ce7b12d486 --- /dev/null +++ b/esphome/components/gree/climate.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID, CONF_MODEL + +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") +MODELS = { + "generic": Model.GREE_GENERIC, + "yan": Model.GREE_YAN, + "yaa": Model.GREE_YAA, + "yac": Model.GREE_YAC, +} + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GreeClimate), + cv.Required(CONF_MODEL): cv.enum(MODELS), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_model(config[CONF_MODEL])) + + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/gree/gree.cpp b/esphome/components/gree/gree.cpp new file mode 100644 index 000000000000..1bbb443fce27 --- /dev/null +++ b/esphome/components/gree/gree.cpp @@ -0,0 +1,157 @@ +#include "gree.h" +#include "esphome/components/remote_base/remote_base.h" + +namespace esphome { +namespace gree { + +static const char *const TAG = "gree.climate"; + +void GreeClimate::set_model(Model model) { this->model_ = model; } + +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) { + remote_state[2] = 0x60; + remote_state[3] = 0x50; + remote_state[4] = this->vertical_swing_(); + } + + if (this->model_ == GREE_YAC) { + remote_state[4] |= (this->horizontal_swing_() << 4); + } + + if (this->model_ == GREE_YAA || this->model_ == GREE_YAC) { + 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 + + if (this->vertical_swing_() == GREE_VDIR_SWING) { + remote_state[0] |= (1 << 6); // Enable swing by setting bit 6 + } else if (this->vertical_swing_() != GREE_VDIR_AUTO) { + remote_state[5] = this->vertical_swing_(); + } + } + + // Calculate the checksum + if (this->model_ == GREE_YAN) { + remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0); + } else { + remote_state[7] = + ((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) + + ((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + ((remote_state[7] & 0xF0) >> 4) + 0x0A) & + 0x0F) + << 4) | + (remote_state[7] & 0x0F); + } + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(GREE_IR_FREQUENCY); + + data->mark(GREE_HEADER_MARK); + data->space(GREE_HEADER_SPACE); + + for (int i = 0; i < 4; i++) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(GREE_BIT_MARK); + bool bit = remote_state[i] & mask; + data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE); + } + } + + data->mark(GREE_BIT_MARK); + data->space(GREE_ZERO_SPACE); + data->mark(GREE_BIT_MARK); + data->space(GREE_ONE_SPACE); + data->mark(GREE_BIT_MARK); + data->space(GREE_ZERO_SPACE); + + data->mark(GREE_BIT_MARK); + data->space(GREE_MESSAGE_SPACE); + + for (int i = 4; i < 8; i++) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(GREE_BIT_MARK); + bool bit = remote_state[i] & mask; + data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE); + } + } + + data->mark(GREE_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +uint8_t GreeClimate::operation_mode_() { + uint8_t operating_mode = GREE_MODE_ON; + + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + operating_mode |= GREE_MODE_COOL; + break; + case climate::CLIMATE_MODE_DRY: + operating_mode |= GREE_MODE_DRY; + break; + case climate::CLIMATE_MODE_HEAT: + operating_mode |= GREE_MODE_HEAT; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + operating_mode |= GREE_MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + operating_mode |= GREE_MODE_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + operating_mode = GREE_MODE_OFF; + break; + } + + return operating_mode; +} + +uint8_t GreeClimate::fan_speed_() { + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + return GREE_FAN_1; + case climate::CLIMATE_FAN_MEDIUM: + return GREE_FAN_2; + case climate::CLIMATE_FAN_HIGH: + return GREE_FAN_3; + case climate::CLIMATE_FAN_AUTO: + default: + return GREE_FAN_AUTO; + } +} + +uint8_t GreeClimate::horizontal_swing_() { + switch (this->swing_mode) { + case climate::CLIMATE_SWING_HORIZONTAL: + case climate::CLIMATE_SWING_BOTH: + return GREE_HDIR_SWING; + default: + return GREE_HDIR_MANUAL; + } +} + +uint8_t GreeClimate::vertical_swing_() { + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + case climate::CLIMATE_SWING_BOTH: + return GREE_VDIR_SWING; + default: + return GREE_VDIR_MANUAL; + } +} + +uint8_t GreeClimate::temperature_() { + return (uint8_t) roundf(clamp(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)); +} + +} // namespace gree +} // namespace esphome diff --git a/esphome/components/gree/gree.h b/esphome/components/gree/gree.h new file mode 100644 index 000000000000..e7131a2b896e --- /dev/null +++ b/esphome/components/gree/gree.h @@ -0,0 +1,97 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace gree { + +// Values for GREE IR Controllers +// Temperature +const uint8_t GREE_TEMP_MIN = 16; // Celsius +const 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; + +const uint8_t GREE_MODE_OFF = 0x00; +const 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; +const uint8_t GREE_FAN_TURBO = 0x80; + +// 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; + +// 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; + +// State Frame size +const 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; + +// Only available on YAC +// 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; + +// Model codes +enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC }; + +class GreeClimate : public climate_ir::ClimateIR { + public: + GreeClimate() + : climate_ir::ClimateIR(GREE_TEMP_MIN, GREE_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + + void set_model(Model model); + + protected: + // Transmit via IR the state of this climate controller. + void transmit_state() override; + + uint8_t operation_mode_(); + uint8_t fan_speed_(); + uint8_t horizontal_swing_(); + uint8_t vertical_swing_(); + uint8_t temperature_(); + + Model model_{}; +}; + +} // namespace gree +} // namespace esphome diff --git a/esphome/components/grove_tb6612fng/__init__.py b/esphome/components/grove_tb6612fng/__init__.py index 75610ce9d373..7db0198a89e0 100644 --- a/esphome/components/grove_tb6612fng/__init__.py +++ b/esphome/components/grove_tb6612fng/__init__.py @@ -8,12 +8,15 @@ CONF_CHANNEL, CONF_SPEED, CONF_DIRECTION, + CONF_ADDRESS, ) DEPENDENCIES = ["i2c"] CODEOWNERS = ["@max246"] +MULTI_CONF = True + grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng") GROVE_TB6612FNG = grove_tb6612fng_ns.class_( "GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice @@ -33,6 +36,9 @@ GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorNoStandbyAction", automation.Action ) +GROVETB6612FNGMotorChangeAddressAction = grove_tb6612fng_ns.class_( + "GROVETB6612FNGMotorChangeAddressAction", automation.Action +) DIRECTION_TYPE = { "FORWARD": 1, @@ -150,3 +156,22 @@ async def grove_tb6612fng_no_standby_to_code(config, action_id, template_arg, ar await cg.register_parented(var, config[CONF_ID]) return var + + +@automation.register_action( + "grove_tb6612fng.change_address", + GROVETB6612FNGMotorChangeAddressAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + cv.Required(CONF_ADDRESS): cv.i2c_address, + } + ), +) +async def grove_tb6612fng_change_address_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_channel = await cg.templatable(config[CONF_ADDRESS], args, int) + cg.add(var.set_address(template_channel)) + return var diff --git a/esphome/components/grove_tb6612fng/grove_tb6612fng.h b/esphome/components/grove_tb6612fng/grove_tb6612fng.h index ccdab6472ae2..2743ef4ed7a0 100644 --- a/esphome/components/grove_tb6612fng/grove_tb6612fng.h +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.h @@ -84,8 +84,7 @@ class GroveMotorDriveTB6612FNG : public Component, public i2c::I2CDevice { *************************************************************/ void set_i2c_addr(uint8_t addr); - /************************************************************* - Description + /***********************************change_address Drive a motor. Parameter chl: MOTOR_CHA or MOTOR_CHB @@ -204,5 +203,13 @@ class GROVETB6612FNGMotorNoStandbyAction : public Action, public Parented void play(Ts... x) override { this->parent_->not_standby(); } }; +template +class GROVETB6612FNGMotorChangeAddressAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, address) + + void play(Ts... x) override { this->parent_->set_i2c_addr(this->address_.value(x...)); } +}; + } // namespace grove_tb6612fng } // namespace esphome diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py index f95d679c3ea5..0db15ae53e56 100644 --- a/esphome/components/growatt_solar/sensor.py +++ b/esphome/components/growatt_solar/sensor.py @@ -6,6 +6,9 @@ CONF_CURRENT, CONF_FREQUENCY, CONF_ID, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, @@ -21,10 +24,6 @@ UNIT_WATT, ) -CONF_PHASE_A = "phase_a" -CONF_PHASE_B = "phase_b" -CONF_PHASE_C = "phase_c" - CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_GENERATION_TIME = "total_generation_time" diff --git a/esphome/components/gt911/__init__.py b/esphome/components/gt911/__init__.py new file mode 100644 index 000000000000..1f7ecd1d5e87 --- /dev/null +++ b/esphome/components/gt911/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@jesserockz", "@clydebarrow"] +DEPENDENCIES = ["i2c"] + +gt911_ns = cg.esphome_ns.namespace("gt911") diff --git a/esphome/components/gt911/binary_sensor/__init__.py b/esphome/components/gt911/binary_sensor/__init__.py new file mode 100644 index 000000000000..18f5c49dbd51 --- /dev/null +++ b/esphome/components/gt911/binary_sensor/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_INDEX + +from .. import gt911_ns +from ..touchscreen import GT911Touchscreen, GT911ButtonListener + +CONF_GT911_ID = "gt911_id" + +GT911Button = gt911_ns.class_( + "GT911Button", + binary_sensor.BinarySensor, + cg.Component, + GT911ButtonListener, + cg.Parented.template(GT911Touchscreen), +) + +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(GT911Button).extend( + { + cv.GenerateID(CONF_GT911_ID): cv.use_id(GT911Touchscreen), + cv.Optional(CONF_INDEX, default=0): cv.int_range(min=0, max=3), + } +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_GT911_ID]) + cg.add(var.set_index(config[CONF_INDEX])) diff --git a/esphome/components/gt911/binary_sensor/gt911_button.cpp b/esphome/components/gt911/binary_sensor/gt911_button.cpp new file mode 100644 index 000000000000..35ffaecefcc4 --- /dev/null +++ b/esphome/components/gt911/binary_sensor/gt911_button.cpp @@ -0,0 +1,27 @@ +#include "gt911_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gt911 { + +static const char *const TAG = "GT911.binary_sensor"; + +void GT911Button::setup() { + this->parent_->register_button_listener(this); + this->publish_initial_state(false); +} + +void GT911Button::dump_config() { + LOG_BINARY_SENSOR("", "GT911 Button", this); + ESP_LOGCONFIG(TAG, " Index: %u", this->index_); +} + +void GT911Button::update_button(uint8_t index, bool state) { + if (index != this->index_) + return; + + this->publish_state(state); +} + +} // namespace gt911 +} // namespace esphome diff --git a/esphome/components/gt911/binary_sensor/gt911_button.h b/esphome/components/gt911/binary_sensor/gt911_button.h new file mode 100644 index 000000000000..556ed65f919f --- /dev/null +++ b/esphome/components/gt911/binary_sensor/gt911_button.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/gt911/touchscreen/gt911_touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace gt911 { + +class GT911Button : public binary_sensor::BinarySensor, + public Component, + public GT911ButtonListener, + public Parented { + public: + void setup() override; + void dump_config() override; + + void set_index(uint8_t index) { this->index_ = index; } + + void update_button(uint8_t index, bool state) override; + + protected: + uint8_t index_; +}; + +} // namespace gt911 +} // namespace esphome diff --git a/esphome/components/gt911/touchscreen/__init__.py b/esphome/components/gt911/touchscreen/__init__.py new file mode 100644 index 000000000000..9a0d5cc1696b --- /dev/null +++ b/esphome/components/gt911/touchscreen/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_INTERRUPT_PIN, CONF_ID +from .. import gt911_ns + + +GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") +GT911Touchscreen = gt911_ns.class_( + "GT911Touchscreen", + touchscreen.Touchscreen, + i2c.I2CDevice, +) + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GT911Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + } +).extend(i2c.i2c_device_schema(0x5D)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp new file mode 100644 index 000000000000..99dba66c2227 --- /dev/null +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -0,0 +1,116 @@ +#include "gt911_touchscreen.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gt911 { + +static const char *const TAG = "gt911.touchscreen"; + +static const uint8_t GET_TOUCH_STATE[2] = {0x81, 0x4E}; +static const uint8_t CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00}; +static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F}; +static const uint8_t GET_SWITCHES[2] = {0x80, 0x4D}; +static const uint8_t GET_MAX_VALUES[2] = {0x80, 0x48}; +static const size_t MAX_TOUCHES = 5; // max number of possible touches reported +static const size_t MAX_BUTTONS = 4; // max number of buttons scanned + +#define ERROR_CHECK(err) \ + if ((err) != i2c::ERROR_OK) { \ + ESP_LOGE(TAG, "Failed to communicate!"); \ + this->status_set_warning(); \ + return; \ + } + +void GT911Touchscreen::setup() { + i2c::ErrorCode err; + ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); + + // check the configuration of the int line. + uint8_t data[4]; + err = this->write(GET_SWITCHES, 2); + if (err == i2c::ERROR_OK) { + err = this->read(data, 1); + if (err == i2c::ERROR_OK) { + ESP_LOGD(TAG, "Read from switches: 0x%02X", data[0]); + if (this->interrupt_pin_ != nullptr) { + // datasheet says NOT to use pullup/down on the int line. + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, + (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); + } + } + } + if (err == i2c::ERROR_OK) { + err = this->write(GET_MAX_VALUES, 2); + if (err == i2c::ERROR_OK) { + err = this->read(data, sizeof(data)); + if (err == i2c::ERROR_OK) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = encode_uint16(data[1], data[0]); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = encode_uint16(data[3], data[2]); + } + esph_log_d(TAG, "calibration max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_); + } + } + } + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to communicate!"); + this->mark_failed(); + return; + } + + ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete"); +} + +void GT911Touchscreen::update_touches() { + i2c::ErrorCode err; + uint8_t touch_state = 0; + uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte + + err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false); + ERROR_CHECK(err); + err = this->read(&touch_state, 1); + ERROR_CHECK(err); + this->write(CLEAR_TOUCH_STATE, sizeof(CLEAR_TOUCH_STATE)); + uint8_t num_of_touches = touch_state & 0x07; + + if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) { + this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet. + return; + } + + err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false); + ERROR_CHECK(err); + // num_of_touches is guaranteed to be 0..5. Also read the key data + err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); + ERROR_CHECK(err); + + for (uint8_t i = 0; i != num_of_touches; i++) { + uint16_t id = data[i][0]; + uint16_t x = encode_uint16(data[i][2], data[i][1]); + uint16_t y = encode_uint16(data[i][4], data[i][3]); + this->add_raw_touch_position_(id, x, y); + } + auto keys = data[num_of_touches][0] & ((1 << MAX_BUTTONS) - 1); + if (keys != this->button_state_) { + this->button_state_ = keys; + for (size_t i = 0; i != MAX_BUTTONS; i++) { + for (auto *listener : this->button_listeners_) + listener->update_button(i, (keys & (1 << i)) != 0); + } + } +} + +void GT911Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "GT911 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} + +} // namespace gt911 +} // namespace esphome diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.h b/esphome/components/gt911/touchscreen/gt911_touchscreen.h new file mode 100644 index 000000000000..a9e1279ed33b --- /dev/null +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace gt911 { + +class GT911ButtonListener { + public: + virtual void update_button(uint8_t index, bool state) = 0; +}; + +class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } + + protected: + void update_touches() override; + + InternalGPIOPin *interrupt_pin_{}; + std::vector button_listeners_; + uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update. +}; + +} // namespace gt911 +} // namespace esphome diff --git a/esphome/components/haier/binary_sensor/__init__.py b/esphome/components/haier/binary_sensor/__init__.py new file mode 100644 index 000000000000..4f72560a7b1f --- /dev/null +++ b/esphome/components/haier/binary_sensor/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_FAN, + ICON_RADIATOR, +) +from ..climate import ( + CONF_HAIER_ID, + HonClimate, +) + +BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True) + +# Haier sensors +CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status" +CONF_DEFROST_STATUS = "defrost_status" +CONF_COMPRESSOR_STATUS = "compressor_status" +CONF_INDOOR_FAN_STATUS = "indoor_fan_status" +CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status" +CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status" + +# Additional icons +ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" +ICON_HVAC = "mdi:hvac" +ICON_VALVE = "mdi:valve" + +SENSOR_TYPES = { + CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_FAN, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_SNOWFLAKE_THERMOMETER, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_HVAC, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_FAN, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_VALVE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema( + icon=ICON_RADIATOR, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), + } +).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_HAIER_ID]) + + for type, _ in SENSOR_TYPES.items(): + if conf := config.get(type): + sens = await binary_sensor.new_binary_sensor(conf) + binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper()) + cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens)) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index acb079c822dd..b16244fd90f6 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -2,28 +2,27 @@ import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv -from esphome.components import uart, sensor, climate, logger +from esphome.components import uart, climate, logger from esphome import automation from esphome.const import ( CONF_BEEPER, + CONF_DISPLAY, CONF_ID, CONF_LEVEL, CONF_LOGGER, CONF_LOGS, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, + CONF_OUTDOOR_TEMPERATURE, CONF_PROTOCOL, CONF_SUPPORTED_MODES, CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_SWING_MODES, CONF_TARGET_TEMPERATURE, CONF_TEMPERATURE_STEP, + CONF_TRIGGER_ID, CONF_VISUAL, CONF_WIFI, - DEVICE_CLASS_TEMPERATURE, - ICON_THERMOMETER, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, ) from esphome.components.climate import ( ClimateMode, @@ -38,20 +37,22 @@ PROTOCOL_MAX_TEMPERATURE = 30.0 PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 +PROTOCOL_CONTROL_PACKET_SIZE = 10 CODEOWNERS = ["@paveldn"] -AUTO_LOAD = ["sensor"] DEPENDENCIES = ["climate", "uart"] -CONF_WIFI_SIGNAL = "wifi_signal" +CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control" CONF_ANSWER_TIMEOUT = "answer_timeout" -CONF_DISPLAY = "display" -CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" -CONF_VERTICAL_AIRFLOW = "vertical_airflow" +CONF_CONTROL_METHOD = "control_method" +CONF_CONTROL_PACKET_SIZE = "control_packet_size" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" +CONF_ON_ALARM_START = "on_alarm_start" +CONF_ON_ALARM_END = "on_alarm_end" +CONF_VERTICAL_AIRFLOW = "vertical_airflow" +CONF_WIFI_SIGNAL = "wifi_signal" PROTOCOL_HON = "HON" PROTOCOL_SMARTAIR2 = "SMARTAIR2" -PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] haier_ns = cg.esphome_ns.namespace("haier") HaierClimateBase = haier_ns.class_( @@ -60,6 +61,7 @@ HonClimate = haier_ns.class_("HonClimate", HaierClimateBase) Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase) +CONF_HAIER_ID = "haier_id" AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True) AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { @@ -81,8 +83,8 @@ } SUPPORTED_SWING_MODES_OPTIONS = { - "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, # always available - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, # always available + "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, } @@ -97,16 +99,35 @@ } SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { + "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, } SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { - "ECO": ClimatePreset.CLIMATE_PRESET_ECO, + "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "ECO": ClimatePreset.CLIMATE_PRESET_ECO, "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, } +HonControlMethod = haier_ns.enum("HonControlMethod", True) +SUPPORTED_HON_CONTROL_METHODS = { + "MONITOR_ONLY": HonControlMethod.MONITOR_ONLY, + "SET_GROUP_PARAMETERS": HonControlMethod.SET_GROUP_PARAMETERS, + "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, +} + +HaierAlarmStartTrigger = haier_ns.class_( + "HaierAlarmStartTrigger", + automation.Trigger.template(cg.uint8, cg.const_char_ptr), +) + +HaierAlarmEndTrigger = haier_ns.class_( + "HaierAlarmEndTrigger", + automation.Trigger.template(cg.uint8, cg.const_char_ptr), +) + def validate_visual(config): if CONF_VISUAL in config: @@ -136,12 +157,10 @@ def validate_visual(config): f"Configured visual temperature step {temp_step} is wrong, it should be a multiple of 0.5" ) else: - config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = ( - { - CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, - CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, - }, - ) + config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = { + CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, + CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, + } else: config[CONF_VISUAL] = { CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE, @@ -186,11 +205,12 @@ def validate_visual(config): PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Smartair2Climate), + cv.Optional( + CONF_ALTERNATIVE_SWING_CONTROL, default=False + ): cv.boolean, cv.Optional( CONF_SUPPORTED_PRESETS, - default=list( - SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys() - ), + default=list(["BOOST", "COMFORT"]), # No AWAY by default ): cv.ensure_list( cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) ), @@ -199,19 +219,37 @@ def validate_visual(config): PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(HonClimate), + cv.Optional( + CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS" + ): cv.ensure_list( + cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) + ), cv.Optional(CONF_BEEPER, default=True): cv.boolean, + cv.Optional( + CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE + ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), cv.Optional( CONF_SUPPORTED_PRESETS, - default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), + default=list(["BOOST", "ECO", "SLEEP"]), # No AWAY by default ): cv.ensure_list( cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) ), - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, + cv.Optional(CONF_OUTDOOR_TEMPERATURE): cv.invalid( + f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead" + ), + cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + HaierAlarmStartTrigger + ), + } + ), + cv.Optional(CONF_ON_ALARM_END): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + HaierAlarmEndTrigger + ), + } ), } ), @@ -410,13 +448,12 @@ async def to_code(config): await climate.register_climate(var, config) cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) + if CONF_CONTROL_METHOD in config: + cg.add(var.set_control_method(config[CONF_CONTROL_METHOD])) if CONF_BEEPER in config: cg.add(var.set_beeper_state(config[CONF_BEEPER])) if CONF_DISPLAY in config: cg.add(var.set_display_state(config[CONF_DISPLAY])) - if CONF_OUTDOOR_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) - cg.add(var.set_outdoor_temperature_sensor(sens)) if CONF_SUPPORTED_MODES in config: cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: @@ -425,5 +462,25 @@ async def to_code(config): cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) if CONF_ANSWER_TIMEOUT in config: cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) + if CONF_ALTERNATIVE_SWING_CONTROL in config: + cg.add( + var.set_alternative_swing_control(config[CONF_ALTERNATIVE_SWING_CONTROL]) + ) + if CONF_CONTROL_PACKET_SIZE in config: + cg.add( + var.set_extra_control_packet_bytes_size( + config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE + ) + ) + for conf in config.get(CONF_ON_ALARM_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + ) + for conf in config.get(CONF_ON_ALARM_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + ) # https://github.com/paveldn/HaierProtocol - cg.add_library("pavlodn/HaierProtocol", "0.9.20") + cg.add_library("pavlodn/HaierProtocol", "0.9.25") diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 22899b1a707d..a3f68bb081a1 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -19,56 +19,46 @@ constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000; constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000; constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400; -constexpr size_t CONTROL_TIMEOUT_MS = 7000; -constexpr size_t NO_COMMAND = 0xFF; // Indicate that there is no command supplied -#if (HAIER_LOG_LEVEL > 4) -// To reduce size of binary this function only available when log level is Verbose const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { static const char *phase_names[] = { "SENDING_INIT_1", - "WAITING_INIT_1_ANSWER", "SENDING_INIT_2", - "WAITING_INIT_2_ANSWER", "SENDING_FIRST_STATUS_REQUEST", - "WAITING_FIRST_STATUS_ANSWER", - "SENDING_ALARM_STATUS_REQUEST", - "WAITING_ALARM_STATUS_ANSWER", + "SENDING_FIRST_ALARM_STATUS_REQUEST", "IDLE", - "UNKNOWN", "SENDING_STATUS_REQUEST", - "WAITING_STATUS_ANSWER", "SENDING_UPDATE_SIGNAL_REQUEST", - "WAITING_UPDATE_SIGNAL_ANSWER", "SENDING_SIGNAL_LEVEL", - "WAITING_SIGNAL_LEVEL_ANSWER", "SENDING_CONTROL", - "WAITING_CONTROL_ANSWER", - "SENDING_POWER_ON_COMMAND", - "WAITING_POWER_ON_ANSWER", - "SENDING_POWER_OFF_COMMAND", - "WAITING_POWER_OFF_ANSWER", + "SENDING_ACTION_COMMAND", + "SENDING_ALARM_STATUS_REQUEST", "UNKNOWN" // Should be the last! }; + static_assert( + (sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1), + "Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases"); int phase_index = (int) phase; if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0)) phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES; return phase_names[phase_index]; } -#endif + +bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, + size_t timeout) { + return std::chrono::duration_cast(now - tpoint).count() > timeout; +} HaierClimateBase::HaierClimateBase() : haier_protocol_(*this), protocol_phase_(ProtocolPhases::SENDING_INIT_1), - action_request_(ActionRequest::NO_ACTION), display_status_(true), health_mode_(false), force_send_control_(false), - forced_publish_(false), forced_request_status_(false), - first_control_attempt_(false), reset_protocol_request_(false), - send_wifi_signal_(true) { + send_wifi_signal_(true), + use_crc_(false) { this->traits_ = climate::ClimateTraits(); this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, @@ -84,42 +74,43 @@ HaierClimateBase::~HaierClimateBase() {} void HaierClimateBase::set_phase(ProtocolPhases phase) { if (this->protocol_phase_ != phase) { -#if (HAIER_LOG_LEVEL > 4) ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); -#else - ESP_LOGV(TAG, "Phase transition: %d => %d", (int) this->protocol_phase_, (int) phase); -#endif this->protocol_phase_ = phase; } } -bool HaierClimateBase::check_timeout_(std::chrono::steady_clock::time_point now, - std::chrono::steady_clock::time_point tpoint, size_t timeout) { - return std::chrono::duration_cast(now - tpoint).count() > timeout; +void HaierClimateBase::reset_phase_() { + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); } -bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { - return this->check_timeout_(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); +void HaierClimateBase::reset_to_idle_() { + this->force_send_control_ = false; + if (this->current_hvac_settings_.valid) + this->current_hvac_settings_.reset(); + this->forced_request_status_ = true; + this->set_phase(ProtocolPhases::IDLE); + this->action_request_.reset(); } -bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { - return this->check_timeout_(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); +bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { + return check_timeout(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); } -bool HaierClimateBase::is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now) { - return this->check_timeout_(now, this->control_request_timestamp_, CONTROL_TIMEOUT_MS); +bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { + return check_timeout(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); } bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { - return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); + return check_timeout(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); } bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) { - return this->check_timeout_(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); + return check_timeout(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); } #ifdef USE_WIFI -haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) { +haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() { static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; if (wifi::global_wifi_component->is_connected()) { wifi_status_data[1] = 0; @@ -131,7 +122,8 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t wifi_status_data[1] = 1; wifi_status_data[3] = 0; } - return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data)); + return haier_protocol::HaierMessage(haier_protocol::FrameType::REPORT_NETWORK_STATUS, wifi_status_data, + sizeof(wifi_status_data)); } #endif @@ -140,7 +132,7 @@ bool HaierClimateBase::get_display_state() const { return this->display_status_; void HaierClimateBase::set_display_state(bool state) { if (this->display_status_ != state) { this->display_status_ = state; - this->set_force_send_control_(true); + this->force_send_control_ = true; } } @@ -149,15 +141,24 @@ bool HaierClimateBase::get_health_mode() const { return this->health_mode_; } void HaierClimateBase::set_health_mode(bool state) { if (this->health_mode_ != state) { this->health_mode_ = state; - this->set_force_send_control_(true); + this->force_send_control_ = true; } } -void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionRequest::TURN_POWER_ON; } +void HaierClimateBase::send_power_on_command() { + this->action_request_ = + PendingAction({ActionRequest::TURN_POWER_ON, esphome::optional()}); +} -void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } +void HaierClimateBase::send_power_off_command() { + this->action_request_ = + PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional()}); +} -void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } +void HaierClimateBase::toggle_power() { + this->action_request_ = + PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional()}); +} void HaierClimateBase::set_supported_swing_modes(const std::set &modes) { this->traits_.set_supported_swing_modes(modes); @@ -165,9 +166,7 @@ void HaierClimateBase::set_supported_swing_modes(const std::settraits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); } -void HaierClimateBase::set_answer_timeout(uint32_t timeout) { - this->answer_timeout_ = std::chrono::milliseconds(timeout); -} +void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); } void HaierClimateBase::set_supported_modes(const std::set &modes) { this->traits_.set_supported_modes(modes); @@ -183,29 +182,42 @@ void HaierClimateBase::set_supported_presets(const std::setsend_wifi_signal_ = send_wifi; } -haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t request_message_type, - uint8_t expected_request_message_type, - uint8_t answer_message_type, - uint8_t expected_answer_message_type, - ProtocolPhases expected_phase) { +void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &message) { + this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); +} + +haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( + haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, + haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, + ProtocolPhases expected_phase) { haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; - if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) + if ((expected_request_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) && + (request_message_type != expected_request_message_type)) result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; - if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) + if ((expected_answer_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) && + (answer_message_type != expected_answer_message_type)) result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; - if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) + if (!this->haier_protocol_.is_waiting_for_answer() || + ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_))) result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; - if (is_message_invalid(answer_message_type)) + if (answer_message_type == haier_protocol::FrameType::INVALID) result = haier_protocol::HandlerError::INVALID_ANSWER; return result; } -haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t request_type) { -#if (HAIER_LOG_LEVEL > 4) - ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", request_type, phase_to_string_(this->protocol_phase_)); -#else - ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); -#endif +haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_handler_( + haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, + size_t data_size) { + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, haier_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, + haier_protocol::FrameType::CONFIRM, ProtocolPhases::SENDING_SIGNAL_LEVEL); + this->set_phase(ProtocolPhases::IDLE); + return result; +} + +haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) { + ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type, + phase_to_string_(this->protocol_phase_)); if (this->protocol_phase_ > ProtocolPhases::IDLE) { this->set_phase(ProtocolPhases::IDLE); } else { @@ -219,79 +231,95 @@ void HaierClimateBase::setup() { // Set timestamp here to give AC time to boot this->last_request_timestamp_ = std::chrono::steady_clock::now(); this->set_phase(ProtocolPhases::SENDING_INIT_1); - this->set_handlers(); this->haier_protocol_.set_default_timeout_handler( std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); + this->set_handlers(); } void HaierClimateBase::dump_config() { LOG_CLIMATE("", "Haier Climate", this); - ESP_LOGCONFIG(TAG, " Device communication status: %s", - (this->protocol_phase_ >= ProtocolPhases::IDLE) ? "established" : "none"); + ESP_LOGCONFIG(TAG, " Device communication status: %s", this->valid_connection() ? "established" : "none"); } void HaierClimateBase::loop() { std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); if ((std::chrono::duration_cast(now - this->last_valid_status_timestamp_).count() > COMMUNICATION_TIMEOUT_MS) || - (this->reset_protocol_request_)) { + (this->reset_protocol_request_ && (!this->haier_protocol_.is_waiting_for_answer()))) { + this->last_valid_status_timestamp_ = now; if (this->protocol_phase_ >= ProtocolPhases::IDLE) { // No status too long, reseting protocol + // No need to reset protocol if we didn't pass initialization phase if (this->reset_protocol_request_) { this->reset_protocol_request_ = false; ESP_LOGW(TAG, "Protocol reset requested"); } else { ESP_LOGW(TAG, "Communication timeout, reseting protocol"); } - this->last_valid_status_timestamp_ = now; - this->set_force_send_control_(false); - if (this->hvac_settings_.valid) - this->hvac_settings_.reset(); - this->set_phase(ProtocolPhases::SENDING_INIT_1); + this->process_protocol_reset(); return; - } else { - // No need to reset protocol if we didn't pass initialization phase - this->last_valid_status_timestamp_ = now; } }; - if ((this->protocol_phase_ == ProtocolPhases::IDLE) || - (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || - (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || - (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL)) { + if ((!this->haier_protocol_.is_waiting_for_answer()) && + ((this->protocol_phase_ == ProtocolPhases::IDLE) || + (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || + (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || + (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL))) { // If control message or action is pending we should send it ASAP unless we are in initialisation // procedure or waiting for an answer - if (this->action_request_ != ActionRequest::NO_ACTION) { - this->process_pending_action(); - } else if (this->hvac_settings_.valid || this->force_send_control_) { + if (this->action_request_.has_value() && this->prepare_pending_action()) { + this->set_phase(ProtocolPhases::SENDING_ACTION_COMMAND); + } else if (this->next_hvac_settings_.valid || this->force_send_control_) { ESP_LOGV(TAG, "Control packet is pending..."); this->set_phase(ProtocolPhases::SENDING_CONTROL); + if (this->next_hvac_settings_.valid) { + this->current_hvac_settings_ = this->next_hvac_settings_; + this->next_hvac_settings_.reset(); + } else { + this->current_hvac_settings_.reset(); + } } } this->process_phase(now); this->haier_protocol_.loop(); } -void HaierClimateBase::process_pending_action() { - ActionRequest request = this->action_request_; - if (this->action_request_ == ActionRequest::TOGGLE_POWER) { - request = this->mode == CLIMATE_MODE_OFF ? ActionRequest::TURN_POWER_ON : ActionRequest::TURN_POWER_OFF; - } - switch (request) { - case ActionRequest::TURN_POWER_ON: - this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND); - break; - case ActionRequest::TURN_POWER_OFF: - this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND); - break; - case ActionRequest::TOGGLE_POWER: - case ActionRequest::NO_ACTION: - // shouldn't get here, do nothing - break; - default: - ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_); - break; - } - this->action_request_ = ActionRequest::NO_ACTION; +void HaierClimateBase::process_protocol_reset() { + this->force_send_control_ = false; + if (this->current_hvac_settings_.valid) + this->current_hvac_settings_.reset(); + if (this->next_hvac_settings_.valid) + this->next_hvac_settings_.reset(); + this->mode = CLIMATE_MODE_OFF; + this->current_temperature = NAN; + this->target_temperature = NAN; + this->fan_mode.reset(); + this->preset.reset(); + this->publish_state(); + this->set_phase(ProtocolPhases::SENDING_INIT_1); +} + +bool HaierClimateBase::prepare_pending_action() { + if (this->action_request_.has_value()) { + switch (this->action_request_.value().action) { + case ActionRequest::SEND_CUSTOM_COMMAND: + return true; + case ActionRequest::TURN_POWER_ON: + this->action_request_.value().message = this->get_power_message(true); + return true; + case ActionRequest::TURN_POWER_OFF: + this->action_request_.value().message = this->get_power_message(false); + return true; + case ActionRequest::TOGGLE_POWER: + this->action_request_.value().message = this->get_power_message(this->mode == ClimateMode::CLIMATE_MODE_OFF); + return true; + default: + ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_.value().action); + this->action_request_.reset(); + return false; + } + } else + return false; } ClimateTraits HaierClimateBase::traits() { return traits_; } @@ -302,23 +330,22 @@ void HaierClimateBase::control(const ClimateCall &call) { ESP_LOGW(TAG, "Can't send control packet, first poll answer not received"); return; // cancel the control, we cant do it without a poll answer. } - if (this->hvac_settings_.valid) { - ESP_LOGW(TAG, "Overriding old valid settings before they were applied!"); + if (this->current_hvac_settings_.valid) { + ESP_LOGW(TAG, "New settings come faster then processed!"); } { if (call.get_mode().has_value()) - this->hvac_settings_.mode = call.get_mode(); + this->next_hvac_settings_.mode = call.get_mode(); if (call.get_fan_mode().has_value()) - this->hvac_settings_.fan_mode = call.get_fan_mode(); + this->next_hvac_settings_.fan_mode = call.get_fan_mode(); if (call.get_swing_mode().has_value()) - this->hvac_settings_.swing_mode = call.get_swing_mode(); + this->next_hvac_settings_.swing_mode = call.get_swing_mode(); if (call.get_target_temperature().has_value()) - this->hvac_settings_.target_temperature = call.get_target_temperature(); + this->next_hvac_settings_.target_temperature = call.get_target_temperature(); if (call.get_preset().has_value()) - this->hvac_settings_.preset = call.get_preset(); - this->hvac_settings_.valid = true; + this->next_hvac_settings_.preset = call.get_preset(); + this->next_hvac_settings_.valid = true; } - this->first_control_attempt_ = true; } void HaierClimateBase::HvacSettings::reset() { @@ -330,19 +357,9 @@ void HaierClimateBase::HvacSettings::reset() { this->preset.reset(); } -void HaierClimateBase::set_force_send_control_(bool status) { - this->force_send_control_ = status; - if (status) { - this->first_control_attempt_ = true; - } -} - -void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) { - if (this->answer_timeout_.has_value()) { - this->haier_protocol_.send_message(command, use_crc, this->answer_timeout_.value()); - } else { - this->haier_protocol_.send_message(command, use_crc); - } +void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats, + std::chrono::milliseconds interval) { + this->haier_protocol_.send_message(command, use_crc, num_repeats, interval); this->last_request_timestamp_ = std::chrono::steady_clock::now(); } diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index b2446d6fb5a5..504c841e5f64 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -11,7 +11,7 @@ namespace esphome { namespace haier { enum class ActionRequest : uint8_t { - NO_ACTION = 0, + SEND_CUSTOM_COMMAND = 0, TURN_POWER_ON = 1, TURN_POWER_OFF = 2, TOGGLE_POWER = 3, @@ -33,7 +33,6 @@ class HaierClimateBase : public esphome::Component, void control(const esphome::climate::ClimateCall &call) override; void dump_config() override; float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } - void set_fahrenheit(bool fahrenheit); void set_display_state(bool state); bool get_display_state() const; void set_health_mode(bool state); @@ -45,6 +44,7 @@ class HaierClimateBase : public esphome::Component, void set_supported_modes(const std::set &modes); void set_supported_swing_modes(const std::set &modes); void set_supported_presets(const std::set &presets); + bool valid_connection() { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; size_t read_array(uint8_t *data, size_t len) noexcept override { return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; @@ -55,63 +55,57 @@ class HaierClimateBase : public esphome::Component, bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; void set_answer_timeout(uint32_t timeout); void set_send_wifi(bool send_wifi); + void send_custom_command(const haier_protocol::HaierMessage &message); protected: enum class ProtocolPhases { UNKNOWN = -1, // INITIALIZATION SENDING_INIT_1 = 0, - WAITING_INIT_1_ANSWER = 1, - SENDING_INIT_2 = 2, - WAITING_INIT_2_ANSWER = 3, - SENDING_FIRST_STATUS_REQUEST = 4, - WAITING_FIRST_STATUS_ANSWER = 5, - SENDING_ALARM_STATUS_REQUEST = 6, - WAITING_ALARM_STATUS_ANSWER = 7, + SENDING_INIT_2, + SENDING_FIRST_STATUS_REQUEST, + SENDING_FIRST_ALARM_STATUS_REQUEST, // FUNCTIONAL STATE - IDLE = 8, - SENDING_STATUS_REQUEST = 10, - WAITING_STATUS_ANSWER = 11, - SENDING_UPDATE_SIGNAL_REQUEST = 12, - WAITING_UPDATE_SIGNAL_ANSWER = 13, - SENDING_SIGNAL_LEVEL = 14, - WAITING_SIGNAL_LEVEL_ANSWER = 15, - SENDING_CONTROL = 16, - WAITING_CONTROL_ANSWER = 17, - SENDING_POWER_ON_COMMAND = 18, - WAITING_POWER_ON_ANSWER = 19, - SENDING_POWER_OFF_COMMAND = 20, - WAITING_POWER_OFF_ANSWER = 21, + IDLE, + SENDING_STATUS_REQUEST, + SENDING_UPDATE_SIGNAL_REQUEST, + SENDING_SIGNAL_LEVEL, + SENDING_CONTROL, + SENDING_ACTION_COMMAND, + SENDING_ALARM_STATUS_REQUEST, NUM_PROTOCOL_PHASES }; -#if (HAIER_LOG_LEVEL > 4) const char *phase_to_string_(ProtocolPhases phase); -#endif virtual void set_handlers() = 0; virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; virtual haier_protocol::HaierMessage get_control_message() = 0; - virtual bool is_message_invalid(uint8_t message_type) = 0; - virtual void process_pending_action(); + virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; + virtual bool prepare_pending_action(); + virtual void process_protocol_reset(); esphome::climate::ClimateTraits traits() override; - // Answers handlers - haier_protocol::HandlerError answer_preprocess_(uint8_t request_message_type, uint8_t expected_request_message_type, - uint8_t answer_message_type, uint8_t expected_answer_message_type, + // Answer handlers + haier_protocol::HandlerError answer_preprocess_(haier_protocol::FrameType request_message_type, + haier_protocol::FrameType expected_request_message_type, + haier_protocol::FrameType answer_message_type, + haier_protocol::FrameType expected_answer_message_type, ProtocolPhases expected_phase); + haier_protocol::HandlerError report_network_status_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, + const uint8_t *data, size_t data_size); // Timeout handler - haier_protocol::HandlerError timeout_default_handler_(uint8_t request_type); + haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type); // Helper functions - void set_force_send_control_(bool status); - void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); + void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats = 0, + std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); virtual void set_phase(ProtocolPhases phase); - bool check_timeout_(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, - size_t timeout); + void reset_phase_(); + void reset_to_idle_(); bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); - bool is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now); bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); #ifdef USE_WIFI - haier_protocol::HaierMessage get_wifi_signal_message_(uint8_t message_type); + haier_protocol::HaierMessage get_wifi_signal_message_(); #endif struct HvacSettings { @@ -122,29 +116,34 @@ class HaierClimateBase : public esphome::Component, esphome::optional preset; bool valid; HvacSettings() : valid(false){}; + HvacSettings(const HvacSettings &) = default; + HvacSettings &operator=(const HvacSettings &) = default; void reset(); }; + struct PendingAction { + ActionRequest action; + esphome::optional message; + }; haier_protocol::ProtocolHandler haier_protocol_; ProtocolPhases protocol_phase_; - ActionRequest action_request_; + esphome::optional action_request_; uint8_t fan_mode_speed_; uint8_t other_modes_fan_speed_; bool display_status_; bool health_mode_; bool force_send_control_; - bool forced_publish_; bool forced_request_status_; - bool first_control_attempt_; bool reset_protocol_request_; + bool send_wifi_signal_; + bool use_crc_; esphome::climate::ClimateTraits traits_; - HvacSettings hvac_settings_; + HvacSettings current_hvac_settings_; + HvacSettings next_hvac_settings_; + std::unique_ptr last_status_message_; std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout std::chrono::steady_clock::time_point last_status_request_; // To request AC status - std::chrono::steady_clock::time_point control_request_timestamp_; // To send control message - optional answer_timeout_; // Message answer timeout - bool send_wifi_signal_; - std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level + std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level }; } // namespace haier diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index d4944410f703..9933cb4c8f77 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -2,6 +2,7 @@ #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" +#include "esphome/core/helpers.h" #include "hon_climate.h" #include "hon_packet.h" @@ -14,6 +15,9 @@ namespace haier { static const char *const TAG = "haier.climate"; constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; +constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; +constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); +constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000; hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { switch (direction) { @@ -48,14 +52,10 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir } HonClimate::HonClimate() - : last_status_message_(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]), - cleaning_status_(CleaningState::NO_CLEANING), - got_valid_outdoor_temp_(false), - hvac_hardware_info_available_(false), - hvac_functions_{false, false, false, false, false}, - use_crc_(hvac_functions_[2]), - active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - outdoor_sensor_(nullptr) { + : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00} { + last_status_message_ = std::unique_ptr(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; } @@ -66,20 +66,18 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } bool HonClimate::get_beeper_state() const { return this->beeper_status_; } -void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } - AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { this->vertical_direction_ = direction; - this->set_force_send_control_(true); + this->force_send_control_ = true; } AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { this->horizontal_direction_ = direction; - this->set_force_send_control_(true); + this->force_send_control_ = true; } std::string HonClimate::get_cleaning_status_text() const { @@ -98,35 +96,43 @@ CleaningState HonClimate::get_cleaning_status() const { return this->cleaning_st void HonClimate::start_self_cleaning() { if (this->cleaning_status_ == CleaningState::NO_CLEANING) { ESP_LOGI(TAG, "Sending self cleaning start request"); - this->action_request_ = ActionRequest::START_SELF_CLEAN; - this->set_force_send_control_(true); + this->action_request_ = + PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional()}); } } void HonClimate::start_steri_cleaning() { if (this->cleaning_status_ == CleaningState::NO_CLEANING) { ESP_LOGI(TAG, "Sending steri cleaning start request"); - this->action_request_ = ActionRequest::START_STERI_CLEAN; - this->set_force_send_control_(true); + this->action_request_ = + PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional()}); } } -haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, +void HonClimate::add_alarm_start_callback(std::function &&callback) { + this->alarm_start_callback_.add(std::move(callback)); +} + +void HonClimate::add_alarm_end_callback(std::function &&callback) { + this->alarm_end_callback_.add(std::move(callback)); +} + +haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { // Should check this before preprocess - if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) { + if (message_type == haier_protocol::FrameType::INVALID) { ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " "protocol instead of hOn"); this->set_phase(ProtocolPhases::SENDING_INIT_1); return haier_protocol::HandlerError::INVALID_ANSWER; } - haier_protocol::HandlerError result = this->answer_preprocess_( - request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, - (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER); + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type, + haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1); if (result == haier_protocol::HandlerError::HANDLER_OK) { if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { // Wrong structure - this->set_phase(ProtocolPhases::SENDING_INIT_1); return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; } // All OK @@ -134,54 +140,57 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint char tmp[9]; tmp[8] = 0; strncpy(tmp, answr->protocol_version, 8); - this->hvac_protocol_version_ = std::string(tmp); + this->hvac_hardware_info_ = HardwareInfo(); + this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp); strncpy(tmp, answr->software_version, 8); - this->hvac_software_version_ = std::string(tmp); + this->hvac_hardware_info_.value().software_version_ = std::string(tmp); strncpy(tmp, answr->hardware_version, 8); - this->hvac_hardware_version_ = std::string(tmp); + this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp); strncpy(tmp, answr->device_name, 8); - this->hvac_device_name_ = std::string(tmp); - this->hvac_functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support - this->hvac_functions_[1] = (answr->functions[1] & 0x02) != 0; // controller-device mode support - this->hvac_functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support - this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support - this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support - this->hvac_hardware_info_available_ = true; + this->hvac_hardware_info_.value().device_name_ = std::string(tmp); + this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support + this->hvac_hardware_info_.value().functions_[1] = + (answr->functions[1] & 0x02) != 0; // controller-device mode support + this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support + this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support + this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support + this->use_crc_ = this->hvac_hardware_info_.value().functions_[2]; this->set_phase(ProtocolPhases::SENDING_INIT_2); return result; } else { - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->reset_phase_(); return result; } } -haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, +haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { - haier_protocol::HandlerError result = this->answer_preprocess_( - request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, - (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER); + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type, + haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2); if (result == haier_protocol::HandlerError::HANDLER_OK) { this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); return result; } else { - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->reset_phase_(); return result; } } -haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, uint8_t message_type, - const uint8_t *data, size_t data_size) { +haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, + size_t data_size) { haier_protocol::HandlerError result = - this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::CONTROL, message_type, - (uint8_t) hon_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); + this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type, + haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); if (result == haier_protocol::HandlerError::HANDLER_OK) { result = this->process_status_message_(data, data_size); if (result != haier_protocol::HandlerError::HANDLER_OK) { ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->reset_phase_(); + this->action_request_.reset(); + this->force_send_control_ = false; } else { if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); @@ -189,36 +198,48 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, sizeof(hon_protocol::HaierPacketControl)); } - if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { - ESP_LOGI(TAG, "First HVAC status received"); - this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); - } else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || - (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || - (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { - this->set_phase(ProtocolPhases::IDLE); - } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { - this->set_phase(ProtocolPhases::IDLE); - this->set_force_send_control_(false); - if (this->hvac_settings_.valid) - this->hvac_settings_.reset(); + switch (this->protocol_phase_) { + case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: + ESP_LOGI(TAG, "First HVAC status received"); + this->set_phase(ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST); + break; + case ProtocolPhases::SENDING_ACTION_COMMAND: + // Do nothing, phase will be changed in process_phase + break; + case ProtocolPhases::SENDING_STATUS_REQUEST: + this->set_phase(ProtocolPhases::IDLE); + break; + case ProtocolPhases::SENDING_CONTROL: + if (!this->control_messages_queue_.empty()) + this->control_messages_queue_.pop(); + if (this->control_messages_queue_.empty()) { + this->set_phase(ProtocolPhases::IDLE); + this->force_send_control_ = false; + if (this->current_hvac_settings_.valid) + this->current_hvac_settings_.reset(); + } else { + this->set_phase(ProtocolPhases::SENDING_CONTROL); + } + break; + default: + break; } } return result; } else { - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->action_request_.reset(); + this->force_send_control_ = false; + this->reset_phase_(); return result; } } -haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_(uint8_t request_type, - uint8_t message_type, - const uint8_t *data, - size_t data_size) { - haier_protocol::HandlerError result = - this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION, - message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, - ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); +haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_( + haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, + size_t data_size) { + haier_protocol::HandlerError result = this->answer_preprocess_( + request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type, + haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); if (result == haier_protocol::HandlerError::HANDLER_OK) { this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); return result; @@ -228,30 +249,24 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl } } -haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(uint8_t request_type, - uint8_t message_type, - const uint8_t *data, size_t data_size) { - haier_protocol::HandlerError result = - this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, - (uint8_t) hon_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); - this->set_phase(ProtocolPhases::IDLE); - return result; -} - -haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, +haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { - if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { - if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { + if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) { + if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { // Unexpected answer to request this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; } - if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { + if ((this->protocol_phase_ != ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) && + (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST)) { // Don't expect this answer now this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; } - memcpy(this->active_alarms_, data + 2, 8); + if (data_size < sizeof(active_alarms_) + 2) + return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::HANDLER_OK; } else { @@ -260,45 +275,66 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_ } } +haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type, + const uint8_t *buffer, size_t size) { + haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; + if (size < sizeof(this->active_alarms_) + 2) { + // Log error but confirm anyway to avoid to many messages + result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + } + this->process_alarm_message_(buffer, size, true); + this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM)); + this->last_alarm_request_ = std::chrono::steady_clock::now(); + return result; +} + void HonClimate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), + haier_protocol::FrameType::GET_DEVICE_VERSION, std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::GET_DEVICE_ID), + haier_protocol::FrameType::GET_DEVICE_ID, std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::CONTROL), + haier_protocol::FrameType::CONTROL, std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION), + haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::GET_ALARM_STATUS), + haier_protocol::FrameType::GET_ALARM_STATUS, std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (hon_protocol::FrameType::REPORT_NETWORK_STATUS), + haier_protocol::FrameType::REPORT_NETWORK_STATUS, std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_message_handler( + haier_protocol::FrameType::ALARM_STATUS, + std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); } void HonClimate::dump_config() { HaierClimateBase::dump_config(); ESP_LOGCONFIG(TAG, " Protocol version: hOn"); - if (this->hvac_hardware_info_available_) { - ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_protocol_version_.c_str()); - ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_software_version_.c_str()); - ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_version_.c_str()); - ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_device_name_.c_str()); - ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s", (this->hvac_functions_[0] ? " interactive" : ""), - (this->hvac_functions_[1] ? " controller-device" : ""), (this->hvac_functions_[2] ? " crc" : ""), - (this->hvac_functions_[3] ? " multinode" : ""), (this->hvac_functions_[4] ? " role" : "")); + ESP_LOGCONFIG(TAG, " Control method: %d", (uint8_t) this->control_method_); + if (this->hvac_hardware_info_.has_value()) { + ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_hardware_info_.value().protocol_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_hardware_info_.value().software_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_info_.value().hardware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_hardware_info_.value().device_name_.c_str()); + ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s", + (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""), + (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""), + (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""), + (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""), + (this->hvac_hardware_info_.value().functions_[4] ? " role" : "")); ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str()); } } @@ -307,7 +343,6 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { switch (this->protocol_phase_) { case ProtocolPhases::SENDING_INIT_1: if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { - this->hvac_hardware_info_available_ = false; // Indicate device capabilities: // bit 0 - if 1 module support interactive mode // bit 1 - if 1 module support controller-device mode @@ -316,143 +351,152 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { // bit 4..bit 15 - not used uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( - (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); + haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); - this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); } break; case ProtocolPhases::SENDING_INIT_2: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - static const haier_protocol::HaierMessage DEVICEID_REQUEST((uint8_t) hon_protocol::FrameType::GET_DEVICE_ID); + static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID); this->send_message_(DEVICEID_REQUEST, this->use_crc_); - this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER); } break; case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage STATUS_REQUEST( - (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); - this->send_message_(STATUS_REQUEST, this->use_crc_); + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); + static const haier_protocol::HaierMessage BIG_DATA_REQUEST( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA); + if ((this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) || + (!this->should_get_big_data_())) { + this->send_message_(STATUS_REQUEST, this->use_crc_); + } else { + this->send_message_(BIG_DATA_REQUEST, this->use_crc_); + } this->last_status_request_ = now; - this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); } break; #ifdef USE_WIFI case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST( - (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); + haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION); this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); this->last_signal_request_ = now; - this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); } break; case ProtocolPhases::SENDING_SIGNAL_LEVEL: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS), - this->use_crc_); - this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + this->send_message_(this->get_wifi_signal_message_(), this->use_crc_); } break; - case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: - break; #else case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::SENDING_SIGNAL_LEVEL: - case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: this->set_phase(ProtocolPhases::IDLE); break; #endif + case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( - (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS); + static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS); this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); - this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); + this->last_alarm_request_ = now; } break; case ProtocolPhases::SENDING_CONTROL: - if (this->first_control_attempt_) { - this->control_request_timestamp_ = now; - this->first_control_attempt_ = false; + if (this->control_messages_queue_.empty()) { + switch (this->control_method_) { + case HonControlMethod::SET_GROUP_PARAMETERS: { + haier_protocol::HaierMessage control_message = this->get_control_message(); + this->control_messages_queue_.push(control_message); + } break; + case HonControlMethod::SET_SINGLE_PARAMETER: + this->fill_control_messages_queue_(); + break; + case HonControlMethod::MONITOR_ONLY: + ESP_LOGI(TAG, "AC control is disabled, monitor only"); + this->reset_to_idle_(); + return; + default: + ESP_LOGW(TAG, "Unsupported control method for hOn protocol!"); + this->reset_to_idle_(); + return; + } } - if (this->is_control_message_timeout_exceeded_(now)) { - ESP_LOGW(TAG, "Sending control packet timeout!"); - this->set_force_send_control_(false); - if (this->hvac_settings_.valid) - this->hvac_settings_.reset(); - this->forced_request_status_ = true; - this->forced_publish_ = true; - this->set_phase(ProtocolPhases::IDLE); + if (this->control_messages_queue_.empty()) { + ESP_LOGW(TAG, "Control message queue is empty!"); + this->reset_to_idle_(); } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { - haier_protocol::HaierMessage control_message = get_control_message(); - this->send_message_(control_message, this->use_crc_); - ESP_LOGI(TAG, "Control packet sent"); - this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); + ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size()); + this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES, + CONTROL_MESSAGE_RETRIES_INTERVAL); } break; - case ProtocolPhases::SENDING_POWER_ON_COMMAND: - case ProtocolPhases::SENDING_POWER_OFF_COMMAND: - if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - uint8_t pwr_cmd_buf[2] = {0x00, 0x00}; - if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) - pwr_cmd_buf[1] = 0x01; - haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, - ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, - pwr_cmd_buf, sizeof(pwr_cmd_buf)); - this->send_message_(power_cmd, this->use_crc_); - this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND - ? ProtocolPhases::WAITING_POWER_ON_ANSWER - : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + case ProtocolPhases::SENDING_ACTION_COMMAND: + if (this->action_request_.has_value()) { + if (this->action_request_.value().message.has_value()) { + this->send_message_(this->action_request_.value().message.value(), this->use_crc_); + this->action_request_.value().message.reset(); + } else { + // Message already sent, reseting request and return to idle + this->action_request_.reset(); + this->set_phase(ProtocolPhases::IDLE); + } + } else { + ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!"); + this->set_phase(ProtocolPhases::IDLE); } break; - - case ProtocolPhases::WAITING_INIT_1_ANSWER: - case ProtocolPhases::WAITING_INIT_2_ANSWER: - case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: - case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: - case ProtocolPhases::WAITING_STATUS_ANSWER: - case ProtocolPhases::WAITING_CONTROL_ANSWER: - case ProtocolPhases::WAITING_POWER_ON_ANSWER: - case ProtocolPhases::WAITING_POWER_OFF_ANSWER: - break; case ProtocolPhases::IDLE: { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->forced_request_status_ = false; + } else if (std::chrono::duration_cast(now - this->last_alarm_request_).count() > + ALARM_STATUS_REQUEST_INTERVAL_MS) { + this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); } #ifdef USE_WIFI else if (this->send_wifi_signal_ && (std::chrono::duration_cast(now - this->last_signal_request_).count() > - SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) + SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) { this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); + } #endif } break; default: // Shouldn't get here -#if (HAIER_LOG_LEVEL > 4) ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); -#else - ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); -#endif this->set_phase(ProtocolPhases::SENDING_INIT_1); break; } } +haier_protocol::HaierMessage HonClimate::get_power_message(bool state) { + if (state) { + static haier_protocol::HaierMessage power_on_message( + haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, + std::initializer_list({0x00, 0x01}).begin(), 2); + return power_on_message; + } else { + static haier_protocol::HaierMessage power_off_message( + haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, + std::initializer_list({0x00, 0x00}).begin(), 2); + return power_off_message; + } +} + haier_protocol::HaierMessage HonClimate::get_control_message() { uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + control_out_buffer[4] = 0; // This byte should be cleared before setting values bool has_hvac_settings = false; - if (this->hvac_settings_.valid) { + if (this->current_hvac_settings_.valid) { has_hvac_settings = true; - HvacSettings climate_control; - climate_control = this->hvac_settings_; + HvacSettings &climate_control = this->current_hvac_settings_; if (climate_control.mode.has_value()) { switch (climate_control.mode.value()) { case CLIMATE_MODE_OFF: @@ -535,7 +579,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { } if (climate_control.target_temperature.has_value()) { float target_temp = climate_control.target_temperature.value(); - out_data->set_point = ((int) target_temp) - 16; // set the temperature at our offset, subtract 16. + out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; } if (out_data->ac_power == 0) { @@ -549,31 +593,41 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_ECO: // Eco is not supported in Fan only mode out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_BOOST: out_data->quiet_mode = 0; // Boost is not supported in Fan only mode out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; case CLIMATE_PRESET_AWAY: out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; + // 10 degrees allowed only in heat mode + out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; break; case CLIMATE_PRESET_SLEEP: out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 1; + out_data->ten_degree = 0; break; default: ESP_LOGE("Control", "Unsupported preset"); + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + out_data->ten_degree = 0; break; } } @@ -587,54 +641,158 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { control_out_buffer[4] = 0; // This byte should be cleared before setting values out_data->display_status = this->display_status_ ? 1 : 0; out_data->health_mode = this->health_mode_ ? 1 : 0; - switch (this->action_request_) { - case ActionRequest::START_SELF_CLEAN: - this->action_request_ = ActionRequest::NO_ACTION; - out_data->self_cleaning_status = 1; - out_data->steri_clean = 0; - out_data->set_point = 0x06; - out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; - out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; - out_data->ac_power = 1; - out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; - out_data->light_status = 0; - break; - case ActionRequest::START_STERI_CLEAN: - this->action_request_ = ActionRequest::NO_ACTION; - out_data->self_cleaning_status = 0; - out_data->steri_clean = 1; - out_data->set_point = 0x06; - out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; - out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; - out_data->ac_power = 1; - out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; - out_data->light_status = 0; - break; - default: - // No change - break; - } - return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL, + return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); } +void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) { + constexpr size_t active_alarms_size = sizeof(this->active_alarms_); + if (size >= active_alarms_size + 2) { + if (check_new) { + size_t alarm_code = 0; + for (int i = active_alarms_size - 1; i >= 0; i--) { + if (packet[2 + i] != active_alarms_[i]) { + uint8_t alarm_bit = 1; + for (int b = 0; b < 8; b++) { + if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) { + bool alarm_status = (packet[2 + i] & alarm_bit) != 0; + int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO; + const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT + ? esphome::haier::hon_protocol::HON_ALARM_MESSAGES[alarm_code].c_str() + : "Unknown"; + esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated", + alarm_code, alarm_message); + if (alarm_status) { + this->alarm_start_callback_.call(alarm_code, alarm_message); + this->active_alarm_count_ += 1.0f; + } else { + this->alarm_end_callback_.call(alarm_code, alarm_message); + this->active_alarm_count_ -= 1.0f; + } + } + alarm_bit <<= 1; + alarm_code++; + } + active_alarms_[i] = packet[2 + i]; + } else + alarm_code += 8; + } + } else { + float alarm_count = 0.0f; + static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + for (size_t i = 0; i < sizeof(this->active_alarms_); i++) { + alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]); + } + this->active_alarm_count_ = alarm_count; + memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_)); + } + } +} + +#ifdef USE_SENSOR +void HonClimate::set_sub_sensor(SubSensorType type, sensor::Sensor *sens) { + if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { + if (type >= SubSensorType::BIG_DATA_FRAME_SUB_SENSORS) { + if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { + this->big_data_sensors_--; + } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { + this->big_data_sensors_++; + } + } + this->sub_sensors_[(size_t) type] = sens; + } +} + +void HonClimate::update_sub_sensor_(SubSensorType type, float value) { + if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { + size_t index = (size_t) type; + if ((this->sub_sensors_[index] != nullptr) && + ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value))) + this->sub_sensors_[index]->publish_state(value); + } +} +#endif // USE_SENSOR + +#ifdef USE_BINARY_SENSOR +void HonClimate::set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens) { + if (type < SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT) { + if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { + this->big_data_sensors_--; + } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { + this->big_data_sensors_++; + } + this->sub_binary_sensors_[(size_t) type] = sens; + } +} + +void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value) { + if (value < 2) { + bool converted_value = value == 1; + size_t index = (size_t) type; + if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) || + (this->sub_binary_sensors_[index]->state != converted_value))) + this->sub_binary_sensors_[index]->publish_state(converted_value); + } +} +#endif // USE_BINARY_SENSOR + haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { - if (size < sizeof(hon_protocol::HaierStatus)) + size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + + this->extra_control_packet_bytes_; + if (size < expected_size) return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; - hon_protocol::HaierStatus packet; - if (size < sizeof(hon_protocol::HaierStatus)) - size = sizeof(hon_protocol::HaierStatus); - memcpy(&packet, packet_buffer, size); + uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; + if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) { + // Got BigData packet + const hon_protocol::HaierPacketBigData *bd_packet = + (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]); +#ifdef USE_SENSOR + this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20); + this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64); + this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64); + this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1])); + this->update_sub_sensor_(SubSensorType::COMPRESSOR_FREQUENCY, bd_packet->compressor_frequency); + this->update_sub_sensor_(SubSensorType::COMPRESSOR_CURRENT, + encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0); + this->update_sub_sensor_( + SubSensorType::EXPANSION_VALVE_OPEN_DEGREE, + encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0); +#endif // USE_SENSOR +#ifdef USE_BINARY_SENSOR + this->update_sub_binary_sensor_(SubBinarySensorType::OUTDOOR_FAN_STATUS, bd_packet->outdoor_fan_status); + this->update_sub_binary_sensor_(SubBinarySensorType::DEFROST_STATUS, bd_packet->defrost_status); + this->update_sub_binary_sensor_(SubBinarySensorType::COMPRESSOR_STATUS, bd_packet->compressor_status); + this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_FAN_STATUS, bd_packet->indoor_fan_status); + this->update_sub_binary_sensor_(SubBinarySensorType::FOUR_WAY_VALVE_STATUS, bd_packet->four_way_valve_status); + this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_ELECTRIC_HEATING_STATUS, + bd_packet->indoor_electric_heating_status); +#endif // USE_BINARY_SENSOR + } + struct { + hon_protocol::HaierPacketControl control; + hon_protocol::HaierPacketSensors sensors; + } packet; + memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl)); + memcpy(&packet.sensors, + packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_, + sizeof(hon_protocol::HaierPacketSensors)); if (packet.sensors.error_status != 0) { ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); } - if ((this->outdoor_sensor_ != nullptr) && (got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { - got_valid_outdoor_temp_ = true; - float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); - if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) - this->outdoor_sensor_->publish_state(otemp); +#ifdef USE_SENSOR + if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) && + (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { + this->got_valid_outdoor_temp_ = true; + this->update_sub_sensor_(SubSensorType::OUTDOOR_TEMPERATURE, + (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET)); } + if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) { + this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity); + } +#endif // USE_SENSOR bool should_publish = false; { // Extra modes/presets @@ -645,6 +803,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * this->preset = CLIMATE_PRESET_BOOST; } else if (packet.control.sleep_mode != 0) { this->preset = CLIMATE_PRESET_SLEEP; + } else if (packet.control.ten_degree != 0) { + this->preset = CLIMATE_PRESET_AWAY; } else { this->preset = CLIMATE_PRESET_NONE; } @@ -703,7 +863,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * // Do something only if display status changed if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display - this->set_force_send_control_(true); + this->force_send_control_ = true; } else { this->display_status_ = disp_status; } @@ -732,7 +892,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); if (new_cleaning == CleaningState::NO_CLEANING) { // Turning AC off after cleaning - this->action_request_ = ActionRequest::TURN_POWER_OFF; + this->action_request_ = + PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional()}); } this->cleaning_status_ = new_cleaning; } @@ -783,50 +944,286 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * should_publish = should_publish || (old_swing_mode != this->swing_mode); } this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); - if (this->forced_publish_ || should_publish) { -#if (HAIER_LOG_LEVEL > 4) - std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); -#endif + if (should_publish) { this->publish_state(); -#if (HAIER_LOG_LEVEL > 4) - ESP_LOGV(TAG, "Publish delay: %lld ms", - std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - - _publish_start) - .count()); -#endif - this->forced_publish_ = false; } if (should_publish) { ESP_LOGI(TAG, "HVAC values changed"); } - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "HVAC Mode = 0x%X", packet.control.ac_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Fan speed Status = 0x%X", packet.control.fan_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Set Point Status = 0x%X", packet.control.set_point); + int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG; + esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point); return haier_protocol::HandlerError::HANDLER_OK; } -bool HonClimate::is_message_invalid(uint8_t message_type) { - return message_type == (uint8_t) hon_protocol::FrameType::INVALID; +void HonClimate::fill_control_messages_queue_() { + static uint8_t one_buf[] = {0x00, 0x01}; + static uint8_t zero_buf[] = {0x00, 0x00}; + if (!this->current_hvac_settings_.valid && !this->force_send_control_) + return; + this->clear_control_messages_queue_(); + HvacSettings climate_control; + climate_control = this->current_hvac_settings_; + // Beeper command + { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, + this->beeper_status_ ? zero_buf : one_buf, 2)); + } + // Health mode + { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, + this->health_mode_ ? one_buf : zero_buf, 2)); + } + // Climate mode + bool new_power = this->mode != CLIMATE_MODE_OFF; + uint8_t fan_mode_buf[] = {0x00, 0xFF}; + uint8_t quiet_mode_buf[] = {0x00, 0xFF}; + if (climate_control.mode.has_value()) { + uint8_t buffer[2] = {0x00, 0x00}; + switch (climate_control.mode.value()) { + case CLIMATE_MODE_OFF: + new_power = false; + break; + case CLIMATE_MODE_HEAT_COOL: + new_power = true; + buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2)); + fan_mode_buf[1] = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_HEAT: + new_power = true; + buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2)); + fan_mode_buf[1] = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_DRY: + new_power = true; + buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2)); + fan_mode_buf[1] = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_FAN_ONLY: + new_power = true; + buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2)); + fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode + // Disabling eco mode for Fan only + quiet_mode_buf[1] = 0; + break; + case CLIMATE_MODE_COOL: + new_power = true; + buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2)); + fan_mode_buf[1] = this->other_modes_fan_speed_; + break; + default: + ESP_LOGE("Control", "Unsupported climate mode"); + break; + } + } + // Climate power + { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_POWER, + new_power ? one_buf : zero_buf, 2)); + } + // CLimate preset + { + uint8_t fast_mode_buf[] = {0x00, 0xFF}; + uint8_t away_mode_buf[] = {0x00, 0xFF}; + if (!new_power) { + // If AC is off - no presets allowed + quiet_mode_buf[1] = 0x00; + fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; + } else if (climate_control.preset.has_value()) { + switch (climate_control.preset.value()) { + case CLIMATE_PRESET_NONE: + quiet_mode_buf[1] = 0x00; + fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; + break; + case CLIMATE_PRESET_ECO: + // Eco is not supported in Fan only mode + quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; + fast_mode_buf[1] = 0x00; + away_mode_buf[1] = 0x00; + break; + case CLIMATE_PRESET_BOOST: + quiet_mode_buf[1] = 0x00; + // Boost is not supported in Fan only mode + fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; + away_mode_buf[1] = 0x00; + break; + case CLIMATE_PRESET_AWAY: + quiet_mode_buf[1] = 0x00; + fast_mode_buf[1] = 0x00; + away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; + break; + default: + ESP_LOGE("Control", "Unsupported preset"); + break; + } + } + auto presets = this->traits_.get_supported_presets(); + if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::QUIET_MODE, + quiet_mode_buf, 2)); + } + if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAST_MODE, + fast_mode_buf, 2)); + } + if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, + away_mode_buf, 2)); + } + } + // Target temperature + if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { + uint8_t buffer[2] = {0x00, 0x00}; + buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::SET_POINT, + buffer, 2)); + } + // Fan mode + if (climate_control.fan_mode.has_value()) { + switch (climate_control.fan_mode.value()) { + case CLIMATE_FAN_LOW: + fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW; + break; + case CLIMATE_FAN_MEDIUM: + fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID; + break; + case CLIMATE_FAN_HIGH: + fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH; + break; + case CLIMATE_FAN_AUTO: + if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode + fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO; + break; + default: + ESP_LOGE("Control", "Unsupported fan mode"); + break; + } + if (fan_mode_buf[1] != 0xFF) { + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAN_MODE, + fan_mode_buf, 2)); + } + } +} + +void HonClimate::clear_control_messages_queue_() { + while (!this->control_messages_queue_.empty()) + this->control_messages_queue_.pop(); } -void HonClimate::process_pending_action() { - switch (this->action_request_) { - case ActionRequest::START_SELF_CLEAN: - case ActionRequest::START_STERI_CLEAN: - // Will reset action with control message sending - this->set_phase(ProtocolPhases::SENDING_CONTROL); - break; +bool HonClimate::prepare_pending_action() { + switch (this->action_request_.value().action) { + case ActionRequest::START_SELF_CLEAN: { + uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; + memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); + hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + out_data->self_cleaning_status = 1; + out_data->steri_clean = 0; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + this->action_request_.value().message = haier_protocol::HaierMessage( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, + control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); + } + return true; + case ActionRequest::START_STERI_CLEAN: { + uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; + memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); + hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + out_data->self_cleaning_status = 0; + out_data->steri_clean = 1; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + this->action_request_.value().message = haier_protocol::HaierMessage( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, + control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); + } + return true; default: - HaierClimateBase::process_pending_action(); - break; + return HaierClimateBase::prepare_pending_action(); + } +} + +void HonClimate::process_protocol_reset() { + HaierClimateBase::process_protocol_reset(); +#ifdef USE_SENSOR + for (auto &sub_sensor : this->sub_sensors_) { + if ((sub_sensor != nullptr) && sub_sensor->has_state()) + sub_sensor->publish_state(NAN); + } +#endif // USE_SENSOR + this->got_valid_outdoor_temp_ = false; + this->hvac_hardware_info_.reset(); +} + +bool HonClimate::should_get_big_data_() { + if (this->big_data_sensors_ > 0) { + static uint8_t counter = 0; + counter = (counter + 1) % 3; + return counter == 1; } + return false; } } // namespace haier diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index cf566e3b8e0e..c4fae20a98ae 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -1,7 +1,13 @@ #pragma once #include +#ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#include "esphome/core/automation.h" #include "haier_base.h" namespace esphome { @@ -30,7 +36,51 @@ enum class CleaningState : uint8_t { STERI_CLEAN = 2, }; +enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; + class HonClimate : public HaierClimateBase { +#ifdef USE_SENSOR + public: + enum class SubSensorType { + // Used data based sensors + OUTDOOR_TEMPERATURE = 0, + HUMIDITY, + // Big data based sensors + INDOOR_COIL_TEMPERATURE, + OUTDOOR_COIL_TEMPERATURE, + OUTDOOR_DEFROST_TEMPERATURE, + OUTDOOR_IN_AIR_TEMPERATURE, + OUTDOOR_OUT_AIR_TEMPERATURE, + POWER, + COMPRESSOR_FREQUENCY, + COMPRESSOR_CURRENT, + EXPANSION_VALVE_OPEN_DEGREE, + SUB_SENSOR_TYPE_COUNT, + BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE, + }; + void set_sub_sensor(SubSensorType type, sensor::Sensor *sens); + + protected: + void update_sub_sensor_(SubSensorType type, float value); + sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr}; +#endif +#ifdef USE_BINARY_SENSOR + public: + enum class SubBinarySensorType { + OUTDOOR_FAN_STATUS = 0, + DEFROST_STATUS, + COMPRESSOR_STATUS, + INDOOR_FAN_STATUS, + FOUR_WAY_VALVE_STATUS, + INDOOR_ELECTRIC_HEATING_STATUS, + SUB_BINARY_SENSOR_TYPE_COUNT, + }; + void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens); + + protected: + void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value); + binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr}; +#endif public: HonClimate(); HonClimate(const HonClimate &) = delete; @@ -39,7 +89,6 @@ class HonClimate : public HaierClimateBase { void dump_config() override; void set_beeper_state(bool state); bool get_beeper_state() const; - void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor); AirflowVerticalDirection get_vertical_airflow() const; void set_vertical_airflow(AirflowVerticalDirection direction); AirflowHorizontalDirection get_horizontal_airflow() const; @@ -48,44 +97,84 @@ class HonClimate : public HaierClimateBase { CleaningState get_cleaning_status() const; void start_self_cleaning(); void start_steri_cleaning(); + void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; + void set_control_method(HonControlMethod method) { this->control_method_ = method; }; + void add_alarm_start_callback(std::function &&callback); + void add_alarm_end_callback(std::function &&callback); + float get_active_alarm_count() const { return this->active_alarm_count_; } protected: void set_handlers() override; void process_phase(std::chrono::steady_clock::time_point now) override; haier_protocol::HaierMessage get_control_message() override; - bool is_message_invalid(uint8_t message_type) override; - void process_pending_action() override; + haier_protocol::HaierMessage get_power_message(bool state) override; + bool prepare_pending_action() override; + void process_protocol_reset() override; + bool should_get_big_data_(); // Answers handlers - haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, + haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError get_management_information_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, - const uint8_t *data, size_t data_size); - haier_protocol::HandlerError get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); + haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, + size_t size); // Helper functions haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); - std::unique_ptr last_status_message_; + void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new); + void fill_control_messages_queue_(); + void clear_control_messages_queue_(); + + struct HardwareInfo { + std::string protocol_version_; + std::string software_version_; + std::string hardware_version_; + std::string device_name_; + bool functions_[5]; + }; + bool beeper_status_; CleaningState cleaning_status_; bool got_valid_outdoor_temp_; AirflowVerticalDirection vertical_direction_; AirflowHorizontalDirection horizontal_direction_; - bool hvac_hardware_info_available_; - std::string hvac_protocol_version_; - std::string hvac_software_version_; - std::string hvac_hardware_version_; - std::string hvac_device_name_; - bool hvac_functions_[5]; - bool &use_crc_; + esphome::optional hvac_hardware_info_; uint8_t active_alarms_[8]; - esphome::sensor::Sensor *outdoor_sensor_; + int extra_control_packet_bytes_; + HonControlMethod control_method_; + std::queue control_messages_queue_; + CallbackManager alarm_start_callback_{}; + CallbackManager alarm_end_callback_{}; + float active_alarm_count_{NAN}; + std::chrono::steady_clock::time_point last_alarm_request_; + int big_data_sensors_{0}; +}; + +class HaierAlarmStartTrigger : public Trigger { + public: + explicit HaierAlarmStartTrigger(HonClimate *parent) { + parent->add_alarm_start_callback( + [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); + } +}; + +class HaierAlarmEndTrigger : public Trigger { + public: + explicit HaierAlarmEndTrigger(HonClimate *parent) { + parent->add_alarm_end_callback( + [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); + } }; } // namespace haier diff --git a/esphome/components/haier/hon_packet.h b/esphome/components/haier/hon_packet.h index c6b32df200c7..bbca7bb653f2 100644 --- a/esphome/components/haier/hon_packet.h +++ b/esphome/components/haier/hon_packet.h @@ -35,24 +35,38 @@ enum class ConditioningMode : uint8_t { FAN = 0x06 }; +enum class DataParameters : uint8_t { + AC_POWER = 0x01, + SET_POINT = 0x02, + AC_MODE = 0x04, + FAN_MODE = 0x05, + USE_FAHRENHEIT = 0x07, + TEN_DEGREE = 0x0A, + HEALTH_MODE = 0x0B, + BEEPER_STATUS = 0x16, + LOCK_REMOTE = 0x17, + QUIET_MODE = 0x19, + FAST_MODE = 0x1A, +}; + enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; struct HaierPacketControl { // Control bytes starts here - // 10 + // 1 uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C) - // 11 + // 2 uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode uint8_t : 0; - // 12 + // 3 uint8_t fan_mode : 3; // See enum FanMode uint8_t special_mode : 2; // See enum SpecialMode uint8_t ac_mode : 3; // See enum ConditioningMode - // 13 + // 4 uint8_t : 8; - // 14 + // 5 uint8_t ten_degree : 1; // 10 degree status uint8_t display_status : 1; // If 0 disables AC's display uint8_t half_degree : 1; // Use half degree @@ -61,7 +75,7 @@ struct HaierPacketControl { uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius uint8_t : 1; uint8_t steri_clean : 1; - // 15 + // 6 uint8_t ac_power : 1; // Is ac on or off uint8_t health_mode : 1; // Health mode (negative ions) on or off uint8_t electric_heating_status : 1; // Electric heating status @@ -70,16 +84,16 @@ struct HaierPacketControl { uint8_t sleep_mode : 1; // Sleep mode uint8_t lock_remote : 1; // Disable remote uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command) - // 16 + // 7 uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%) - // 17 + // 8 uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode uint8_t : 3; uint8_t human_sensing_status : 2; // Human sensing status - // 18 + // 9 uint8_t change_filter : 1; // Filter need replacement uint8_t : 0; - // 19 + // 10 uint8_t fresh_air_status : 1; // Fresh air status uint8_t humidification_status : 1; // Humidification status uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status @@ -91,43 +105,67 @@ struct HaierPacketControl { }; struct HaierPacketSensors { - // 20 + // 11 uint8_t room_temperature; // 0.5°C step - // 21 + // 12 uint8_t room_humidity; // 0%-100% with 1% step - // 22 + // 13 uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C) - // 23 + // 14 uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) uint8_t : 1; uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only) - // 24 + // 15 uint8_t error_status; // See enum ErrorStatus - // 25 + // 16 uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) uint8_t : 3; uint8_t err_confirmation : 1; // If 1 clear error status - // 26 + // 17 uint16_t total_cleaning_time; // Cleaning cumulative time (1h step) - // 28 + // 19 uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) - // 30 + // 21 uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) - // 32 + // 23 uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step) - // 34 + // 25 uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step) - // 36 + // 27 uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step) }; -struct HaierStatus { - uint16_t subcommand; - HaierPacketControl control; - HaierPacketSensors sensors; +struct HaierPacketBigData { + // 29 + uint8_t power[2]; // AC power consumption (0W - 65535W, 1W step) + // 31 + uint8_t indoor_coil_temperature; // 0.5°C step, -20°C offset (0=-20°C) + // 32 + uint8_t outdoor_out_air_temperature; // 1°C step, -64°C offset (0=-64°C) + // 33 + uint8_t outdoor_coil_temperature; // 1°C step, -64°C offset (0=-64°C) + // 34 + uint8_t outdoor_in_air_temperature; // 1°C step, -64°C offset (0=-64°C) + // 35 + uint8_t outdoor_defrost_temperature; // 1°C step, -64°C offset (0=-64°C) + // 36 + uint8_t compressor_frequency; // 1Hz step, 0Hz - 127Hz + // 37 + uint8_t compressor_current[2]; // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF) + // 39 + uint8_t outdoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t defrost_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t : 0; + // 40 + uint8_t compressor_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t indoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t four_way_valve_status : 2; // 0 - off, 1 - on, 2 - information not available + uint8_t indoor_electric_heating_status : 2; // 0 - off, 1 - on, 2 - information not available + // 41 + uint8_t expansion_valve_open_degree[2]; // 0 - 4095 }; struct DeviceVersionAnswer { @@ -140,76 +178,6 @@ struct DeviceVersionAnswer { uint8_t functions[2]; }; -// In this section comments: -// - module is the ESP32 control module (communication module in Haier protocol document) -// - device is the conditioner control board (network appliances in Haier protocol document) -enum class FrameType : uint8_t { - CONTROL = 0x01, // Requests or sets one or multiple parameters (module <-> device, required) - STATUS = 0x02, // Contains one or multiple parameters values, usually answer to control frame (module <-> device, - // required) - INVALID = 0x03, // Communication error indication (module <-> device, required) - ALARM_STATUS = 0x04, // Alarm status report (module <-> device, interactive, required) - CONFIRM = 0x05, // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module - // <-> device, required) - REPORT = 0x06, // Report frame (module <-> device, interactive, required) - STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required) - SYSTEM_DOWNLINK = 0x11, // System downlink frame (module -> device, optional) - DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional) - SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional) - SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional) - DEVICE_QUERY = 0x15, // Device query frame (module <- device, optional) - DEVICE_QUERY_RESPONSE = 0x16, // Device query response frame (module -> device, optional) - GROUP_COMMAND = 0x60, // Group command frame (module -> device, interactive, optional) - GET_DEVICE_VERSION = 0x61, // Requests device version (module -> device, required) - GET_DEVICE_VERSION_RESPONSE = 0x62, // Device version answer (module <- device, required_ - GET_ALL_ADDRESSES = 0x67, // Requests all devices addresses (module -> device, interactive, optional) - GET_ALL_ADDRESSES_RESPONSE = - 0x68, // Answer to request of all devices addresses (module <- device , interactive, optional) - HANDSET_CHANGE_NOTIFICATION = 0x69, // Handset change notification frame (module <- device , interactive, optional) - GET_DEVICE_ID = 0x70, // Requests Device ID (module -> device, required) - GET_DEVICE_ID_RESPONSE = 0x71, // Response to device ID request (module <- device , required) - GET_ALARM_STATUS = 0x73, // Alarm status request (module -> device, required) - GET_ALARM_STATUS_RESPONSE = 0x74, // Response to alarm status request (module <- device, required) - GET_DEVICE_CONFIGURATION = 0x7C, // Requests device configuration (module -> device, interactive, required) - GET_DEVICE_CONFIGURATION_RESPONSE = - 0x7D, // Response to device configuration request (module <- device, interactive, required) - DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C, // Downlink transparent transmission (proxy data Haier cloud -> device) - // (module -> device, interactive, optional) - UPLINK_TRANSPARENT_TRANSMISSION = 0x8D, // Uplink transparent transmission (proxy data device -> Haier cloud) (module - // <- device, interactive, optional) - START_DEVICE_UPGRADE = 0xE1, // Initiate device OTA upgrade (module -> device, OTA required) - START_DEVICE_UPGRADE_RESPONSE = 0xE2, // Response to initiate device upgrade command (module <- device, OTA required) - GET_FIRMWARE_CONTENT = 0xE5, // Requests to send firmware (module <- device, OTA required) - GET_FIRMWARE_CONTENT_RESPONSE = - 0xE6, // Response to send firmware request (module -> device, OTA required) (multipacket?) - CHANGE_BAUD_RATE = 0xE7, // Requests to change port baud rate (module <- device, OTA required) - CHANGE_BAUD_RATE_RESPONSE = 0xE8, // Response to change port baud rate request (module -> device, OTA required) - GET_SUBBOARD_INFO = 0xE9, // Requests subboard information (module -> device, required) - GET_SUBBOARD_INFO_RESPONSE = 0xEA, // Response to subboard information request (module <- device, required) - GET_HARDWARE_INFO = 0xEB, // Requests information about device and subboard (module -> device, required) - GET_HARDWARE_INFO_RESPONSE = 0xEC, // Response to hardware information request (module <- device, required) - GET_UPGRADE_RESULT = 0xED, // Requests result of the firmware update (module <- device, OTA required) - GET_UPGRADE_RESULT_RESPONSE = 0xEF, // Response to firmware update results request (module -> device, OTA required) - GET_NETWORK_STATUS = 0xF0, // Requests network status (module <- device, interactive, optional) - GET_NETWORK_STATUS_RESPONSE = 0xF1, // Response to network status request (module -> device, interactive, optional) - START_WIFI_CONFIGURATION = 0xF2, // Starts WiFi configuration procedure (module <- device, interactive, required) - START_WIFI_CONFIGURATION_RESPONSE = - 0xF3, // Response to start WiFi configuration request (module -> device, interactive, required) - STOP_WIFI_CONFIGURATION = 0xF4, // Stop WiFi configuration procedure (module <- device, interactive, required) - STOP_WIFI_CONFIGURATION_RESPONSE = - 0xF5, // Response to stop WiFi configuration request (module -> device, interactive, required) - REPORT_NETWORK_STATUS = 0xF7, // Reports network status (module -> device, required) - CLEAR_CONFIGURATION = 0xF8, // Request to clear module configuration (module <- device, interactive, optional) - BIG_DATA_REPORT_CONFIGURATION = - 0xFA, // Configuration for autoreport device full status (module -> device, interactive, optional) - BIG_DATA_REPORT_CONFIGURATION_RESPONSE = - 0xFB, // Response to set big data configuration (module <- device, interactive, optional) - GET_MANAGEMENT_INFORMATION = 0xFC, // Request management information from device (module -> device, required) - GET_MANAGEMENT_INFORMATION_RESPONSE = - 0xFD, // Response to management information request (module <- device, required) - WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional) -}; - enum class SubcommandsControl : uint16_t { GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None) @@ -223,6 +191,62 @@ enum class SubcommandsControl : uint16_t { // content: all values like in status packet) }; +const std::string HON_ALARM_MESSAGES[] = { + "Outdoor module failure", + "Outdoor defrost sensor failure", + "Outdoor compressor exhaust sensor failure", + "Outdoor EEPROM abnormality", + "Indoor coil sensor failure", + "Indoor-outdoor communication failure", + "Power supply overvoltage protection", + "Communication failure between panel and indoor unit", + "Outdoor compressor overheat protection", + "Outdoor environmental sensor abnormality", + "Full water protection", + "Indoor EEPROM failure", + "Outdoor out air sensor failure", + "CBD and module communication failure", + "Indoor DC fan failure", + "Outdoor DC fan failure", + "Door switch failure", + "Dust filter needs cleaning reminder", + "Water shortage protection", + "Humidity sensor failure", + "Indoor temperature sensor failure", + "Manipulator limit failure", + "Indoor PM2.5 sensor failure", + "Outdoor PM2.5 sensor failure", + "Indoor heating overload/high load alarm", + "Outdoor AC current protection", + "Outdoor compressor operation abnormality", + "Outdoor DC current protection", + "Outdoor no-load failure", + "CT current abnormality", + "Indoor cooling freeze protection", + "High and low pressure protection", + "Compressor out air temperature is too high", + "Outdoor evaporator sensor failure", + "Outdoor cooling overload", + "Water pump drainage failure", + "Three-phase power supply failure", + "Four-way valve failure", + "External alarm/scraper flow switch failure", + "Temperature cutoff protection alarm", + "Different mode operation failure", + "Electronic expansion valve failure", + "Dual heat source sensor Tw failure", + "Communication failure with the wired controller", + "Indoor unit address duplication failure", + "50Hz zero crossing failure", + "Outdoor unit failure", + "Formaldehyde sensor failure", + "VOC sensor failure", + "CO2 sensor failure", + "Firewall failure", +}; + +constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]); + } // namespace hon_protocol } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/sensor/__init__.py b/esphome/components/haier/sensor/__init__.py new file mode 100644 index 000000000000..01f997baa570 --- /dev/null +++ b/esphome/components/haier/sensor/__init__.py @@ -0,0 +1,151 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_OUTDOOR_TEMPERATURE, + CONF_POWER, + CONF_HUMIDITY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_CURRENT_AC, + ICON_FLASH, + ICON_GAUGE, + ICON_HEATING_COIL, + ICON_PULSE, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + ICON_WEATHER_WINDY, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_PERCENT, + UNIT_WATT, +) +from ..climate import ( + CONF_HAIER_ID, + HonClimate, +) + +SensorTypeEnum = HonClimate.enum("SubSensorType", True) + +# Haier sensors +CONF_COMPRESSOR_CURRENT = "compressor_current" +CONF_COMPRESSOR_FREQUENCY = "compressor_frequency" +CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree" +CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature" +CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature" +CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature" +CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature" +CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature" + +# Additional icons +ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" + +SENSOR_TYPES = { + CONF_COMPRESSOR_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_PULSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_GAUGE, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_HUMIDITY: sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_HEATING_COIL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_HEATING_COIL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_SNOWFLAKE_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_WEATHER_WINDY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_WEATHER_WINDY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + CONF_POWER: sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), + } +).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_HAIER_ID]) + + for type, _ in SENSOR_TYPES.items(): + if conf := config.get(type): + sens = await sensor.new_sensor(conf) + sensor_type = getattr(SensorTypeEnum, type.upper()) + cg.add(paren.set_sub_sensor(sensor_type, sens)) diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index f29f840088d3..00590694d52c 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -12,21 +12,28 @@ namespace haier { static const char *const TAG = "haier.climate"; constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; +constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; +constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); +constexpr uint8_t INIT_REQUESTS_RETRY = 2; +constexpr std::chrono::milliseconds INIT_REQUESTS_RETRY_INTERVAL = std::chrono::milliseconds(2000); -Smartair2Climate::Smartair2Climate() - : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {} +Smartair2Climate::Smartair2Climate() { + last_status_message_ = std::unique_ptr(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]); +} -haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, +haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { haier_protocol::HandlerError result = - this->answer_preprocess_(request_type, (uint8_t) smartair2_protocol::FrameType::CONTROL, message_type, - (uint8_t) smartair2_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); + this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type, + haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); if (result == haier_protocol::HandlerError::HANDLER_OK) { result = this->process_status_message_(data, data_size); if (result != haier_protocol::HandlerError::HANDLER_OK) { ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + this->reset_phase_(); + this->action_request_.reset(); + this->force_send_control_ = false; } else { if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); @@ -34,36 +41,45 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, sizeof(smartair2_protocol::HaierPacketControl)); } - if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { - ESP_LOGI(TAG, "First HVAC status received"); - this->set_phase(ProtocolPhases::IDLE); - } else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { - this->set_phase(ProtocolPhases::IDLE); - } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { - this->set_phase(ProtocolPhases::IDLE); - this->set_force_send_control_(false); - if (this->hvac_settings_.valid) - this->hvac_settings_.reset(); + switch (this->protocol_phase_) { + case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: + ESP_LOGI(TAG, "First HVAC status received"); + this->set_phase(ProtocolPhases::IDLE); + break; + case ProtocolPhases::SENDING_ACTION_COMMAND: + // Do nothing, phase will be changed in process_phase + break; + case ProtocolPhases::SENDING_STATUS_REQUEST: + this->set_phase(ProtocolPhases::IDLE); + break; + case ProtocolPhases::SENDING_CONTROL: + this->set_phase(ProtocolPhases::IDLE); + this->force_send_control_ = false; + if (this->current_hvac_settings_.valid) + this->current_hvac_settings_.reset(); + break; + default: + break; } } return result; } else { - this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + this->action_request_.reset(); + this->force_send_control_ = false; + this->reset_phase_(); return result; } } -haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type, - uint8_t message_type, - const uint8_t *data, - size_t data_size) { - if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION) +haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_( + haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, + size_t data_size) { + if (request_type != haier_protocol::FrameType::GET_DEVICE_VERSION) return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; - if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_) + if (ProtocolPhases::SENDING_INIT_1 != this->protocol_phase_) return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; // Invalid packet is expected answer - if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && + if ((message_type == haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && ((data[37] & 0x04) != 0)) { ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " "instead of smartAir2"); @@ -72,58 +88,35 @@ haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler return haier_protocol::HandlerError::HANDLER_OK; } -haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type, - uint8_t message_type, - const uint8_t *data, - size_t data_size) { - haier_protocol::HandlerError result = this->answer_preprocess_( - request_type, (uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, - (uint8_t) smartair2_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); - this->set_phase(ProtocolPhases::IDLE); - return result; -} - -haier_protocol::HandlerError Smartair2Climate::initial_messages_timeout_handler_(uint8_t message_type) { +haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cycle_for_init_( + haier_protocol::FrameType message_type) { if (this->protocol_phase_ >= ProtocolPhases::IDLE) return HaierClimateBase::timeout_default_handler_(message_type); - this->timeouts_counter_++; - ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type, - (int) this->protocol_phase_, this->timeouts_counter_); - if (this->timeouts_counter_ >= 3) { - ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); - if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) - new_phase = ProtocolPhases::SENDING_INIT_1; - this->set_phase(new_phase); - } else { - // Returning to the previous state to try again - this->set_phase((ProtocolPhases) ((int) this->protocol_phase_ - 1)); - } + ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type, + phase_to_string_(this->protocol_phase_)); + ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); + if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) + new_phase = ProtocolPhases::SENDING_INIT_1; + this->set_phase(new_phase); return haier_protocol::HandlerError::HANDLER_OK; } void Smartair2Climate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( - (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), + haier_protocol::FrameType::GET_DEVICE_VERSION, std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (smartair2_protocol::FrameType::CONTROL), + haier_protocol::FrameType::CONTROL, std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( - (uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), + haier_protocol::FrameType::REPORT_NETWORK_STATUS, std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - this->haier_protocol_.set_timeout_handler( - (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID), - std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); - this->haier_protocol_.set_timeout_handler( - (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), - std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); - this->haier_protocol_.set_timeout_handler( - (uint8_t) (smartair2_protocol::FrameType::CONTROL), - std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); + this->haier_protocol_.set_default_timeout_handler( + std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_, this, std::placeholders::_1)); } void Smartair2Climate::dump_config() { @@ -134,9 +127,7 @@ void Smartair2Climate::dump_config() { void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { switch (this->protocol_phase_) { case ProtocolPhases::SENDING_INIT_1: - if (this->can_send_message() && - (((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) || - ((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) { + if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { // Indicate device capabilities: // bit 0 - if 1 module support interactive mode // bit 1 - if 1 module support controller-device mode @@ -145,92 +136,68 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) // bit 4..bit 15 - not used uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( - (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, - sizeof(module_capabilities)); - this->send_message_(DEVICE_VERSION_REQUEST, false); - this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); + haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); + this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL); } break; case ProtocolPhases::SENDING_INIT_2: - case ProtocolPhases::WAITING_INIT_2_ANSWER: this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); break; case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, - 0x4D01); - this->send_message_(STATUS_REQUEST, false); + static const haier_protocol::HaierMessage STATUS_REQUEST(haier_protocol::FrameType::CONTROL, 0x4D01); + if (this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) { + this->send_message_(STATUS_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL); + } else { + this->send_message_(STATUS_REQUEST, this->use_crc_); + } this->last_status_request_ = now; - this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); } break; #ifdef USE_WIFI case ProtocolPhases::SENDING_SIGNAL_LEVEL: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - this->send_message_( - this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false); + this->send_message_(this->get_wifi_signal_message_(), this->use_crc_); this->last_signal_request_ = now; - this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); } break; - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: - break; #else case ProtocolPhases::SENDING_SIGNAL_LEVEL: - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: this->set_phase(ProtocolPhases::IDLE); break; #endif case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: - case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); break; - case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: - case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: + case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: this->set_phase(ProtocolPhases::SENDING_INIT_1); break; + case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + this->set_phase(ProtocolPhases::IDLE); + break; case ProtocolPhases::SENDING_CONTROL: - if (this->first_control_attempt_) { - this->control_request_timestamp_ = now; - this->first_control_attempt_ = false; - } - if (this->is_control_message_timeout_exceeded_(now)) { - ESP_LOGW(TAG, "Sending control packet timeout!"); - this->set_force_send_control_(false); - if (this->hvac_settings_.valid) - this->hvac_settings_.reset(); - this->forced_request_status_ = true; - this->forced_publish_ = true; - this->set_phase(ProtocolPhases::IDLE); - } else if (this->can_send_message() && this->is_control_message_interval_exceeded_( - now)) // Using CONTROL_MESSAGES_INTERVAL_MS to speedup requests - { - haier_protocol::HaierMessage control_message = get_control_message(); - this->send_message_(control_message, false); - ESP_LOGI(TAG, "Control packet sent"); - this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); + if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { + ESP_LOGI(TAG, "Sending control packet"); + this->send_message_(get_control_message(), this->use_crc_, CONTROL_MESSAGE_RETRIES, + CONTROL_MESSAGE_RETRIES_INTERVAL); } break; - case ProtocolPhases::SENDING_POWER_ON_COMMAND: - case ProtocolPhases::SENDING_POWER_OFF_COMMAND: - if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - haier_protocol::HaierMessage power_cmd( - (uint8_t) smartair2_protocol::FrameType::CONTROL, - this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND ? 0x4D02 : 0x4D03); - this->send_message_(power_cmd, false); - this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND - ? ProtocolPhases::WAITING_POWER_ON_ANSWER - : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + case ProtocolPhases::SENDING_ACTION_COMMAND: + if (this->action_request_.has_value()) { + if (this->action_request_.value().message.has_value()) { + this->send_message_(this->action_request_.value().message.value(), this->use_crc_); + this->action_request_.value().message.reset(); + } else { + // Message already sent, reseting request and return to idle + this->action_request_.reset(); + this->set_phase(ProtocolPhases::IDLE); + } + } else { + ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!"); + this->set_phase(ProtocolPhases::IDLE); } break; - case ProtocolPhases::WAITING_INIT_1_ANSWER: - case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: - case ProtocolPhases::WAITING_STATUS_ANSWER: - case ProtocolPhases::WAITING_CONTROL_ANSWER: - case ProtocolPhases::WAITING_POWER_ON_ANSWER: - case ProtocolPhases::WAITING_POWER_OFF_ANSWER: - break; case ProtocolPhases::IDLE: { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); @@ -245,55 +212,55 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) } break; default: // Shouldn't get here -#if (HAIER_LOG_LEVEL > 4) ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); -#else - ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); -#endif this->set_phase(ProtocolPhases::SENDING_INIT_1); break; } } +haier_protocol::HaierMessage Smartair2Climate::get_power_message(bool state) { + if (state) { + static haier_protocol::HaierMessage power_on_message(haier_protocol::FrameType::CONTROL, 0x4D02); + return power_on_message; + } else { + static haier_protocol::HaierMessage power_off_message(haier_protocol::FrameType::CONTROL, 0x4D03); + return power_off_message; + } +} + haier_protocol::HaierMessage Smartair2Climate::get_control_message() { uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)]; memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl)); smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer; out_data->cntrl = 0; - if (this->hvac_settings_.valid) { - HvacSettings climate_control; - climate_control = this->hvac_settings_; + if (this->current_hvac_settings_.valid) { + HvacSettings &climate_control = this->current_hvac_settings_; if (climate_control.mode.has_value()) { switch (climate_control.mode.value()) { case CLIMATE_MODE_OFF: out_data->ac_power = 0; break; - case CLIMATE_MODE_HEAT_COOL: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; out_data->fan_mode = this->other_modes_fan_speed_; break; - case CLIMATE_MODE_HEAT: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT; out_data->fan_mode = this->other_modes_fan_speed_; break; - case CLIMATE_MODE_DRY: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY; out_data->fan_mode = this->other_modes_fan_speed_; break; - case CLIMATE_MODE_FAN_ONLY: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN; out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode break; - case CLIMATE_MODE_COOL: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL; @@ -327,32 +294,49 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { } // Set swing mode if (climate_control.swing_mode.has_value()) { - switch (climate_control.swing_mode.value()) { - case CLIMATE_SWING_OFF: - out_data->use_swing_bits = 0; - out_data->swing_both = 0; - break; - case CLIMATE_SWING_VERTICAL: - out_data->swing_both = 0; - out_data->vertical_swing = 1; - out_data->horizontal_swing = 0; - break; - case CLIMATE_SWING_HORIZONTAL: - out_data->swing_both = 0; - out_data->vertical_swing = 0; - out_data->horizontal_swing = 1; - break; - case CLIMATE_SWING_BOTH: - out_data->swing_both = 1; - out_data->use_swing_bits = 0; - out_data->vertical_swing = 0; - out_data->horizontal_swing = 0; - break; + if (this->use_alternative_swing_control_) { + switch (climate_control.swing_mode.value()) { + case CLIMATE_SWING_OFF: + out_data->swing_mode = 0; + break; + case CLIMATE_SWING_VERTICAL: + out_data->swing_mode = 1; + break; + case CLIMATE_SWING_HORIZONTAL: + out_data->swing_mode = 2; + break; + case CLIMATE_SWING_BOTH: + out_data->swing_mode = 3; + break; + } + } else { + switch (climate_control.swing_mode.value()) { + case CLIMATE_SWING_OFF: + out_data->use_swing_bits = 0; + out_data->swing_mode = 0; + break; + case CLIMATE_SWING_VERTICAL: + out_data->swing_mode = 0; + out_data->vertical_swing = 1; + out_data->horizontal_swing = 0; + break; + case CLIMATE_SWING_HORIZONTAL: + out_data->swing_mode = 0; + out_data->vertical_swing = 0; + out_data->horizontal_swing = 1; + break; + case CLIMATE_SWING_BOTH: + out_data->swing_mode = 1; + out_data->use_swing_bits = 0; + out_data->vertical_swing = 0; + out_data->horizontal_swing = 0; + break; + } } } if (climate_control.target_temperature.has_value()) { float target_temp = climate_control.target_temperature.value(); - out_data->set_point = target_temp - 16; // set the temperature with offset 16 + out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; } if (out_data->ac_power == 0) { @@ -362,19 +346,29 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { } else if (climate_control.preset.has_value()) { switch (climate_control.preset.value()) { case CLIMATE_PRESET_NONE: + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 0; break; case CLIMATE_PRESET_BOOST: + out_data->ten_degree = 0; out_data->turbo_mode = 1; out_data->quiet_mode = 0; break; case CLIMATE_PRESET_COMFORT: + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 1; break; + case CLIMATE_PRESET_AWAY: + // Only allowed in heat mode + out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; + out_data->turbo_mode = 0; + out_data->quiet_mode = 0; + break; default: ESP_LOGE("Control", "Unsupported preset"); + out_data->ten_degree = 0; out_data->turbo_mode = 0; out_data->quiet_mode = 0; break; @@ -383,7 +377,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { } out_data->display_status = this->display_status_ ? 0 : 1; out_data->health_mode = this->health_mode_ ? 1 : 0; - return haier_protocol::HaierMessage((uint8_t) smartair2_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, + return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, sizeof(smartair2_protocol::HaierPacketControl)); } @@ -400,6 +394,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin this->preset = CLIMATE_PRESET_BOOST; } else if (packet.control.quiet_mode != 0) { this->preset = CLIMATE_PRESET_COMFORT; + } else if (packet.control.ten_degree != 0) { + this->preset = CLIMATE_PRESET_AWAY; } else { this->preset = CLIMATE_PRESET_NONE; } @@ -459,13 +455,19 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin // Do something only if display status changed if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display - this->set_force_send_control_(true); + this->force_send_control_ = true; } else { this->display_status_ = disp_status; } } } } + { + // Health mode + bool old_health_mode = this->health_mode_; + this->health_mode_ = packet.control.health_mode == 1; + should_publish = should_publish || (old_health_mode != this->health_mode_); + } { // Climate mode ClimateMode old_mode = this->mode; @@ -493,70 +495,57 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin } should_publish = should_publish || (old_mode != this->mode); } - { - // Health mode - bool old_health_mode = this->health_mode_; - this->health_mode_ = packet.control.health_mode == 1; - should_publish = should_publish || (old_health_mode != this->health_mode_); - } { // Swing mode ClimateSwingMode old_swing_mode = this->swing_mode; - if (packet.control.swing_both == 0) { - if (packet.control.vertical_swing != 0) { - this->swing_mode = CLIMATE_SWING_VERTICAL; - } else if (packet.control.horizontal_swing != 0) { - this->swing_mode = CLIMATE_SWING_HORIZONTAL; - } else { - this->swing_mode = CLIMATE_SWING_OFF; + if (this->use_alternative_swing_control_) { + switch (packet.control.swing_mode) { + case 1: + this->swing_mode = CLIMATE_SWING_VERTICAL; + break; + case 2: + this->swing_mode = CLIMATE_SWING_HORIZONTAL; + break; + case 3: + this->swing_mode = CLIMATE_SWING_BOTH; + break; + default: + this->swing_mode = CLIMATE_SWING_OFF; + break; } } else { - swing_mode = CLIMATE_SWING_BOTH; + if (packet.control.swing_mode == 0) { + if (packet.control.vertical_swing != 0) { + this->swing_mode = CLIMATE_SWING_VERTICAL; + } else if (packet.control.horizontal_swing != 0) { + this->swing_mode = CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = CLIMATE_SWING_OFF; + } + } else { + swing_mode = CLIMATE_SWING_BOTH; + } } should_publish = should_publish || (old_swing_mode != this->swing_mode); } this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); - if (this->forced_publish_ || should_publish) { -#if (HAIER_LOG_LEVEL > 4) - std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); -#endif + if (should_publish) { this->publish_state(); -#if (HAIER_LOG_LEVEL > 4) - ESP_LOGV(TAG, "Publish delay: %lld ms", - std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - - _publish_start) - .count()); -#endif - this->forced_publish_ = false; } if (should_publish) { ESP_LOGI(TAG, "HVAC values changed"); } - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "HVAC Mode = 0x%X", packet.control.ac_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Fan speed Status = 0x%X", packet.control.fan_mode); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Vertical Swing Status = 0x%X", packet.control.vertical_swing); - esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, - "Set Point Status = 0x%X", packet.control.set_point); + int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG; + esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode); + esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); + esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing); + esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point); return haier_protocol::HandlerError::HANDLER_OK; } -bool Smartair2Climate::is_message_invalid(uint8_t message_type) { - return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; -} - -void Smartair2Climate::set_phase(HaierClimateBase::ProtocolPhases phase) { - int old_phase = (int) this->protocol_phase_; - int new_phase = (int) phase; - int min_p = std::min(old_phase, new_phase); - int max_p = std::max(old_phase, new_phase); - if ((min_p % 2 != 0) || (max_p - min_p > 1)) - this->timeouts_counter_ = 0; - HaierClimateBase::set_phase(phase); +void Smartair2Climate::set_alternative_swing_control(bool swing_control) { + this->use_alternative_swing_control_ = swing_control; } } // namespace haier diff --git a/esphome/components/haier/smartair2_climate.h b/esphome/components/haier/smartair2_climate.h index f173b1074955..6914d8a1fbbe 100644 --- a/esphome/components/haier/smartair2_climate.h +++ b/esphome/components/haier/smartair2_climate.h @@ -13,27 +13,27 @@ class Smartair2Climate : public HaierClimateBase { Smartair2Climate &operator=(const Smartair2Climate &) = delete; ~Smartair2Climate(); void dump_config() override; + void set_alternative_swing_control(bool swing_control); protected: void set_handlers() override; void process_phase(std::chrono::steady_clock::time_point now) override; + haier_protocol::HaierMessage get_power_message(bool state) override; haier_protocol::HaierMessage get_control_message() override; - bool is_message_invalid(uint8_t message_type) override; - void set_phase(HaierClimateBase::ProtocolPhases phase) override; - // Answer and timeout handlers - haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, + // Answer handlers + haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, + haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, + haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size); - haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, - const uint8_t *data, size_t data_size); - haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type); + haier_protocol::HandlerError messages_timeout_handler_with_cycle_for_init_(haier_protocol::FrameType message_type); // Helper functions haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); - std::unique_ptr last_status_message_; - unsigned int timeouts_counter_; + bool use_alternative_swing_control_; }; } // namespace haier diff --git a/esphome/components/haier/smartair2_packet.h b/esphome/components/haier/smartair2_packet.h index f791c21af2bc..22570ff048ac 100644 --- a/esphome/components/haier/smartair2_packet.h +++ b/esphome/components/haier/smartair2_packet.h @@ -41,8 +41,9 @@ struct HaierPacketControl { // 24 uint8_t : 8; // 25 - uint8_t swing_both; // If 1 - swing both direction, if 0 - horizontal_swing and vertical_swing define - // vertical/horizontal/off + uint8_t swing_mode; // In normal mode: If 1 - swing both direction, if 0 - horizontal_swing and + // vertical_swing define vertical/horizontal/off + // In alternative mode: 0 - off, 01 - vertical, 02 - horizontal, 03 - both // 26 uint8_t : 3; uint8_t use_fahrenheit : 1; @@ -82,19 +83,6 @@ struct HaierStatus { HaierPacketControl control; }; -enum class FrameType : uint8_t { - CONTROL = 0x01, - STATUS = 0x02, - INVALID = 0x03, - CONFIRM = 0x05, - GET_DEVICE_VERSION = 0x61, - GET_DEVICE_VERSION_RESPONSE = 0x62, - GET_DEVICE_ID = 0x70, - GET_DEVICE_ID_RESPONSE = 0x71, - REPORT_NETWORK_STATUS = 0xF7, - NO_COMMAND = 0xFF, -}; - } // namespace smartair2_protocol } // namespace haier } // namespace esphome diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index d7c8d544f942..66b72f9e3e9f 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -6,6 +6,9 @@ CONF_CURRENT, CONF_FREQUENCY, CONF_ID, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, CONF_REACTIVE_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, @@ -24,9 +27,6 @@ UNIT_WATT, ) -CONF_PHASE_A = "phase_a" -CONF_PHASE_B = "phase_b" -CONF_PHASE_C = "phase_c" CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_GENERATION_TIME = "total_generation_time" diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py index 421883a1ffff..424e944290e9 100644 --- a/esphome/components/hbridge/fan/__init__.py +++ b/esphome/components/hbridge/fan/__init__.py @@ -3,6 +3,7 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import fan, output +from esphome.components.fan import validate_preset_modes from esphome.const import ( CONF_ID, CONF_DECAY_MODE, @@ -10,6 +11,7 @@ CONF_PIN_A, CONF_PIN_B, CONF_ENABLE_PIN, + CONF_PRESET_MODES, ) from .. import hbridge_ns @@ -28,7 +30,6 @@ # Actions BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action) - CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( { cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan), @@ -39,6 +40,7 @@ ), cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, } ).extend(cv.COMPONENT_SCHEMA) @@ -69,3 +71,6 @@ async def to_code(config): if CONF_ENABLE_PIN in config: enable_pin = await cg.get_variable(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable_pin)) + + if CONF_PRESET_MODES in config: + cg.add(var.set_preset_modes(config[CONF_PRESET_MODES])) diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 44cf5ae0496e..605a9d4ef399 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -33,7 +33,12 @@ void HBridgeFan::setup() { restore->apply(*this); this->write_state_(); } + + // Construct traits + this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); } + void HBridgeFan::dump_config() { LOG_FAN("", "H-Bridge Fan", this); if (this->decay_mode_ == DECAY_MODE_SLOW) { @@ -42,9 +47,7 @@ void HBridgeFan::dump_config() { ESP_LOGCONFIG(TAG, " Decay Mode: Fast"); } } -fan::FanTraits HBridgeFan::get_traits() { - return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); -} + void HBridgeFan::control(const fan::FanCall &call) { if (call.get_state().has_value()) this->state = *call.get_state(); @@ -54,10 +57,12 @@ 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->write_state_(); this->publish_state(); } + void HBridgeFan::write_state_() { float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; if (speed == 0.0f) { // off means idle diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 4389b97ccb5f..4234fccae3e0 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/automation.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" @@ -20,10 +22,11 @@ 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 setup() override; void dump_config() override; - fan::FanTraits get_traits() override; + fan::FanTraits get_traits() override { return this->traits_; } fan::FanCall brake(); @@ -34,6 +37,8 @@ class HBridgeFan : public Component, public fan::Fan { output::BinaryOutput *oscillating_{nullptr}; int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; + fan::FanTraits traits_; + std::set preset_modes_{}; void control(const fan::FanCall &call) override; void write_state_(); diff --git a/esphome/components/he60r/__init__.py b/esphome/components/he60r/__init__.py new file mode 100644 index 000000000000..c58ce8a01e84 --- /dev/null +++ b/esphome/components/he60r/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/he60r/cover.py b/esphome/components/he60r/cover.py new file mode 100644 index 000000000000..fd4c746016cd --- /dev/null +++ b/esphome/components/he60r/cover.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover, uart +from esphome.const import ( + CONF_CLOSE_DURATION, + CONF_ID, + CONF_OPEN_DURATION, +) + +he60r_ns = cg.esphome_ns.namespace("he60r") +HE60rCover = he60r_ns.class_("HE60rCover", cover.Cover, cg.Component) + +CONFIG_SCHEMA = ( + cover.COVER_SCHEMA.extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(HE60rCover), + cv.Optional( + CONF_OPEN_DURATION, default="15s" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_CLOSE_DURATION, default="15s" + ): cv.positive_time_period_milliseconds, + } + ) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "he60r", + baud_rate=1200, + require_tx=True, + require_rx=True, + data_bits=8, + parity="EVEN", + stop_bits=1, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await cover.register_cover(var, config) + await uart.register_uart_device(var, config) + + cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) + cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) diff --git a/esphome/components/he60r/he60r.cpp b/esphome/components/he60r/he60r.cpp new file mode 100644 index 000000000000..d6e6122b1b54 --- /dev/null +++ b/esphome/components/he60r/he60r.cpp @@ -0,0 +1,265 @@ +#include "he60r.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace he60r { + +static const char *const TAG = "he60r.cover"; +static const uint8_t QUERY_BYTE = 0x38; +static const uint8_t TOGGLE_BYTE = 0x30; + +using namespace esphome::cover; + +void HE60rCover::setup() { + auto restore = this->restore_state_(); + + if (restore.has_value()) { + restore->apply(this); + this->publish_state(false); + } else { + // if no other information, assume half open + this->position = 0.5f; + } + this->current_operation = COVER_OPERATION_IDLE; + this->last_recompute_time_ = this->start_dir_time_ = millis(); + this->set_interval(300, [this]() { this->update_(); }); +} + +CoverTraits HE60rCover::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_stop(true); + traits.set_supports_position(true); + traits.set_supports_toggle(true); + traits.set_is_assumed_state(false); + return traits; +} + +void HE60rCover::dump_config() { + LOG_COVER("", "HE60R Cover", this); + this->check_uart_settings(1200, 1, uart::UART_CONFIG_PARITY_EVEN, 8); + ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); + ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); + auto restore = this->restore_state_(); + if (restore.has_value()) + ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f)); +} + +void HE60rCover::endstop_reached_(CoverOperation operation) { + const uint32_t now = millis(); + + this->set_current_operation_(COVER_OPERATION_IDLE); + auto new_position = operation == COVER_OPERATION_OPENING ? COVER_OPEN : COVER_CLOSED; + if (new_position != this->position || this->current_operation != COVER_OPERATION_IDLE) { + this->position = new_position; + this->current_operation = COVER_OPERATION_IDLE; + if (this->last_command_ == operation) { + float dur = (now - this->start_dir_time_) / 1e3f; + ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(), + operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur); + } + this->publish_state(); + } +} + +void HE60rCover::set_current_operation_(cover::CoverOperation operation) { + if (this->current_operation != operation) { + this->current_operation = operation; + if (operation != COVER_OPERATION_IDLE) + this->last_recompute_time_ = millis(); + this->publish_state(); + } +} + +void HE60rCover::process_rx_(uint8_t data) { + ESP_LOGV(TAG, "Process RX data %X", data); + if (!this->query_seen_) { + this->query_seen_ = data == QUERY_BYTE; + if (!this->query_seen_) + ESP_LOGD(TAG, "RX Byte %02X", data); + return; + } + switch (data) { + case 0xB5: // at closed endstop, jammed? + case 0xF5: // at closed endstop, jammed? + case 0x55: // at closed endstop + this->next_direction_ = COVER_OPERATION_OPENING; + this->endstop_reached_(COVER_OPERATION_CLOSING); + break; + + case 0x52: // at opened endstop + this->next_direction_ = COVER_OPERATION_CLOSING; + this->endstop_reached_(COVER_OPERATION_OPENING); + break; + + case 0x51: // travelling up after encountering obstacle + case 0x01: // travelling up + case 0x11: // travelling up, triggered by remote + this->set_current_operation_(COVER_OPERATION_OPENING); + this->next_direction_ = COVER_OPERATION_IDLE; + break; + + case 0x44: // travelling down + case 0x14: // travelling down, triggered by remote + this->next_direction_ = COVER_OPERATION_IDLE; + this->set_current_operation_(COVER_OPERATION_CLOSING); + break; + + case 0x86: // Stopped, jammed? + case 0x16: // stopped midway while opening, by remote + case 0x06: // stopped midway while opening + this->next_direction_ = COVER_OPERATION_CLOSING; + this->set_current_operation_(COVER_OPERATION_IDLE); + break; + + case 0x10: // stopped midway while closing, by remote + case 0x00: // stopped midway while closing + this->next_direction_ = COVER_OPERATION_OPENING; + this->set_current_operation_(COVER_OPERATION_IDLE); + break; + + default: + break; + } +} + +void HE60rCover::update_() { + if (toggles_needed_ != 0) { + if ((this->counter_++ & 0x3) == 0) { + toggles_needed_--; + ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_); + this->write_byte(TOGGLE_BYTE); + } else { + this->write_byte(QUERY_BYTE); + } + } else { + this->write_byte(QUERY_BYTE); + this->counter_ = 0; + } + if (this->current_operation != COVER_OPERATION_IDLE) { + this->recompute_position_(); + + // if we initiated the move, check if we reached the target position + if (this->last_command_ != COVER_OPERATION_IDLE) { + if (this->is_at_target_()) { + this->start_direction_(COVER_OPERATION_IDLE); + } + } + } +} + +void HE60rCover::loop() { + uint8_t data; + + while (this->available() > 0) { + if (this->read_byte(&data)) { + this->process_rx_(data); + } + } +} + +void HE60rCover::control(const CoverCall &call) { + if (call.get_stop()) { + this->start_direction_(COVER_OPERATION_IDLE); + } else if (call.get_toggle().has_value()) { + // toggle action logic: OPEN - STOP - CLOSE + if (this->last_command_ != COVER_OPERATION_IDLE) { + this->start_direction_(COVER_OPERATION_IDLE); + } else { + this->toggles_needed_++; + } + } else if (call.get_position().has_value()) { + // go to position action + auto pos = *call.get_position(); + // are we at the target? + if (pos == this->position) { + this->start_direction_(COVER_OPERATION_IDLE); + } else { + this->target_position_ = pos; + this->start_direction_(pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING); + } + } +} + +/** + * Check if the cover has reached or passed the target position. This is used only + * for partial open/close requests - endstops are used for full open/close. + * @return True if the cover has reached or passed its target position. For full open/close target always return false. + */ +bool HE60rCover::is_at_target_() const { + // equality of floats is fraught with peril - this is reliable since the values are 0.0 or 1.0 which are + // exactly representable. + if (this->target_position_ == COVER_OPEN || this->target_position_ == COVER_CLOSED) + return false; + // aiming for an intermediate position - exact comparison here will not work and we need to allow for overshoot + switch (this->last_command_) { + case COVER_OPERATION_OPENING: + return this->position >= this->target_position_; + case COVER_OPERATION_CLOSING: + return this->position <= this->target_position_; + case COVER_OPERATION_IDLE: + return this->current_operation == COVER_OPERATION_IDLE; + default: + return true; + } +} +void HE60rCover::start_direction_(CoverOperation dir) { + this->last_command_ = dir; + if (this->current_operation == dir) + return; + ESP_LOGD(TAG, "'%s' - Direction '%s' requested.", this->name_.c_str(), + dir == COVER_OPERATION_OPENING ? "OPEN" + : dir == COVER_OPERATION_CLOSING ? "CLOSE" + : "STOP"); + + if (dir == this->next_direction_) { + // either moving and needs to stop, or stopped and will move correctly on one trigger + this->toggles_needed_ = 1; + } else { + if (this->current_operation == COVER_OPERATION_IDLE) { + // if stopped, but will go the wrong way, need 3 triggers. + this->toggles_needed_ = 3; + } else { + // just stop and reverse + this->toggles_needed_ = 2; + } + ESP_LOGD(TAG, "'%s' - Reversing direction.", this->name_.c_str()); + } + this->start_dir_time_ = millis(); +} + +void HE60rCover::recompute_position_() { + if (this->current_operation == COVER_OPERATION_IDLE) + return; + + const uint32_t now = millis(); + float dir; + float action_dur; + + switch (this->current_operation) { + case COVER_OPERATION_OPENING: + dir = 1.0f; + action_dur = this->open_duration_; + break; + case COVER_OPERATION_CLOSING: + dir = -1.0f; + action_dur = this->close_duration_; + break; + default: + return; + } + + if (now > this->last_recompute_time_) { + auto diff = now - last_recompute_time_; + auto delta = dir * diff / action_dur; + // make sure our guesstimate never reaches full open or close. + this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f); + ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta, + this->position); + this->last_recompute_time_ = now; + this->publish_state(); + } +} + +} // namespace he60r +} // namespace esphome diff --git a/esphome/components/he60r/he60r.h b/esphome/components/he60r/he60r.h new file mode 100644 index 000000000000..624b61fc6598 --- /dev/null +++ b/esphome/components/he60r/he60r.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace he60r { + +class HE60rCover : public cover::Cover, 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; }; + + void set_open_duration(uint32_t duration) { this->open_duration_ = duration; } + void set_close_duration(uint32_t duration) { this->close_duration_ = duration; } + + cover::CoverTraits get_traits() override; + + protected: + void update_(); + void control(const cover::CoverCall &call) override; + bool is_at_target_() const; + void start_direction_(cover::CoverOperation dir); + void update_operation_(cover::CoverOperation dir); + void endstop_reached_(cover::CoverOperation operation); + void recompute_position_(); + void set_current_operation_(cover::CoverOperation operation); + void process_rx_(uint8_t data); + + uint32_t open_duration_{0}; + uint32_t close_duration_{0}; + uint32_t toggles_needed_{0}; + cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE}; + cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE}; + uint32_t last_recompute_time_{0}; + uint32_t start_dir_time_{0}; + float target_position_{0}; + bool query_seen_{}; + uint8_t counter_{}; +}; + +} // namespace he60r +} // namespace esphome diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index a043b4a61b4b..8af4ca590f8e 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -119,4 +119,4 @@ def to_code(config): cg.add_library("tonia/HeatpumpIR", "1.0.23") if CORE.is_esp8266 or CORE.is_esp32: - cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12") + cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4") diff --git a/esphome/components/heatpumpir/ir_sender_esphome.h b/esphome/components/heatpumpir/ir_sender_esphome.h index 7546d990ea3f..944d0e859c06 100644 --- a/esphome/components/heatpumpir/ir_sender_esphome.h +++ b/esphome/components/heatpumpir/ir_sender_esphome.h @@ -3,7 +3,6 @@ #ifdef USE_ARDUINO #include "esphome/components/remote_base/remote_base.h" -#include "esphome/components/remote_transmitter/remote_transmitter.h" #include // arduino-heatpump library namespace esphome { @@ -11,14 +10,13 @@ namespace heatpumpir { class IRSenderESPHome : public IRSender { public: - IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter) - : IRSender(0), transmit_(transmitter->transmit()){}; + IRSenderESPHome(remote_base::RemoteTransmitterBase *transmitter) : IRSender(0), transmit_(transmitter->transmit()){}; void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) void space(int space_length) override; void mark(int mark_length) override; protected: - remote_transmitter::RemoteTransmitterComponent::TransmitCall transmit_; + remote_base::RemoteTransmitterBase::TransmitCall transmit_; }; } // namespace heatpumpir diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index ecdaa07ab2ac..14e83f60e180 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -38,7 +38,7 @@ void HLW8012Component::dump_config() { 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 %u", this->change_mode_every_); + ESP_LOGCONFIG(TAG, " Change measurement mode every %" PRIu32, this->change_mode_every_); ESP_LOGCONFIG(TAG, " Current resistor: %.1f mΩ", this->current_resistor_ * 1000.0f); ESP_LOGCONFIG(TAG, " Voltage Divider: %.1f", this->voltage_divider_); LOG_UPDATE_INTERVAL(this) @@ -96,7 +96,7 @@ void HLW8012Component::update() { this->energy_sensor_->publish_state(energy); } - if (this->change_mode_at_++ == this->change_mode_every_) { + if (this->change_mode_every_ != 0 && this->change_mode_at_++ == this->change_mode_every_) { this->current_mode_ = !this->current_mode_; ESP_LOGV(TAG, "Changing mode to %s mode", this->current_mode_ ? "CURRENT" : "VOLTAGE"); this->change_mode_at_ = 0; diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index adb49ffb66e3..312391f533a6 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -5,6 +5,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/pulse_counter/pulse_counter_sensor.h" +#include + namespace esphome { namespace hlw8012 { diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 033cccc3d4c4..2687edaca2c6 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -79,8 +79,9 @@ cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, cv.Optional(CONF_MODEL, default="HLW8012"): cv.enum(MODELS, upper=True), - cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All( - cv.uint32_t, cv.Range(min=1) + cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.Any( + "never", + cv.All(cv.uint32_t, cv.Range(min=1)), ), cv.Optional(CONF_INITIAL_MODE, default=CONF_VOLTAGE): cv.one_of( *INITIAL_MODES, lower=True @@ -114,6 +115,10 @@ async def to_code(config): cg.add(var.set_energy_sensor(sens)) cg.add(var.set_current_resistor(config[CONF_CURRENT_RESISTOR])) cg.add(var.set_voltage_divider(config[CONF_VOLTAGE_DIVIDER])) - cg.add(var.set_change_mode_every(config[CONF_CHANGE_MODE_EVERY])) cg.add(var.set_initial_mode(INITIAL_MODES[config[CONF_INITIAL_MODE]])) cg.add(var.set_sensor_model(config[CONF_MODEL])) + + interval = config[CONF_CHANGE_MODE_EVERY] + if interval == "never": + interval = 0 + cg.add(var.set_change_mode_every(interval)) diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 6c830f9bad4b..c1b47826a2df 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,6 +1,7 @@ #pragma once #include "abstract_aqi_calculator.h" +// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf namespace esphome { namespace hm3301 { @@ -15,14 +16,16 @@ class AQICalculator : public AbstractAQICalculator { } protected: - static const int AMOUNT_OF_LEVELS = 6; + static const int AMOUNT_OF_LEVELS = 7; - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; + int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, + {201, 300}, {301, 400}, {401, 500}}; - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 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 pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, - {255, 354}, {355, 424}, {425, 604}}; + int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354}, + {355, 424}, {425, 504}, {505, 604}}; int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int grid_index = get_grid_index_(value, array); diff --git a/esphome/components/hmc5883l/hmc5883l.cpp b/esphome/components/hmc5883l/hmc5883l.cpp index de3903d7e20a..24f4b3f8f147 100644 --- a/esphome/components/hmc5883l/hmc5883l.cpp +++ b/esphome/components/hmc5883l/hmc5883l.cpp @@ -1,5 +1,6 @@ #include "hmc5883l.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace hmc5883l { @@ -31,6 +32,10 @@ void HMC5883LComponent::setup() { return; } + if (this->get_update_interval() < App.get_loop_interval()) { + high_freq_.start(); + } + if (id[0] != 0x48 || id[1] != 0x34 || id[2] != 0x33) { this->error_code_ = ID_REGISTERS; this->mark_failed(); diff --git a/esphome/components/hmc5883l/hmc5883l.h b/esphome/components/hmc5883l/hmc5883l.h index 3481f45dc8d7..06fba2af9dc9 100644 --- a/esphome/components/hmc5883l/hmc5883l.h +++ b/esphome/components/hmc5883l/hmc5883l.h @@ -63,6 +63,7 @@ class HMC5883LComponent : public PollingComponent, public i2c::I2CDevice { COMMUNICATION_FAILED, ID_REGISTERS, } error_code_; + HighFrequencyLoopRequester high_freq_; }; } // namespace hmc5883l diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 26e8e2b60c4b..f2decea1502b 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -3,6 +3,10 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ADDRESS, + CONF_FIELD_STRENGTH_X, + CONF_FIELD_STRENGTH_Y, + CONF_FIELD_STRENGTH_Z, + CONF_HEADING, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, @@ -18,10 +22,6 @@ hmc5883l_ns = cg.esphome_ns.namespace("hmc5883l") -CONF_FIELD_STRENGTH_X = "field_strength_x" -CONF_FIELD_STRENGTH_Y = "field_strength_y" -CONF_FIELD_STRENGTH_Z = "field_strength_z" -CONF_HEADING = "heading" HMC5883LComponent = hmc5883l_ns.class_( "HMC5883LComponent", cg.PollingComponent, i2c.I2CDevice diff --git a/esphome/components/honeywell_hih_i2c/__init__.py b/esphome/components/honeywell_hih_i2c/__init__.py new file mode 100644 index 000000000000..8d13fcb15287 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/__init__.py @@ -0,0 +1,3 @@ +"""Support for Honeywell HumidIcon HIH""" + +CODEOWNERS = ["@Benichou34"] diff --git a/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp new file mode 100644 index 000000000000..64d5ddb541d5 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp @@ -0,0 +1,97 @@ +// Honeywell HumidIcon I2C Sensors +// https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/humidity-with-temperature-sensors/common/documents/sps-siot-i2c-comms-humidicon-tn-009061-2-en-ciid-142171.pdf +// + +#include "honeywell_hih.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace honeywell_hih_i2c { + +static const char *const TAG = "honeywell_hih.i2c"; + +static const uint8_t REQUEST_CMD[1] = {0x00}; // Measurement Request Format +static const uint16_t MAX_COUNT = 0x3FFE; // 2^14 - 2 + +void HoneywellHIComponent::read_sensor_data_() { + uint8_t data[4]; + + if (this->read(data, sizeof(data)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return; + } + + const uint16_t raw_humidity = (static_cast(data[0] & 0x3F) << 8) | data[1]; + float humidity = (static_cast(raw_humidity) / MAX_COUNT) * 100; + + const uint16_t raw_temperature = (static_cast(data[2]) << 6) | (data[3] >> 2); + float temperature = (static_cast(raw_temperature) / MAX_COUNT) * 165 - 40; + + ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(humidity); +} + +void HoneywellHIComponent::start_measurement_() { + if (this->write(REQUEST_CMD, sizeof(REQUEST_CMD)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return; + } + + this->measurement_running_ = true; +} + +bool HoneywellHIComponent::is_measurement_ready_() { + uint8_t data[1]; + + if (this->read(data, sizeof(data)) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + this->mark_failed(); + return false; + } + + // Check status bits + return ((data[0] & 0xC0) == 0x00); +} + +void HoneywellHIComponent::measurement_timeout_() { + ESP_LOGE(TAG, "Honeywell HIH Timeout!"); + this->measurement_running_ = false; + this->mark_failed(); +} + +void HoneywellHIComponent::update() { + ESP_LOGV(TAG, "Update Honeywell HIH Sensor"); + + this->start_measurement_(); + // The measurement cycle duration is typically 36.65 ms for temperature and humidity readings. + this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout_(); }); +} + +void HoneywellHIComponent::loop() { + if (this->measurement_running_ && this->is_measurement_ready_()) { + this->measurement_running_ = false; + this->cancel_timeout("meas_timeout"); + this->read_sensor_data_(); + } +} + +void HoneywellHIComponent::dump_config() { + ESP_LOGD(TAG, "Honeywell HIH:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with Honeywell HIH failed!"); + } + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_UPDATE_INTERVAL(this); +} + +float HoneywellHIComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace honeywell_hih_i2c +} // namespace esphome diff --git a/esphome/components/honeywell_hih_i2c/honeywell_hih.h b/esphome/components/honeywell_hih_i2c/honeywell_hih.h new file mode 100644 index 000000000000..4457eab1da1c --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/honeywell_hih.h @@ -0,0 +1,34 @@ +// Honeywell HumidIcon I2C Sensors +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace honeywell_hih_i2c { + +class HoneywellHIComponent : public PollingComponent, public i2c::I2CDevice { + public: + void dump_config() override; + float get_setup_priority() const override; + void loop() override; + void update() override; + + 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; } + + protected: + bool measurement_running_{false}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + + private: + void read_sensor_data_(); + void start_measurement_(); + bool is_measurement_ready_(); + void measurement_timeout_(); +}; + +} // namespace honeywell_hih_i2c +} // namespace esphome diff --git a/esphome/components/honeywell_hih_i2c/sensor.py b/esphome/components/honeywell_hih_i2c/sensor.py new file mode 100644 index 000000000000..f5a6ad2398c6 --- /dev/null +++ b/esphome/components/honeywell_hih_i2c/sensor.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, +) + +DEPENDENCIES = ["i2c"] + +honeywell_hih_ns = cg.esphome_ns.namespace("honeywell_hih_i2c") +HONEYWELLHIComponent = honeywell_hih_ns.class_( + "HoneywellHIComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HONEYWELLHIComponent), + 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_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x27)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/honeywellabp2_i2c/__init__.py b/esphome/components/honeywellabp2_i2c/__init__.py new file mode 100644 index 000000000000..29a910eca98f --- /dev/null +++ b/esphome/components/honeywellabp2_i2c/__init__.py @@ -0,0 +1,3 @@ +"""Support for Honeywell ABP2""" + +CODEOWNERS = ["@jpfaff"] diff --git a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp new file mode 100644 index 000000000000..e2910032ccbf --- /dev/null +++ b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp @@ -0,0 +1,108 @@ +#include "honeywellabp2.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace honeywellabp2_i2c { + +static const uint8_t STATUS_BIT_POWER = 6; +static const uint8_t STATUS_BIT_BUSY = 5; +static const uint8_t STATUS_BIT_ERROR = 2; +static const uint8_t STATUS_MATH_SAT = 0; + +static const char *const TAG = "honeywellabp2"; + +void HONEYWELLABP2Sensor::read_sensor_data() { + if (this->read(raw_data_, 7) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with ABP2 failed!"); + this->mark_failed(); + return; + } + float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts + float temp_counts = encode_uint24(raw_data_[4], raw_data_[5], raw_data_[6]); // calculate digital temperature counts + + this->last_pressure_ = (((press_counts - this->min_count_) / (this->max_count_ - this->min_count_)) * + (this->max_pressure_ - this->min_pressure_)) + + this->min_pressure_; + this->last_temperature_ = (temp_counts * 200 / 16777215) - 50; +} + +void HONEYWELLABP2Sensor::start_measurement() { + if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with ABP2 failed!"); + this->mark_failed(); + return; + } + this->measurement_running_ = true; +} + +bool HONEYWELLABP2Sensor::is_measurement_ready() { + if (this->read(raw_data_, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Communication with ABP2 failed!"); + this->mark_failed(); + return false; + } + if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) { + return false; + } + this->measurement_running_ = false; + return true; +} + +void HONEYWELLABP2Sensor::measurement_timeout() { + ESP_LOGE(TAG, "Timeout!"); + this->measurement_running_ = false; + this->mark_failed(); +} + +float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; } + +float HONEYWELLABP2Sensor::get_temperature() { return this->last_temperature_; } + +void HONEYWELLABP2Sensor::loop() { + if (this->measurement_running_) { + if (this->is_measurement_ready()) { + this->cancel_timeout("meas_timeout"); + + this->read_sensor_data(); + if (pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(this->get_pressure()); + } + if (temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(this->get_temperature()); + } + } + } +} + +void HONEYWELLABP2Sensor::update() { + ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor"); + + this->start_measurement(); + this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); }); +} + +void HONEYWELLABP2Sensor::dump_config() { + ESP_LOGCONFIG(TAG, " Min Pressure Range: %0.1f", this->min_pressure_); + ESP_LOGCONFIG(TAG, " Max Pressure Range: %0.1f", this->max_pressure_); + if (this->transfer_function_ == ABP2_TRANS_FUNC_A) { + ESP_LOGCONFIG(TAG, " Transfer function A"); + } else { + ESP_LOGCONFIG(TAG, " Transfer function B"); + } + LOG_UPDATE_INTERVAL(this); +} + +void HONEYWELLABP2Sensor::set_transfer_function(ABP2TRANFERFUNCTION transfer_function) { + this->transfer_function_ = transfer_function; + if (this->transfer_function_ == ABP2_TRANS_FUNC_B) { + this->max_count_ = this->max_count_b_; + this->min_count_ = this->min_count_b_; + } else { + this->max_count_ = this->max_count_a_; + this->min_count_ = this->min_count_a_; + } +} + +} // namespace honeywellabp2_i2c +} // namespace esphome diff --git a/esphome/components/honeywellabp2_i2c/honeywellabp2.h b/esphome/components/honeywellabp2_i2c/honeywellabp2.h new file mode 100644 index 000000000000..bc81524ac2f1 --- /dev/null +++ b/esphome/components/honeywellabp2_i2c/honeywellabp2.h @@ -0,0 +1,60 @@ +// for Honeywell ABP sensor +// adapting code from https://github.com/vwls/Honeywell_pressure_sensors +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace honeywellabp2_i2c { + +enum ABP2TRANFERFUNCTION { ABP2_TRANS_FUNC_A = 0, ABP2_TRANS_FUNC_B = 1 }; + +class HONEYWELLABP2Sensor : public PollingComponent, public i2c::I2CDevice { + public: + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }; + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }; + void loop() override; + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + void dump_config() override; + + void read_sensor_data(); + void start_measurement(); + bool is_measurement_ready(); + void measurement_timeout(); + + float get_pressure(); + float get_temperature(); + + void set_min_pressure(float min_pressure) { this->min_pressure_ = min_pressure; }; + void set_max_pressure(float max_pressure) { this->max_pressure_ = max_pressure; }; + void set_transfer_function(ABP2TRANFERFUNCTION transfer_function); + + protected: + float min_pressure_ = 0.0; + float max_pressure_ = 0.0; + ABP2TRANFERFUNCTION transfer_function_ = ABP2_TRANS_FUNC_A; + + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + + const float max_count_a_ = 15099494.4; // (90% of 2^24 counts or 0xE66666) + const float min_count_a_ = 1677721.6; // (10% of 2^24 counts or 0x19999A) + const float max_count_b_ = 11744051.2; // (70% of 2^24 counts or 0xB33333) + const float min_count_b_ = 5033164.8; // (30% of 2^24 counts or 0x4CCCCC) + + float max_count_; + float min_count_; + bool measurement_running_ = false; + + uint8_t raw_data_[7]; // holds output data + uint8_t i2c_cmd_[3] = {0xAA, 0x00, 0x00}; // command to be sent + float last_pressure_; + float last_temperature_; +}; + +} // namespace honeywellabp2_i2c +} // namespace esphome diff --git a/esphome/components/honeywellabp2_i2c/sensor.py b/esphome/components/honeywellabp2_i2c/sensor.py new file mode 100644 index 000000000000..c38a380127bd --- /dev/null +++ b/esphome/components/honeywellabp2_i2c/sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.components import i2c +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["i2c"] + +honeywellabp2_ns = cg.esphome_ns.namespace("honeywellabp2_i2c") + +CONF_MIN_PRESSURE = "min_pressure" +CONF_MAX_PRESSURE = "max_pressure" +TRANSFER_FUNCTION = "transfer_function" +ABP2TRANFERFUNCTION = honeywellabp2_ns.enum("ABP2TRANFERFUNCTION") +TRANS_FUNC_OPTIONS = { + "A": ABP2TRANFERFUNCTION.ABP2_TRANS_FUNC_A, + "B": ABP2TRANFERFUNCTION.ABP2_TRANS_FUNC_B, +} + +HONEYWELLABP2Sensor = honeywellabp2_ns.class_( + "HONEYWELLABP2Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HONEYWELLABP2Sensor), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement="Pa", + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Required(CONF_MIN_PRESSURE): cv.float_, + cv.Required(CONF_MAX_PRESSURE): cv.float_, + cv.Required(TRANSFER_FUNCTION): cv.enum(TRANS_FUNC_OPTIONS), + } + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x28)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_min_pressure(pressure_config[CONF_MIN_PRESSURE])) + cg.add(var.set_max_pressure(pressure_config[CONF_MAX_PRESSURE])) + cg.add(var.set_transfer_function(pressure_config[TRANSFER_FUNCTION])) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index 46f763d25547..3bd3b6b1728d 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -3,8 +3,11 @@ KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_HOST, + CONF_MAC_ADDRESS, ) from esphome.core import CORE +from esphome.helpers import IS_MACOS import esphome.config_validation as cv import esphome.codegen as cg @@ -13,26 +16,34 @@ # force import gpio to register pin schema from .gpio import host_pin_to_code # noqa - CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["network"] def set_core_data(config): CORE.data[KEY_HOST] = {} - CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host" + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_HOST CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0) return config CONFIG_SCHEMA = cv.All( - cv.Schema({}), + cv.Schema( + { + cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address, + } + ), set_core_data, ) async def to_code(config): cg.add_build_flag("-DUSE_HOST") + cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) + cg.add_build_flag("-std=c++17") + cg.add_build_flag("-lsodium") + if IS_MACOS: + cg.add_build_flag("-L/opt/homebrew/lib") cg.add_define("ESPHOME_BOARD", "host") cg.add_platformio_option("platform", "platformio/native") diff --git a/esphome/components/host/gpio.py b/esphome/components/host/gpio.py index d523d28ee501..180919de4f39 100644 --- a/esphome/components/host/gpio.py +++ b/esphome/components/host/gpio.py @@ -17,10 +17,8 @@ from .const import host_ns - _LOGGER = logging.getLogger(__name__) - HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin) @@ -45,21 +43,10 @@ def validate_gpio_pin(value): return _translate_pin(value) -HOST_PIN_SCHEMA = cv.All( - { - cv.GenerateID(): cv.declare_id(HostGPIOPin), - cv.Required(CONF_NUMBER): validate_gpio_pin, - cv.Optional(CONF_MODE, default={}): cv.Schema( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, - } - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - }, +HOST_PIN_SCHEMA = pins.gpio_base_schema( + HostGPIOPin, + validate_gpio_pin, + modes=[CONF_INPUT, CONF_OUTPUT, CONF_OPEN_DRAIN, CONF_PULLUP, CONF_PULLDOWN], ) diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index bf45893e40ca..7b939cdebb3c 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -1,36 +1,87 @@ #ifdef USE_HOST +#include +#include #include "preferences.h" -#include -#include "esphome/core/preferences.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" -#include "esphome/core/defines.h" +#include "esphome/core/application.h" namespace esphome { namespace host { +namespace fs = std::filesystem; static const char *const TAG = "host.preferences"; -class HostPreferences : public ESPPreferences { - public: - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; } +void HostPreferences::setup_() { + if (this->setup_complete_) + return; + this->filename_.append(getenv("HOME")); + this->filename_.append("/.esphome"); + this->filename_.append("/prefs"); + fs::create_directories(this->filename_); + this->filename_.append("/"); + this->filename_.append(App.get_name()); + this->filename_.append(".prefs"); + FILE *fp = fopen(this->filename_.c_str(), "rb"); + if (fp != nullptr) { + while (!feof((fp))) { + uint32_t key; + uint8_t len; + if (fread(&key, sizeof(key), 1, fp) != 1) + break; + if (fread(&len, sizeof(len), 1, fp) != 1) + break; + uint8_t data[len]; + if (fread(data, sizeof(uint8_t), len, fp) != len) + break; + std::vector vec(data, data + len); + this->data[key] = vec; + } + fclose(fp); + } + this->setup_complete_ = true; +} + +bool HostPreferences::sync() { + this->setup_(); + FILE *fp = fopen(this->filename_.c_str(), "wb"); + std::map>::iterator it; - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; } + for (it = this->data.begin(); it != this->data.end(); ++it) { + fwrite(&it->first, sizeof(uint32_t), 1, fp); + uint8_t len = it->second.size(); + fwrite(&len, sizeof(len), 1, fp); + fwrite(it->second.data(), sizeof(uint8_t), it->second.size(), fp); + } + fclose(fp); + return true; +} + +bool HostPreferences::reset() { + host_preferences->data.clear(); + return true; +} - bool sync() override { return true; } - bool reset() override { return true; } +ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { + auto backend = new HostPreferenceBackend(type); + return ESPPreferenceObject(backend); }; void setup_preferences() { auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory) + host_preferences = pref; global_preferences = pref; } +bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { + return host_preferences->save(this->key_, data, len); +} + +bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); } + +HostPreferences *host_preferences; } // namespace host ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_HOST diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h index 7462360ec3f8..6707366517a7 100644 --- a/esphome/components/host/preferences.h +++ b/esphome/components/host/preferences.h @@ -2,10 +2,63 @@ #ifdef USE_HOST +#include "esphome/core/preferences.h" +#include + namespace esphome { namespace host { +class HostPreferenceBackend : public ESPPreferenceBackend { + public: + explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; } + + bool save(const uint8_t *data, size_t len) override; + bool load(uint8_t *data, size_t len) override; + + protected: + uint32_t key_{}; +}; + +class HostPreferences : public ESPPreferences { + public: + bool sync() override; + bool reset() override; + + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override; + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + return make_preference(length, type, false); + } + + bool save(uint32_t key, const uint8_t *data, size_t len) { + if (len > 255) + return false; + this->setup_(); + std::vector vec(data, data + len); + this->data[key] = vec; + return true; + } + + bool load(uint32_t key, uint8_t *data, size_t len) { + if (len > 255) + return false; + this->setup_(); + if (this->data.count(key) == 0) + return false; + auto vec = this->data[key]; + if (vec.size() != len) + return false; + memcpy(data, vec.data(), len); + return true; + } + + protected: + void setup_(); + bool setup_complete_{}; + std::string filename_{}; + std::map> data{}; +}; void setup_preferences(); +extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace host } // namespace esphome diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index bd1c82c96b2a..b56e96badcff 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -1,9 +1,10 @@ // Official Datasheet: -// https://www.maxbotix.com/documents/HRXL-MaxSonar-WR_Datasheet.pdf +// HRXL: https://www.maxbotix.com/documents/HRXL-MaxSonar-WR_Datasheet.pdf +// XL: https://www.maxbotix.com/documents/XL-MaxSonar-WR_Datasheet.pdf // // This implementation is designed to work with the TTL Versions of the -// MaxBotix HRXL MaxSonar WR sensor series. The sensor's TTL Pin (5) should be -// wired to one of the ESP's input pins and configured as uart rx_pin. +// MaxBotix HRXL and XL MaxSonar WR sensor series. The sensor's TTL Pin (5) +// should be wired to one of the ESP's input pins and configured as uart rx_pin. #include "hrxl_maxsonar_wr.h" #include "esphome/core/log.h" @@ -17,8 +18,10 @@ static const uint8_t ASCII_NBSP = 0xFF; static const int MAX_DATA_LENGTH_BYTES = 6; /** - * The sensor outputs something like "R1234\r" at a fixed rate of 6 Hz. Where - * 1234 means a distance of 1,234 m. + * HRXL sensors output the format "R1234\r" at 6Hz + * The 1234 means 1234mm + * XL sensors output the format "R123\r" at 5 to 10Hz + * The 123 means 123cm */ void HrxlMaxsonarWrComponent::loop() { uint8_t data; @@ -42,9 +45,17 @@ void HrxlMaxsonarWrComponent::check_buffer_() { if (this->buffer_.back() == static_cast(ASCII_CR) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) { ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.c_str()); - if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && - this->buffer_.back() == static_cast(ASCII_CR)) { - int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0); + size_t rpos = this->buffer_.find(static_cast(ASCII_CR)); + + if (this->buffer_.length() <= MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && rpos != std::string::npos) { + std::string distance = this->buffer_.substr(1, rpos - 1); + int millimeters = parse_number(distance).value_or(0); + + // XL reports in cm instead of mm and reports 3 digits instead of 4 + if (distance.length() == 3) { + millimeters = millimeters * 10; + } + float meters = float(millimeters) / 1000.0; ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); this->publish_state(meters); diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 0958c0768328..b885de18e6c7 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -80,8 +80,6 @@ template class HttpRequestSendAction : public Action { TEMPLATABLE_VALUE(std::string, url) TEMPLATABLE_VALUE(const char *, method) TEMPLATABLE_VALUE(std::string, body) - TEMPLATABLE_VALUE(const char *, useragent) - TEMPLATABLE_VALUE(uint16_t, timeout) void add_header(const char *key, TemplatableValue value) { this->headers_.insert({key, value}); } @@ -105,25 +103,18 @@ template class HttpRequestSendAction : public Action { auto f = std::bind(&HttpRequestSendAction::encode_json_func_, this, x..., std::placeholders::_1); this->parent_->set_body(json::build_json(f)); } - if (this->useragent_.has_value()) { - this->parent_->set_useragent(this->useragent_.value(x...)); - } - if (this->timeout_.has_value()) { - this->parent_->set_timeout(this->timeout_.value(x...)); - } - if (!this->headers_.empty()) { - std::list
headers; - for (const auto &item : this->headers_) { - auto val = item.second; - Header header; - header.name = item.first; - header.value = val.value(x...); - headers.push_back(header); - } - this->parent_->set_headers(headers); + std::list
headers; + for (const auto &item : this->headers_) { + auto val = item.second; + Header header; + header.name = item.first; + header.value = val.value(x...); + headers.push_back(header); } + this->parent_->set_headers(headers); this->parent_->send(this->response_triggers_); this->parent_->close(); + this->parent_->set_body(""); } protected: diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index a38ec7301942..411d1e1d6aa1 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -11,7 +11,11 @@ static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3; static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5; +static const uint8_t HTU21D_WRITERHT_REG_CMD = 0xE6; /**< Write RH/T User Register 1 */ static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; +static const uint8_t HTU21D_WRITEHEATER_REG_CMD = 0x51; /**< Write Heater Control Register */ +static const uint8_t HTU21D_READHEATER_REG_CMD = 0x11; /**< Read Heater Control Register */ +static const uint8_t HTU21D_REG_HTRE_BIT = 0x02; /**< Control Register Heater Bit */ void HTU21DComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up HTU21D..."); @@ -35,41 +39,117 @@ void HTU21DComponent::dump_config() { LOG_SENSOR(" ", "Humidity", this->humidity_); } void HTU21DComponent::update() { - uint16_t raw_temperature; if (this->write(&HTU21D_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } - delay(50); // NOLINT - if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + + // According to the datasheet sht21 temperature readings can take up to 85ms + this->set_timeout(85, [this]() { + uint16_t raw_temperature; + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); + + float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; + + ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature); + + if (this->temperature_ != nullptr) + this->temperature_->publish_state(temperature); + this->status_clear_warning(); + + if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + this->set_timeout(50, [this]() { + uint16_t raw_humidity; + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); + + float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; + + ESP_LOGD(TAG, "Got Humidity=%.1f%%", humidity); + + if (this->humidity_ != nullptr) + this->humidity_->publish_state(humidity); + + int8_t heater_level; + + // HTU21D does have a heater module but does not have heater level + // Setting heater level to 1 in case the heater is ON + if (this->sensor_model_ == HTU21D_SENSOR_MODEL_HTU21D) { + if (this->is_heater_enabled()) { + heater_level = 1; + } else { + heater_level = 0; + } + } else { + heater_level = this->get_heater_level(); + } + + ESP_LOGD(TAG, "Heater Level=%d", heater_level); + + if (this->heater_ != nullptr) + this->heater_->publish_state(heater_level); + this->status_clear_warning(); + }); + }); +} + +bool HTU21DComponent::is_heater_enabled() { + uint8_t raw_heater; + if (this->read_register(HTU21D_REGISTER_STATUS, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { this->status_set_warning(); - return; + return false; } - raw_temperature = i2c::i2ctohs(raw_temperature); - - float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; + raw_heater = i2c::i2ctohs(raw_heater); + return (bool) (((raw_heater) >> (HTU21D_REG_HTRE_BIT)) & 0x01); +} - uint16_t raw_humidity; - if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { +void HTU21DComponent::set_heater(bool status) { + uint8_t raw_heater; + if (this->read_register(HTU21D_REGISTER_STATUS, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { this->status_set_warning(); return; } - delay(50); // NOLINT - if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + raw_heater = i2c::i2ctohs(raw_heater); + if (status) { + raw_heater |= (1 << (HTU21D_REG_HTRE_BIT)); + } else { + raw_heater &= ~(1 << (HTU21D_REG_HTRE_BIT)); + } + + if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } - raw_humidity = i2c::i2ctohs(raw_humidity); +} - float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; - ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); +void HTU21DComponent::set_heater_level(uint8_t level) { + if (this->write_register(HTU21D_WRITEHEATER_REG_CMD, &level, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } +} - if (this->temperature_ != nullptr) - this->temperature_->publish_state(temperature); - if (this->humidity_ != nullptr) - this->humidity_->publish_state(humidity); - this->status_clear_warning(); +int8_t HTU21DComponent::get_heater_level() { + int8_t raw_heater; + if (this->read_register(HTU21D_READHEATER_REG_CMD, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return 0; + } + raw_heater = i2c::i2ctohs(raw_heater); + return raw_heater; } + float HTU21DComponent::get_setup_priority() const { return setup_priority::DATA; } } // namespace htu21d diff --git a/esphome/components/htu21d/htu21d.h b/esphome/components/htu21d/htu21d.h index a408f06d01e6..8533875d43cf 100644 --- a/esphome/components/htu21d/htu21d.h +++ b/esphome/components/htu21d/htu21d.h @@ -3,26 +3,60 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/core/automation.h" namespace esphome { namespace htu21d { +enum HTU21DSensorModels { HTU21D_SENSOR_MODEL_HTU21D = 0, HTU21D_SENSOR_MODEL_SI7021, HTU21D_SENSOR_MODEL_SHT21 }; + class HTU21DComponent : public PollingComponent, public i2c::I2CDevice { public: void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_heater(sensor::Sensor *heater) { heater_ = heater; } /// Setup (reset) the sensor and check connection. void setup() override; void dump_config() override; + void set_sensor_model(HTU21DSensorModels sensor_model) { sensor_model_ = sensor_model; } /// Update the sensor values (temperature+humidity). void update() override; + bool is_heater_enabled(); + void set_heater(bool status); + void set_heater_level(uint8_t level); + int8_t get_heater_level(); + float get_setup_priority() const override; protected: sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *heater_{nullptr}; + HTU21DSensorModels sensor_model_{HTU21D_SENSOR_MODEL_HTU21D}; +}; + +template class SetHeaterLevelAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, level) + + void play(Ts... x) override { + auto level = this->level_.value(x...); + + this->parent_->set_heater_level(level); + } +}; + +template class SetHeaterAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(bool, status) + + void play(Ts... x) override { + auto status = this->status_.value(x...); + + this->parent_->set_heater(status); + } }; } // namespace htu21d diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 2ed318f1c9fd..bf0b9a23fbf5 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -1,15 +1,21 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome import automation from esphome.const import ( CONF_HUMIDITY, CONF_ID, + CONF_MODEL, CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, + CONF_HEATER, + UNIT_EMPTY, + CONF_LEVEL, + CONF_STATUS, ) DEPENDENCIES = ["i2c"] @@ -18,6 +24,15 @@ HTU21DComponent = htu21d_ns.class_( "HTU21DComponent", cg.PollingComponent, i2c.I2CDevice ) +SetHeaterLevelAction = htu21d_ns.class_("SetHeaterLevelAction", automation.Action) +SetHeaterAction = htu21d_ns.class_("SetHeaterAction", automation.Action) +HTU21DSensorModels = htu21d_ns.enum("HTU21DSensorModels") + +MODELS = { + "HTU21D": HTU21DSensorModels.HTU21D_SENSOR_MODEL_HTU21D, + "SI7021": HTU21DSensorModels.HTU21D_SENSOR_MODEL_SI7021, + "SHT21": HTU21DSensorModels.HTU21D_SENSOR_MODEL_SHT21, +} CONFIG_SCHEMA = ( cv.Schema( @@ -35,6 +50,12 @@ device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_HEATER): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MODEL, default="HTU21D"): cv.enum(MODELS, upper=True), } ) .extend(cv.polling_component_schema("60s")) @@ -54,3 +75,47 @@ async def to_code(config): if CONF_HUMIDITY in config: sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity(sens)) + + if CONF_HEATER in config: + sens = await sensor.new_sensor(config[CONF_HEATER]) + cg.add(var.set_heater(sens)) + + cg.add(var.set_sensor_model(config[CONF_MODEL])) + + +@automation.register_action( + "htu21d.set_heater_level", + SetHeaterLevelAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HTU21DComponent), + cv.Required(CONF_LEVEL): cv.templatable(cv.int_), + }, + key=CONF_LEVEL, + ), +) +async def set_heater_level_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + level_ = await cg.templatable(config[CONF_LEVEL], args, int) + cg.add(var.set_level(level_)) + return var + + +@automation.register_action( + "htu21d.set_heater", + SetHeaterAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HTU21DComponent), + cv.Required(CONF_STATUS): cv.templatable(cv.boolean), + }, + key=CONF_STATUS, + ), +) +async def set_heater_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + status_ = await cg.templatable(config[CONF_LEVEL], args, bool) + cg.add(var.set_status(status_)) + return var diff --git a/esphome/components/htu31d/__init__.py b/esphome/components/htu31d/__init__.py new file mode 100644 index 000000000000..039693cb30fc --- /dev/null +++ b/esphome/components/htu31d/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@betterengineering"] diff --git a/esphome/components/htu31d/htu31d.cpp b/esphome/components/htu31d/htu31d.cpp new file mode 100644 index 000000000000..928250a5b24a --- /dev/null +++ b/esphome/components/htu31d/htu31d.cpp @@ -0,0 +1,269 @@ +/* + * This file contains source code derived from Adafruit_HTU31D which is under + * the BSD license: + * Written by Limor Fried/Ladyada for Adafruit Industries. + * BSD license, all text above must be included in any redistribution. + * + * Modifications made by Mark Spicer. + */ + +#include "htu31d.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace htu31d { + +/** Logging prefix */ +static const char *const TAG = "htu31d"; + +/** Default I2C address for the HTU31D. */ +static const uint8_t HTU31D_DEFAULT_I2CADDR = 0x40; + +/** Read temperature and humidity. */ +static const uint8_t HTU31D_READTEMPHUM = 0x00; + +/** Start a conversion! */ +static const uint8_t HTU31D_CONVERSION = 0x40; + +/** Read serial number command. */ +static const uint8_t HTU31D_READSERIAL = 0x0A; + +/** Enable heater */ +static const uint8_t HTU31D_HEATERON = 0x04; + +/** Disable heater */ +static const uint8_t HTU31D_HEATEROFF = 0x02; + +/** Reset command. */ +static const uint8_t HTU31D_RESET = 0x1E; + +/** Diagnostics command. */ +static const uint8_t HTU31D_DIAGNOSTICS = 0x08; + +/** + * Computes a CRC result for the provided input. + * + * @returns the computed CRC result for the provided input + */ +uint8_t compute_crc(uint32_t value) { + uint32_t polynom = 0x98800000; // x^8 + x^5 + x^4 + 1 + uint32_t msb = 0x80000000; + uint32_t mask = 0xFF800000; + uint32_t threshold = 0x00000080; + uint32_t result = value; + + while (msb != threshold) { + // Check if msb of current value is 1 and apply XOR mask + if (result & msb) + result = ((result ^ polynom) & mask) | (result & ~mask); + + // Shift by one + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + + return result; +} + +/** + * Resets the sensor and ensures that the devices serial number can be read over + * I2C. + */ +void HTU31DComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up esphome/components/htu31d HTU31D..."); + + if (!this->reset_()) { + this->mark_failed(); + return; + } + + if (this->read_serial_num_() == 0) { + this->mark_failed(); + return; + } +} + +/** + * Called once every update interval (user configured, defaults to 60s) and sets + * the current temperature and humidity. + */ +void HTU31DComponent::update() { + ESP_LOGD(TAG, "Checking temperature and humidty values"); + + // Trigger a conversion. From the spec sheet: The conversion command triggers + // a single temperature and humidity conversion. + if (this->write_register(HTU31D_CONVERSION, nullptr, 0) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Received errror writing conversion register"); + return; + } + + // Wait conversion time. + this->set_timeout(20, [this]() { + uint8_t thdata[6]; + if (this->read_register(HTU31D_READTEMPHUM, thdata, 6) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error reading temperature/humidty register"); + return; + } + + // Calculate temperature value. + uint16_t raw_temp = encode_uint16(thdata[0], thdata[1]); + + uint8_t crc = compute_crc((uint32_t) raw_temp << 8); + if (crc != thdata[2]) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error validating temperature CRC"); + return; + } + + float temperature = raw_temp; + temperature /= 65535.0f; + temperature *= 165; + temperature -= 40; + + if (this->temperature_ != nullptr) { + this->temperature_->publish_state(temperature); + } + + // Calculate humidty value. + uint16_t raw_hum = encode_uint16(thdata[3], thdata[4]); + + crc = compute_crc((uint32_t) raw_hum << 8); + if (crc != thdata[5]) { + this->status_set_warning(); + ESP_LOGE(TAG, "Error validating humidty CRC"); + return; + } + + float humidity = raw_hum; + humidity /= 65535.0f; + humidity *= 100; + + if (this->humidity_ != nullptr) { + this->humidity_->publish_state(humidity); + } + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); + this->status_clear_warning(); + }); +} + +/** + * Logs the current compoenent config. + */ +void HTU31DComponent::dump_config() { + ESP_LOGCONFIG(TAG, "HTU31D:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with HTU31D failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); +} + +/** + * Sends a 'reset' request to the HTU31D, followed by a 15ms delay. + * + * @returns True if was able to write the command successfully + */ +bool HTU31DComponent::reset_() { + if (this->write_register(HTU31D_RESET, nullptr, 0) != i2c::ERROR_OK) { + return false; + } + + delay(15); + return true; +} + +/** + * Reads the serial number from the device and checks the CRC. + * + * @returns the 24bit serial number from the device + */ +uint32_t HTU31DComponent::read_serial_num_() { + uint8_t reply[4]; + uint32_t serial = 0; + uint8_t padding = 0; + + // Verify we can read the device serial. + if (this->read_register(HTU31D_READSERIAL, reply, 4) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error reading device serial"); + return 0; + } + + serial = encode_uint32(reply[0], reply[1], reply[2], padding); + + uint8_t crc = compute_crc(serial); + if (crc != reply[3]) { + ESP_LOGE(TAG, "Error validating serial CRC"); + return 0; + } + + ESP_LOGD(TAG, "Found serial: 0x%X", serial); + + return serial; +} + +/** + * Checks the diagnostics register to determine if the heater is currently + * enabled. + * + * @returns True if the heater is currently enabled, False otherwise + */ +bool HTU31DComponent::is_heater_enabled() { + uint8_t reply[1]; + uint8_t heater_enabled_position = 0; + uint8_t mask = 1 << heater_enabled_position; + uint8_t diagnostics = 0; + + if (this->read_register(HTU31D_DIAGNOSTICS, reply, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Error reading device serial"); + return false; + } + + diagnostics = reply[0]; + return (diagnostics & mask) != 0; +} + +/** + * Sets the heater state on or off. + * + * @param desired True for on, and False for off. + */ +void HTU31DComponent::set_heater_state(bool desired) { + bool current = this->is_heater_enabled(); + + // If the current state matches the desired state, there is nothing to do. + if (current == desired) { + return; + } + + // Update heater state. + esphome::i2c::ErrorCode err; + if (desired) { + err = this->write_register(HTU31D_HEATERON, nullptr, 0); + } else { + err = this->write_register(HTU31D_HEATEROFF, nullptr, 0); + } + + // Record any error. + if (err != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Received error updating heater state"); + return; + } +} + +/** + * Sets the startup priority for this component. + * + * @returns The startup priority + */ +float HTU31DComponent::get_setup_priority() const { return setup_priority::DATA; } +} // namespace htu31d +} // namespace esphome diff --git a/esphome/components/htu31d/htu31d.h b/esphome/components/htu31d/htu31d.h new file mode 100644 index 000000000000..9462133ced92 --- /dev/null +++ b/esphome/components/htu31d/htu31d.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace htu31d { + +class HTU31DComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; /// Setup (reset) the sensor and check connection. + void update() override; /// Update the sensor values (temperature+humidity). + void dump_config() override; /// Dumps the configuration values. + + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } + + void set_heater_state(bool desired); + bool is_heater_enabled(); + + float get_setup_priority() const override; + + protected: + bool reset_(); + uint32_t read_serial_num_(); + + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; +}; +} // namespace htu31d +} // namespace esphome diff --git a/esphome/components/htu31d/sensor.py b/esphome/components/htu31d/sensor.py new file mode 100644 index 000000000000..fe53aa376e53 --- /dev/null +++ b/esphome/components/htu31d/sensor.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +htu31d_ns = cg.esphome_ns.namespace("htu31d") +HTU31DComponent = htu31d_ns.class_( + "HTU31DComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HTU31DComponent), + 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_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity(sens)) diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index 62adc4ae86ba..dbbf4c91f467 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -28,7 +28,7 @@ void HX711Sensor::update() { uint32_t result; if (this->read_sensor_(&result)) { int32_t value = static_cast(result); - ESP_LOGD(TAG, "'%s': Got value %d", this->name_.c_str(), value); + ESP_LOGD(TAG, "'%s': Got value %" PRId32, this->name_.c_str(), value); this->publish_state(value); } } diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 9fef649b0371..0cb6868ab5bf 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -4,6 +4,8 @@ #include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" +#include + namespace esphome { namespace hx711 { diff --git a/esphome/components/hydreon_rgxx/__init__.py b/esphome/components/hydreon_rgxx/__init__.py index 5fe050edf275..b488bfc1b411 100644 --- a/esphome/components/hydreon_rgxx/__init__.py +++ b/esphome/components/hydreon_rgxx/__init__.py @@ -6,6 +6,7 @@ hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx") RGModel = hydreon_rgxx_ns.enum("RGModel") +RG15Resolution = hydreon_rgxx_ns.enum("RG15Resolution") HydreonRGxxComponent = hydreon_rgxx_ns.class_( "HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice ) diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index da4345e136be..95702fe9e8b4 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -17,6 +17,17 @@ void HydreonRGxxComponent::dump_config() { if (this->is_failed()) { ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!"); } + if (model_ == RG9) { + ESP_LOGCONFIG(TAG, " Model: RG9"); + ESP_LOGCONFIG(TAG, " Disable Led: %s", TRUEFALSE(this->disable_led_)); + } else { + ESP_LOGCONFIG(TAG, " Model: RG15"); + if (this->resolution_ == FORCE_HIGH) { + ESP_LOGCONFIG(TAG, " Resolution: high"); + } else { + ESP_LOGCONFIG(TAG, " Resolution: low"); + } + } LOG_UPDATE_INTERVAL(this); int i = 0; @@ -187,7 +198,24 @@ void HydreonRGxxComponent::process_line_() { this->cancel_interval("reboot"); this->no_response_count_ = 0; ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); - this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode + + if (this->model_ == RG15) { + if (this->resolution_ == FORCE_HIGH) { + this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode + } else { + this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode + } + } + + if (this->model_ == RG9) { + this->write_str("P\n"); // set sensor to (P)polling mode + + if (this->disable_led_) { + this->write_str("D 1\n"); // set sensor (D 1)rain detection LED disabled + } else { + this->write_str("D 0\n"); // set sensor (D 0)rain detection LED enabled + } + } return; } if (this->buffer_starts_with_("SW")) { @@ -227,7 +255,22 @@ void HydreonRGxxComponent::process_line_() { if (n == std::string::npos) { continue; } - float data = strtof(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr); + + if (n == this->buffer_.find('t', n)) { + // The device temperature ('t') response contains both °C and °F values: + // "t 72F 22C". + // ESPHome uses only °C, only parse °C value (move past 'F'). + n = this->buffer_.find('F', n); + if (n == std::string::npos) { + continue; + } + n += 1; // move past 'F' + } else { + n += strlen(PROTOCOL_NAMES[i]); // move past protocol name + } + + // parse value, starting at str position n + float data = strtof(this->buffer_.substr(n).c_str(), nullptr); this->sensors_[i]->publish_state(data); ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); this->sensors_received_ |= (1 << i); diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index 34b9bd8d5eec..76b0985a245a 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -16,6 +16,11 @@ enum RGModel { RG15 = 2, }; +enum RG15Resolution { + FORCE_LOW = 1, + FORCE_HIGH = 2, +}; + #ifdef HYDREON_RGXX_NUM_SENSORS static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS; #else @@ -37,6 +42,7 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; } #endif void set_model(RGModel model) { model_ = model; } + void set_resolution(RG15Resolution resolution) { resolution_ = resolution; } void set_request_temperature(bool b) { request_temperature_ = b; } /// Schedule data readings. @@ -49,6 +55,8 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { float get_setup_priority() const override; + void set_disable_led(bool disable_led) { this->disable_led_ = disable_led; } + protected: void process_line_(); void schedule_reboot_(); @@ -66,12 +74,16 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { int16_t boot_count_ = 0; int16_t no_response_count_ = 0; std::string buffer_; + RGModel model_ = RG9; + RG15Resolution resolution_ = FORCE_HIGH; + int sw_version_ = 0; bool too_cold_ = false; bool lens_bad_ = false; bool em_sat_ = false; bool request_temperature_ = false; + bool disable_led_ = false; // bit field showing which sensors we have received data for int sensors_received_ = -1; diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index c2dbbd673711..72b74bf6240d 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -5,6 +5,7 @@ CONF_ID, CONF_MODEL, CONF_MOISTURE, + CONF_RESOLUTION, CONF_TEMPERATURE, DEVICE_CLASS_PRECIPITATION_INTENSITY, DEVICE_CLASS_PRECIPITATION, @@ -14,7 +15,7 @@ ICON_THERMOMETER, ) -from . import RGModel, HydreonRGxxComponent +from . import RGModel, RG15Resolution, HydreonRGxxComponent UNIT_INTENSITY = "intensity" UNIT_MILLIMETERS = "mm" @@ -25,20 +26,33 @@ CONF_TOTAL_ACC = "total_acc" CONF_R_INT = "r_int" +CONF_DISABLE_LED = "disable_led" + RG_MODELS = { "RG_9": RGModel.RG9, "RG_15": RGModel.RG15, - # https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf - # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf - # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf + # RG-15 + # 1.000 - https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf + # RG-9 + # 1.000 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf + # 1.100 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf + # 1.200 - https://rainsensors.com/wp-content/uploads/sites/3/2022/03/2022.02.17-rev-1.200-rg-9_instructions.pdf +} + +RG15_RESOLUTION = { + "low": RG15Resolution.FORCE_LOW, + "high": RG15Resolution.FORCE_HIGH, } -SUPPORTED_SENSORS = { + +SUPPORTED_OPTIONS = { CONF_ACC: ["RG_15"], CONF_EVENT_ACC: ["RG_15"], CONF_TOTAL_ACC: ["RG_15"], CONF_R_INT: ["RG_15"], + CONF_RESOLUTION: ["RG_15"], CONF_MOISTURE: ["RG_9"], CONF_TEMPERATURE: ["RG_9"], + CONF_DISABLE_LED: ["RG_9"], } PROTOCOL_NAMES = { CONF_MOISTURE: "R", @@ -51,7 +65,7 @@ def _validate(config): - for conf, models in SUPPORTED_SENSORS.items(): + for conf, models in SUPPORTED_OPTIONS.items(): if conf in config: if config[CONF_MODEL] not in models: raise cv.Invalid( @@ -69,6 +83,7 @@ def _validate(config): upper=True, space="_", ), + cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False), cv.Optional(CONF_ACC): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIMETERS, accuracy_decimals=2, @@ -105,6 +120,7 @@ def _validate(config): icon=ICON_THERMOMETER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_DISABLE_LED): cv.boolean, } ) .extend(cv.polling_component_schema("60s")) @@ -131,4 +147,11 @@ async def to_code(config): sens = await sensor.new_sensor(config[conf]) cg.add(var.set_sensor(sens, i)) + cg.add(var.set_model(config[CONF_MODEL])) + if CONF_RESOLUTION in config: + cg.add(var.set_resolution(config[CONF_RESOLUTION])) + cg.add(var.set_request_temperature(CONF_TEMPERATURE in config)) + + if CONF_DISABLE_LED in config: + cg.add(var.set_disable_led(config[CONF_DISABLE_LED])) diff --git a/esphome/components/hyt271/hyt271.cpp b/esphome/components/hyt271/hyt271.cpp index 94558fff045c..3b81294cfcdd 100644 --- a/esphome/components/hyt271/hyt271.cpp +++ b/esphome/components/hyt271/hyt271.cpp @@ -17,7 +17,7 @@ void HYT271Component::dump_config() { LOG_SENSOR(" ", "Humidity", this->humidity_); } void HYT271Component::update() { - uint8_t raw_data[4]; + uint8_t raw_data[4] = {0, 0, 0, 0}; if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) { this->status_set_warning(); diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index e38cfd23fa34..f52a0edb9f5b 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -4,6 +4,7 @@ from esphome import pins from esphome.const import ( CONF_FREQUENCY, + CONF_TIMEOUT, CONF_ID, CONF_INPUT, CONF_OUTPUT, @@ -12,6 +13,9 @@ CONF_SDA, CONF_ADDRESS, CONF_I2C_ID, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, ) from esphome.core import coroutine_with_priority, CORE @@ -36,9 +40,8 @@ def _bus_declare_type(value): raise NotImplementedError -pin_with_input_and_output_support = cv.All( - pins.internal_gpio_pin_number({CONF_INPUT: True}), - pins.internal_gpio_pin_number({CONF_OUTPUT: True}), +pin_with_input_and_output_support = pins.internal_gpio_pin_number( + {CONF_OUTPUT: True, CONF_INPUT: True} ) @@ -57,10 +60,11 @@ def _bus_declare_type(value): cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( cv.frequency, cv.Range(min=0, min_included=False) ), + cv.Optional(CONF_TIMEOUT): cv.positive_time_period, cv.Optional(CONF_SCAN, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on(["esp32", "esp8266", "rp2040"]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), ) @@ -79,6 +83,8 @@ async def to_code(config): cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) 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: cg.add_library("Wire", None) @@ -117,23 +123,56 @@ async def register_i2c_device(var, config): def final_validate_device_schema( - name: str, *, min_frequency: cv.frequency = None, max_frequency: cv.frequency = None + name: str, + *, + min_frequency: cv.frequency = None, + max_frequency: cv.frequency = None, + min_timeout: cv.time_period = None, + max_timeout: cv.time_period = None, ): hub_schema = {} - if min_frequency is not None: + if (min_frequency is not None) and (max_frequency is not None): + hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( + min=cv.frequency(min_frequency), + min_included=True, + max=cv.frequency(max_frequency), + max_included=True, + msg=f"Component {name} requires a frequency between {min_frequency} and {max_frequency} for the I2C bus", + ) + elif min_frequency is not None: hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( min=cv.frequency(min_frequency), min_included=True, msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus", ) - - if max_frequency is not None: + elif max_frequency is not None: hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( max=cv.frequency(max_frequency), max_included=True, msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus", ) + if (min_timeout is not None) and (max_timeout is not None): + hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( + min=cv.time_period(min_timeout), + min_included=True, + max=cv.time_period(max_timeout), + max_included=True, + msg=f"Component {name} requires a timeout between {min_timeout} and {max_timeout} for the I2C bus", + ) + elif min_timeout is not None: + hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( + min=cv.time_period(min_timeout), + min_included=True, + msg=f"Component {name} requires a minimum timeout of {min_timeout} for the I2C bus", + ) + elif max_timeout is not None: + hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( + max=cv.time_period(max_timeout), + max_included=True, + msg=f"Component {name} cannot be used with a timeout of over {max_timeout} for the I2C bus", + ) + return cv.Schema( {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, extra=cv.ALLOW_EXTRA, diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index eb5d463b65e5..8d8e139c61c9 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -11,43 +11,116 @@ namespace i2c { #define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); -class I2CDevice; +class I2CDevice; // forward declaration + +/// @brief This class is used to create I2CRegister objects that act as proxies to read/write internal registers on an +/// I2C device. +/// @details +/// @n typical usage: +/// @code +/// constexpr uint8_t ADDR_REGISTER_1 = 0x12; +/// i2c::I2CRegister reg_1 = this->reg(ADDR_REGISTER_1); // declare +/// reg_1 |= 0x01; // set bit +/// reg_1 &= ~0x01; // reset bit +/// reg_1 = 10; // Set value +/// uint val = reg_1.get(); // get value +/// @endcode +/// @details The I²C protocol specifies how to read/write in sets of 8-bits followed by an Acknowledgement (ACK/NACK) +/// from the device receiving the data. How the device interprets the bits read/written can vary greatly from +/// device to device. However most of the devices follow the same protocol for reading/writing 8 bit registers using as +/// implemented in the I2CRegister: after sending the device address, the controller sends one byte with the internal +/// register address and then read or write the specified register content. class I2CRegister { public: + /// @brief overloads the = operator. This allows to set the value of an i2c register + /// @param value value to be set in the register + /// @return pointer to current object I2CRegister &operator=(uint8_t value); + + /// @brief overloads the compound &= operator. This allows to reset specific bits of an I²C register + /// @param value used for the & operation + /// @return pointer to current object I2CRegister &operator&=(uint8_t value); + + /// @brief overloads the compound |= operator. This allows to set specific bits of an I²C register + /// @param value used for the & operation + /// @return pointer to current object I2CRegister &operator|=(uint8_t value); + /// @brief overloads the uint8_t() cast operator to return the I²C register value + /// @return pointer to current object explicit operator uint8_t() const { return get(); } + /// @brief returns the register value + /// @return the register value uint8_t get() const; protected: friend class I2CDevice; + /// @brief protected constructor that stores the owning object and the register address. Note as only friends can + /// create an I2CRegister @see I2CDevice::reg() + /// @param parent our parent + /// @param a_register address of the i2c register I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} - I2CDevice *parent_; - uint8_t register_; + I2CDevice *parent_; ///< I2CDevice object pointer + uint8_t register_; ///< the internal address of the register }; +/// @brief This class is used to create I2CRegister16 objects that act as proxies to read/write internal registers +/// (specified with a 16 bit address) on an I2C device. +/// @details +/// @n typical usage: +/// @code +/// constexpr uint16_t X16_BIT_ADDR_REGISTER_1 = 0x1234; +/// i2c::I2CRegister16 reg_1 = this->reg16(X16_BIT_ADDR_REGISTER_1); // declare +/// reg_1 |= 0x01; // set bit +/// reg_1 &= ~0x01; // reset bit +/// reg_1 = 10; // Set value +/// uint val = reg_1.get(); // get value +/// @endcode +/// @details The I²C protocol specification, reads/writes in sets of 8-bits followed by an Acknowledgement (ACK/NACK) +/// from the device receiving the data. How the device interprets the bits read/written to it can vary greatly from +/// device to device. This class can be used to access in the device 8 bits registers that uses a 16 bits internal +/// address. After sending the device address, the controller sends the internal register address (using two consecutive +/// bytes following the big indian convention) and then read or write the register content. class I2CRegister16 { public: + /// @brief overloads the = operator. This allows to set the value of an I²C register + /// @param value value to be set in the register + /// @return pointer to current object I2CRegister16 &operator=(uint8_t value); + + /// @brief overloads the compound &= operator. This allows to reset specific bits of an I²C register + /// @param value used for the & operation + /// @return pointer to current object I2CRegister16 &operator&=(uint8_t value); + + /// @brief overloads the compound |= operator. This allows to set bits of an I²C register + /// @param value used for the & operation + /// @return pointer to current object I2CRegister16 &operator|=(uint8_t value); + /// @brief overloads the uint8_t() cast operator to return the I²C register value + /// @return the register value explicit operator uint8_t() const { return get(); } + /// @brief returns the register value + /// @return the register value uint8_t get() const; protected: friend class I2CDevice; + /// @brief protected constructor that store the owning object and the register address. Only friends can create an + /// I2CRegister16 @see I2CDevice::reg16() + /// @param parent our parent + /// @param a_register 16 bits address of the i2c register I2CRegister16(I2CDevice *parent, uint16_t a_register) : parent_(parent), register_(a_register) {} - I2CDevice *parent_; - uint16_t register_; + I2CDevice *parent_; ///< I2CDevice object pointer + uint16_t register_; ///< the internal 16 bits address of the register }; // like ntohs/htons but without including networking headers. @@ -55,29 +128,91 @@ class I2CRegister16 { inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); } inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); } +/// @brief This Class provides the methods to read/write bytes from/to an i2c device. +/// Objects keep a list of devices found on bus as well as a pointer to the I2CBus in use. class I2CDevice { public: + /// @brief we use the C++ default constructor I2CDevice() = default; + /// @brief We store the address of the device on the bus + /// @param address of the device void set_i2c_address(uint8_t address) { address_ = address; } + + /// @brief we store the pointer to the I2CBus to use + /// @param bus pointer to the I2CBus object void set_i2c_bus(I2CBus *bus) { bus_ = bus; } + /// @brief calls the I2CRegister constructor + /// @param a_register address of the I²C register + /// @return an I2CRegister proxy object I2CRegister reg(uint8_t a_register) { return {this, a_register}; } + + /// @brief calls the I2CRegister16 constructor + /// @param a_register 16 bits address of the I²C register + /// @return an I2CRegister16 proxy object I2CRegister16 reg16(uint16_t a_register) { return {this, a_register}; } + /// @brief reads an array of bytes from the device using an I2CBus + /// @param data pointer to an array to store the bytes + /// @param len length of the buffer = number of bytes to read + /// @return an i2c::ErrorCode ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } + + /// @brief reads an array of bytes from a specific register in the I²C device + /// @param a_register an 8 bits internal address of the I²C register to read from + /// @param data pointer to an array to store the bytes + /// @param len length of the buffer = number of bytes to read + /// @param stop (true/false): True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true); + + /// @brief reads an array of bytes from a specific register in the I²C device + /// @param a_register the 16 bits internal address of the I²C register to read from + /// @param data pointer to an array of bytes to store the information + /// @param len length of the buffer = number of bytes to read + /// @param stop (true/false): True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop = true); - ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); } + /// @brief writes an array of bytes to a device using an I2CBus + /// @param data pointer to an array that contains the bytes to send + /// @param len length of the buffer = number of bytes to write + /// @param stop (true/false): True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode + ErrorCode write(const uint8_t *data, size_t len, bool stop = true) { return bus_->write(address_, data, len, stop); } + + /// @brief writes an array of bytes to a specific register in the I²C device + /// @param a_register the internal address of the register to read from + /// @param data pointer to an array to store the bytes + /// @param len length of the buffer = number of bytes to read + /// @param stop (true/false): True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true); + + /// @brief write an array of bytes to a specific register in the I²C device + /// @param a_register the 16 bits internal address of the register to read from + /// @param data pointer to an array to store the bytes + /// @param len length of the buffer = number of bytes to read + /// @param stop (true/false): True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop = true); - // Compat APIs + /// + /// Compat APIs + /// All methods below have been added for compatibility reasons. They do not bring any functionality and therefore on + /// new code it is not recommend to use them. + /// bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len) { return read_register(a_register, data, len) == ERROR_OK; } + bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; } template optional> read_bytes(uint8_t a_register) { @@ -131,8 +266,8 @@ class I2CDevice { bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } protected: - uint8_t address_{0x00}; - I2CBus *bus_{nullptr}; + uint8_t address_{0x00}; ///< store the address of the device on the bus + I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance }; } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h index 2633a7adf6fa..fbfc88323ebb 100644 --- a/esphome/components/i2c/i2c_bus.h +++ b/esphome/components/i2c/i2c_bus.h @@ -7,50 +7,93 @@ namespace esphome { namespace i2c { +/// @brief Error codes returned by I2CBus and I2CDevice methods enum ErrorCode { - ERROR_OK = 0, - ERROR_INVALID_ARGUMENT = 1, - ERROR_NOT_ACKNOWLEDGED = 2, - ERROR_TIMEOUT = 3, - ERROR_NOT_INITIALIZED = 4, - ERROR_TOO_LARGE = 5, - ERROR_UNKNOWN = 6, - ERROR_CRC = 7, + NO_ERROR = 0, ///< No error found during execution of method + ERROR_OK = 0, ///< No error found during execution of method + ERROR_INVALID_ARGUMENT = 1, ///< method called invalid argument(s) + ERROR_NOT_ACKNOWLEDGED = 2, ///< I2C bus acknowledgment not received + ERROR_TIMEOUT = 3, ///< timeout while waiting to receive bytes + ERROR_NOT_INITIALIZED = 4, ///< call method to a not initialized bus + ERROR_TOO_LARGE = 5, ///< requested a transfer larger than buffers can hold + ERROR_UNKNOWN = 6, ///< miscellaneous I2C error during execution + ERROR_CRC = 7, ///< bytes received with a CRC error }; +/// @brief the ReadBuffer structure stores a pointer to a read buffer and its length struct ReadBuffer { - uint8_t *data; - size_t len; + uint8_t *data; ///< pointer to the read buffer + size_t len; ///< length of the buffer }; + +/// @brief the WriteBuffer structure stores a pointer to a write buffer and its length struct WriteBuffer { - const uint8_t *data; - size_t len; + const uint8_t *data; ///< pointer to the write buffer + size_t len; ///< length of the buffer }; +/// @brief This Class provides the methods to read and write bytes from an I2CBus. +/// @note The I2CBus virtual class follows a *Factory design pattern* that provides all the interfaces methods required +/// by clients while deferring the actual implementation of these methods to a subclasses. I2C-bus specification and +/// user manual can be found here https://www.nxp.com/docs/en/user-guide/UM10204.pdf and an interesting I²C Application +/// note https://www.nxp.com/docs/en/application-note/AN10216.pdf class I2CBus { public: + /// @brief Creates a ReadBuffer and calls the virtual readv() method to read bytes into this buffer + /// @param address address of the I²C component on the i2c bus + /// @param buffer pointer to an array of bytes that will be used to store the data received + /// @param len length of the buffer = number of bytes to read + /// @return an i2c::ErrorCode virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) { ReadBuffer buf; buf.data = buffer; buf.len = len; return readv(address, &buf, 1); } - virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; + + /// @brief This virtual method reads bytes from an I2CBus into an array of ReadBuffer. + /// @param address address of the I²C component on the i2c bus + /// @param buffers pointer to an array of ReadBuffer + /// @param count number of ReadBuffer to read + /// @return an i2c::ErrorCode + /// @details This is a pure virtual method that must be implemented in a subclass. + virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t count) = 0; + virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { return write(address, buffer, len, true); } + + /// @brief Creates a WriteBuffer and calls the writev() method to send the bytes from this buffer + /// @param address address of the I²C component on the i2c bus + /// @param buffer pointer to an array of bytes that contains the data to be sent + /// @param len length of the buffer = number of bytes to write + /// @param stop true or false: True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) { WriteBuffer buf; buf.data = buffer; buf.len = len; return writev(address, &buf, 1, stop); } + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return writev(address, buffers, cnt, true); } - virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0; + + /// @brief This virtual method writes bytes to an I2CBus from an array of WriteBuffer. + /// @param address address of the I²C component on the i2c bus + /// @param buffers pointer to an array of WriteBuffer + /// @param count number of WriteBuffer to write + /// @param stop true or false: True will send a stop message, releasing the bus after + /// transmission. False will send a restart, keeping the connection active. + /// @return an i2c::ErrorCode + /// @details This is a pure virtual method that must be implemented in the subclass. + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t count, bool stop) = 0; protected: + /// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair + /// that contains the address and the corresponding bool presence flag. void i2c_scan_() { for (uint8_t address = 8; address < 120; address++) { auto err = writev(address, nullptr, 0); @@ -61,8 +104,8 @@ class I2CBus { } } } - std::vector> scan_results_; - bool scan_{false}; + std::vector> scan_results_; ///< array containing scan results + bool scan_{false}; ///< Should we scan ? Can be set in the yaml }; } // namespace i2c diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index d80ab1fd1d1b..cd1b2aacc78e 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -24,7 +24,7 @@ void ArduinoI2CBus::setup() { } next_bus_num++; #elif defined(USE_ESP8266) - wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) + wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory) #elif defined(USE_RP2040) static bool first = true; if (first) { @@ -35,6 +35,16 @@ void ArduinoI2CBus::setup() { } #endif + this->set_pins_and_clock_(); + + this->initialized_ = true; + if (this->scan_) { + ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); + this->i2c_scan_(); + } +} + +void ArduinoI2CBus::set_pins_and_clock_() { #ifdef USE_RP2040 wire_->setSDA(this->sda_pin_); wire_->setSCL(this->scl_pin_); @@ -42,18 +52,35 @@ void ArduinoI2CBus::setup() { #else wire_->begin(static_cast(sda_pin_), static_cast(scl_pin_)); #endif - wire_->setClock(frequency_); - initialized_ = true; - if (this->scan_) { - ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); - this->i2c_scan_(); + 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) + // https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h + wire_->setClockStretchLimit(timeout_); // unit: us +#elif defined(USE_RP2040) + // https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h + wire_->setTimeout(timeout_ / 1000); // unit: ms +#endif } + wire_->setClock(frequency_); } + void ArduinoI2CBus::dump_config() { ESP_LOGCONFIG(TAG, "I2C Bus:"); ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + if (timeout_ > 0) { +#if defined(USE_ESP32) + ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000); +#elif defined(USE_ESP8266) + ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_); +#elif defined(USE_RP2040) + ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000); +#endif + } switch (this->recovery_result_) { case RECOVERY_COMPLETED: ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); @@ -82,6 +109,10 @@ void ArduinoI2CBus::dump_config() { } ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { +#if defined(USE_ESP8266) + this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances +#endif + // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { @@ -120,6 +151,10 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) return ERROR_OK; } ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { +#if defined(USE_ESP8266) + this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances +#endif + // logging is only enabled with vv level, if warnings are shown the caller // should log them if (!initialized_) { @@ -164,7 +199,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; case 2: case 3: - ESP_LOGVV(TAG, "TX failed: not acknowledged"); + ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status); return ERROR_NOT_ACKNOWLEDGED; case 5: ESP_LOGVV(TAG, "TX failed: timeout"); diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 7298c3a1c975..6a670a2a054e 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -27,9 +27,11 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + void set_timeout(uint32_t timeout) { timeout_ = timeout; } private: void recover_(); + void set_pins_and_clock_(); RecoveryCode recovery_result_; protected: @@ -37,6 +39,7 @@ class ArduinoI2CBus : public I2CBus, public Component { uint8_t sda_pin_; uint8_t scl_pin_; uint32_t frequency_; + uint32_t timeout_ = 0; bool initialized_ = false; }; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 5d35c1968b51..cbb748cca18b 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -1,12 +1,12 @@ #ifdef USE_ESP_IDF #include "i2c_bus_esp_idf.h" +#include +#include +#include "esphome/core/application.h" #include "esphome/core/hal.h" -#include "esphome/core/log.h" #include "esphome/core/helpers.h" -#include "esphome/core/application.h" -#include -#include +#include "esphome/core/log.h" namespace esphome { namespace i2c { @@ -45,6 +45,20 @@ void IDFI2CBus::setup() { this->mark_failed(); return; } + if (timeout_ > 0) { // if timeout specified in yaml: + if (timeout_ > 13000) { + ESP_LOGW(TAG, "i2c timeout of %" PRIu32 "us greater than max of 13ms on esp-idf, setting to max", timeout_); + timeout_ = 13000; + } + err = i2c_set_timeout(port_, timeout_ * 80); // unit: APB 80MHz clock cycle + if (err != ESP_OK) { + ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } else { + ESP_LOGV(TAG, "i2c_timeout set to %d ticks (%d us)", timeout_ * 80, timeout_); + } + } err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM); if (err != ESP_OK) { ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); @@ -62,6 +76,9 @@ void IDFI2CBus::dump_config() { ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " Frequency: %" PRIu32 " Hz", this->frequency_); + if (timeout_ > 0) { + ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 "us", this->timeout_); + } switch (this->recovery_result_) { case RECOVERY_COMPLETED: ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); @@ -127,6 +144,8 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { return ERROR_UNKNOWN; } err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); + // i2c_master_cmd_begin() will block for a whole second if no ack: + // https://github.com/espressif/esp-idf/issues/4999 i2c_cmd_link_delete(cmd); if (err == ESP_FAIL) { // transfer not acked diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index c80ea8c99d11..afb4c2d22b8b 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -29,6 +29,7 @@ class IDFI2CBus : public I2CBus, public Component { void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + void set_timeout(uint32_t timeout) { timeout_ = timeout; } private: void recover_(); @@ -41,6 +42,7 @@ class IDFI2CBus : public I2CBus, public Component { uint8_t scl_pin_; bool scl_pullup_enabled_; uint32_t frequency_; + uint32_t timeout_ = 0; bool initialized_ = false; }; diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp index 9e2e3f136ab1..1890e27bdfc0 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp @@ -10,14 +10,19 @@ namespace i2s_audio { static const char *const TAG = "audio"; void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { + media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING; + if (call.get_announcement().has_value()) { + play_state = call.get_announcement().value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING + : media_player::MEDIA_PLAYER_STATE_PLAYING; + } if (call.get_media_url().has_value()) { this->current_url_ = call.get_media_url(); - - if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) { + if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) { if (this->audio_->isRunning()) { this->audio_->stopSong(); } this->audio_->connecttohost(this->current_url_.value().c_str()); + this->state = play_state; } else { this->start(); } @@ -35,7 +40,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { case media_player::MEDIA_PLAYER_COMMAND_PLAY: if (!this->audio_->isRunning()) this->audio_->pauseResume(); - this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + this->state = play_state; break; case media_player::MEDIA_PLAYER_COMMAND_PAUSE: if (this->audio_->isRunning()) @@ -126,7 +131,9 @@ void I2SAudioMediaPlayer::loop() { void I2SAudioMediaPlayer::play_() { this->audio_->loop(); - if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) { + if ((this->state == media_player::MEDIA_PLAYER_STATE_PLAYING || + this->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) && + !this->audio_->isRunning()) { this->stop(); } } @@ -164,6 +171,10 @@ void I2SAudioMediaPlayer::start_() { if (this->current_url_.has_value()) { this->audio_->connecttohost(this->current_url_.value().c_str()); this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + if (this->is_announcement_.has_value()) { + this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING + : media_player::MEDIA_PLAYER_STATE_PLAYING; + } this->publish_state(); } } diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h index 092e6de8e831..d7d9b1f74ae3 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h @@ -78,6 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, HighFrequencyLoopRequester high_freq_; optional current_url_{}; + optional is_announcement_{}; }; } // namespace i2s_audio diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index b917da30450a..5ee359dc26fa 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -20,7 +20,9 @@ CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" +CONF_SAMPLE_RATE = "sample_rate" CONF_BITS_PER_SAMPLE = "bits_per_sample" +CONF_USE_APLL = "use_apll" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component @@ -62,9 +64,11 @@ def validate_esp32_variant(config): cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), + cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All( _validate_bits, cv.enum(BITS_PER_SAMPLE) ), + cv.Optional(CONF_USE_APLL, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -105,6 +109,8 @@ async def to_code(config): cg.add(var.set_pdm(config[CONF_PDM])) cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_use_apll(config[CONF_USE_APLL])) await microphone.register_microphone(var, config) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index cf0628d638b8..1475df0975a9 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -37,6 +37,8 @@ void I2SAudioMicrophone::setup() { void I2SAudioMicrophone::start() { if (this->is_failed()) return; + if (this->state_ == microphone::STATE_RUNNING) + return; // Already running this->state_ = microphone::STATE_STARTING; } void I2SAudioMicrophone::start_() { @@ -45,43 +47,71 @@ void I2SAudioMicrophone::start_() { } i2s_driver_config_t config = { .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), - .sample_rate = 16000, + .sample_rate = this->sample_rate_, .bits_per_sample = this->bits_per_sample_, .channel_format = this->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = 256, - .use_apll = false, + .use_apll = this->use_apll_, .tx_desc_auto_clear = false, .fixed_mclk = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, }; + esp_err_t err; + #if SOC_I2S_SUPPORTS_ADC if (this->adc_) { config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); - i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); + err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } - i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_); - i2s_adc_enable(this->parent_->get_port()); - } else { + err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } + err = i2s_adc_enable(this->parent_->get_port()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } + + } else #endif + { if (this->pdm_) config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM); - i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); + err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } i2s_pin_config_t pin_config = this->parent_->get_pin_config(); pin_config.data_in_num = this->din_pin_; - i2s_set_pin(this->parent_->get_port(), &pin_config); -#if SOC_I2S_SUPPORTS_ADC + err = i2s_set_pin(this->parent_->get_port(), &pin_config); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } } -#endif this->state_ = microphone::STATE_RUNNING; this->high_freq_.start(); + this->status_clear_error(); } void I2SAudioMicrophone::stop() { @@ -95,11 +125,33 @@ void I2SAudioMicrophone::stop() { } void I2SAudioMicrophone::stop_() { - i2s_stop(this->parent_->get_port()); - i2s_driver_uninstall(this->parent_->get_port()); + esp_err_t err; +#if SOC_I2S_SUPPORTS_ADC + if (this->adc_) { + err = i2s_adc_disable(this->parent_->get_port()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } + } +#endif + err = i2s_stop(this->parent_->get_port()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } + err = i2s_driver_uninstall(this->parent_->get_port()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error uninstalling I2S driver: %s", esp_err_to_name(err)); + this->status_set_error(); + return; + } this->parent_->unlock(); this->state_ = microphone::STATE_STOPPED; this->high_freq_.stop(); + this->status_clear_error(); } size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { @@ -110,6 +162,10 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { this->status_set_warning(); return 0; } + if (bytes_read == 0) { + this->status_set_warning(); + return 0; + } this->status_clear_warning(); if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { return bytes_read; diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index dc6b70047a66..68b9a94fbd4e 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -31,7 +31,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } + void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } + void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } protected: void start_(); @@ -45,7 +47,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif bool pdm_{false}; i2s_channel_fmt_t channel_; + uint32_t sample_rate_; i2s_bits_per_sample_t bits_per_sample_; + bool use_apll_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 43bc00513632..95e63035fe7d 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -11,7 +11,7 @@ namespace esphome { namespace i2s_audio { -static const size_t BUFFER_COUNT = 10; +static const size_t BUFFER_COUNT = 20; static const char *const TAG = "i2s_audio.speaker"; @@ -19,7 +19,7 @@ void I2SAudioSpeaker::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent)); - this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent)); + this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent)); } void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; } @@ -29,7 +29,7 @@ void I2SAudioSpeaker::start_() { } this->state_ = speaker::STATE_RUNNING; - xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 0, &this->player_task_handle_); + xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_); } void I2SAudioSpeaker::player_task(void *params) { @@ -47,7 +47,7 @@ void I2SAudioSpeaker::player_task(void *params) { .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, - .dma_buf_len = 1024, + .dma_buf_len = 128, .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = I2S_PIN_NO_CHANGE, @@ -60,7 +60,17 @@ void I2SAudioSpeaker::player_task(void *params) { } #endif - i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr); + esp_err_t err = i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr); + if (err != ESP_OK) { + event.type = TaskEventType::WARNING; + event.err = err; + xQueueSend(this_speaker->event_queue_, &event, 0); + event.type = TaskEventType::STOPPED; + xQueueSend(this_speaker->event_queue_, &event, 0); + while (true) { + delay(10); + } + } #if SOC_I2S_SUPPORTS_DAC if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) { @@ -88,9 +98,7 @@ void I2SAudioSpeaker::player_task(void *params) { } if (data_event.stop) { // Stop signal from main thread - while (xQueueReceive(this_speaker->buffer_queue_, &data_event, 0) == pdTRUE) { - // Flush queue - } + xQueueReset(this_speaker->buffer_queue_); // Flush queue break; } size_t bytes_written; @@ -103,7 +111,7 @@ void I2SAudioSpeaker::player_task(void *params) { uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, - (100 / portTICK_PERIOD_MS)); + (10 / portTICK_PERIOD_MS)); if (err != ESP_OK) { event = {.type = TaskEventType::WARNING, .err = err}; xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); @@ -122,7 +130,6 @@ void I2SAudioSpeaker::player_task(void *params) { event.type = TaskEventType::STOPPING; xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); - i2s_stop(this_speaker->parent_->get_port()); i2s_driver_uninstall(this_speaker->parent_->get_port()); event.type = TaskEventType::STOPPED; @@ -151,17 +158,24 @@ void I2SAudioSpeaker::watch_() { if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) { switch (event.type) { case TaskEventType::STARTING: + ESP_LOGD(TAG, "Starting I2S Audio Speaker"); + break; case TaskEventType::STARTED: + ESP_LOGD(TAG, "Started I2S Audio Speaker"); + break; case TaskEventType::STOPPING: + ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); break; case TaskEventType::PLAYING: this->status_clear_warning(); break; case TaskEventType::STOPPED: - this->parent_->unlock(); this->state_ = speaker::STATE_STOPPED; vTaskDelete(this->player_task_handle_); this->player_task_handle_ = nullptr; + this->parent_->unlock(); + xQueueReset(this->buffer_queue_); + ESP_LOGD(TAG, "Stopped I2S Audio Speaker"); break; case TaskEventType::WARNING: ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err)); @@ -177,9 +191,9 @@ void I2SAudioSpeaker::loop() { this->start_(); break; case speaker::STATE_RUNNING: + case speaker::STATE_STOPPING: this->watch_(); break; - case speaker::STATE_STOPPING: case speaker::STATE_STOPPED: break; } @@ -206,6 +220,8 @@ size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) { return index; } +bool I2SAudioSpeaker::has_buffered_data() const { return uxQueueMessagesWaiting(this->buffer_queue_) > 0; } + } // namespace i2s_audio } // namespace esphome diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index b075722e1b10..20c36a69d3f9 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -56,6 +56,8 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud size_t play(const uint8_t *data, size_t length) override; + bool has_buffered_data() const override; + protected: void start_(); // void stop_(); diff --git a/esphome/components/iaqcore/__init__.py b/esphome/components/iaqcore/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/iaqcore/iaqcore.cpp b/esphome/components/iaqcore/iaqcore.cpp new file mode 100644 index 000000000000..810e8da0b2d7 --- /dev/null +++ b/esphome/components/iaqcore/iaqcore.cpp @@ -0,0 +1,99 @@ +#include "iaqcore.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace iaqcore { + +static const char *const TAG = "iaqcore"; + +enum IAQCoreErrorCode : uint8_t { ERROR_OK = 0, ERROR_RUNIN = 0x10, ERROR_BUSY = 0x01, ERROR_ERROR = 0x80 }; + +struct SensorData { + uint16_t co2; + IAQCoreErrorCode status; + int32_t resistance; + uint16_t tvoc; + + SensorData(const uint8_t *buffer) { + this->co2 = encode_uint16(buffer[0], buffer[1]); + this->status = static_cast(buffer[2]); + this->resistance = encode_uint32(buffer[3], buffer[4], buffer[5], buffer[6]); + this->tvoc = encode_uint16(buffer[7], buffer[8]); + } +}; + +void IAQCore::setup() { + if (this->write(nullptr, 0) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication failed!"); + this->mark_failed(); + return; + } +} + +void IAQCore::update() { + uint8_t buffer[sizeof(SensorData)]; + + if (this->read_register(0xB5, buffer, sizeof(buffer), false) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Read failed"); + this->status_set_warning(); + this->publish_nans_(); + return; + } + + SensorData data(buffer); + + switch (data.status) { + case ERROR_OK: + ESP_LOGD(TAG, "OK"); + break; + case ERROR_RUNIN: + ESP_LOGI(TAG, "Warming up"); + break; + case ERROR_BUSY: + ESP_LOGI(TAG, "Busy"); + break; + case ERROR_ERROR: + ESP_LOGE(TAG, "Error"); + break; + } + + if (data.status != ERROR_OK) { + this->status_set_warning(); + this->publish_nans_(); + return; + } + + if (this->co2_ != nullptr) { + this->co2_->publish_state(data.co2); + } + if (this->tvoc_ != nullptr) { + this->tvoc_->publish_state(data.tvoc); + } + + this->status_clear_warning(); +} + +void IAQCore::publish_nans_() { + if (this->co2_ != nullptr) { + this->co2_->publish_state(NAN); + } + if (this->tvoc_ != nullptr) { + this->tvoc_->publish_state(NAN); + } +} + +void IAQCore::dump_config() { + ESP_LOGCONFIG(TAG, "AMS iAQ Core:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with AMS iAQ Core failed!"); + } + LOG_SENSOR(" ", "CO2", this->co2_); + LOG_SENSOR(" ", "TVOC", this->tvoc_); +} + +} // namespace iaqcore +} // namespace esphome diff --git a/esphome/components/iaqcore/iaqcore.h b/esphome/components/iaqcore/iaqcore.h new file mode 100644 index 000000000000..f343c2a7055a --- /dev/null +++ b/esphome/components/iaqcore/iaqcore.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace iaqcore { + +class IAQCore : public PollingComponent, public i2c::I2CDevice { + public: + void set_co2(sensor::Sensor *co2) { co2_ = co2; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; } + + void setup() override; + void update() override; + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *co2_{nullptr}; + sensor::Sensor *tvoc_{nullptr}; + + void publish_nans_(); +}; + +} // namespace iaqcore +} // namespace esphome diff --git a/esphome/components/iaqcore/sensor.py b/esphome/components/iaqcore/sensor.py new file mode 100644 index 000000000000..51c5b283b753 --- /dev/null +++ b/esphome/components/iaqcore/sensor.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_CO2, + CONF_ID, + CONF_TVOC, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_MILLION, + UNIT_PARTS_PER_BILLION, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@yozik04"] + + +iaqcore_ns = cg.esphome_ns.namespace("iaqcore") +iAQCore = iaqcore_ns.class_("IAQCore", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(iAQCore), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if co2_config := config.get(CONF_CO2): + sens = await sensor.new_sensor(co2_config) + cg.add(var.set_co2(sens)) + + if tvoc_config := config.get(CONF_TVOC): + sens = await sensor.new_sensor(tvoc_config) + cg.add(var.set_tvoc(sens)) + + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 0435460b6a82..3aaf76d6f8ba 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import core, pins -from esphome.components import display, spi +from esphome.components import display, spi, font +from esphome.components.display import validate_rotation from esphome.core import CORE, HexInt from esphome.const import ( CONF_COLOR_PALETTE, @@ -13,7 +14,17 @@ CONF_PAGES, CONF_RESET_PIN, CONF_DIMENSIONS, - CONF_DATA_RATE, + CONF_WIDTH, + CONF_HEIGHT, + CONF_ROTATION, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_SWAP_XY, + CONF_COLOR_ORDER, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_TRANSFORM, + CONF_INVERT_COLORS, ) DEPENDENCIES = ["spi"] @@ -25,35 +36,50 @@ def AUTO_LOAD(): return [] -CODEOWNERS = ["@nielsnl68"] +CODEOWNERS = ["@nielsnl68", "@clydebarrow"] -ili9XXX_ns = cg.esphome_ns.namespace("ili9xxx") -ili9XXXSPI = ili9XXX_ns.class_( - "ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx") +ILI9XXXDisplay = ili9xxx_ns.class_( + "ILI9XXXDisplay", + cg.PollingComponent, + spi.SPIDevice, + display.Display, + display.DisplayBuffer, ) -ILI9XXXColorMode = ili9XXX_ns.enum("ILI9XXXColorMode") +ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode") +ColorOrder = display.display_ns.enum("ColorMode") MODELS = { - "M5STACK": ili9XXX_ns.class_("ILI9XXXM5Stack", ili9XXXSPI), - "M5CORE": ili9XXX_ns.class_("ILI9XXXM5CORE", ili9XXXSPI), - "TFT_2.4": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), - "TFT_2.4R": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), - "ILI9341": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), - "ILI9342": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), - "ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI), - "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), - "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), - "ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI), - "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), - "S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI), - "S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI), + "GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), + "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), + "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), + "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), + "TFT_2.4R": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), + "ILI9341": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), + "ILI9342": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay), + "ILI9481": ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay), + "ILI9481-18": ili9xxx_ns.class_("ILI9XXXILI948118", ILI9XXXDisplay), + "ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), + "ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), + "ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), + "ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), + "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), + "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), + "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), + "WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), +} + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, } COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" +CONF_INVERT_DISPLAY = "invert_display" def _validate(config): @@ -76,6 +102,7 @@ def _validate(config): "TFT_2.4R", "ILI9341", "ILI9342", + "ST7789V", ]: raise cv.Invalid( "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" @@ -84,11 +111,22 @@ def _validate(config): CONFIG_SCHEMA = cv.All( + font.validate_pillow_installed, display.FULL_DISPLAY_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(ili9XXXSPI), + cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), - cv.Optional(CONF_DIMENSIONS): cv.dimensions, + cv.Optional(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_LED_PIN): cv.invalid( @@ -99,11 +137,23 @@ def _validate(config): cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( cv.file_ ), - cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA, + cv.Optional(CONF_INVERT_DISPLAY): cv.invalid( + "'invert_display' has been replaced by 'invert_colors'" + ), + cv.Optional(CONF_INVERT_COLORS): cv.boolean, + cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True), + cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation, + cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema( + { + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), } ) .extend(cv.polling_component_schema("1s")) - .extend(spi.spi_device_schema(False)), + .extend(spi.spi_device_schema(False, "40MHz")), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), _validate, ) @@ -113,11 +163,17 @@ async def to_code(config): rhs = MODELS[config[CONF_MODEL]].new() var = cg.Pvariable(config[CONF_ID], rhs) - await cg.register_component(var, config) await display.register_display(var, config) await spi.register_spi_device(var, config) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) cg.add(var.set_dc_pin(dc)) + if CONF_COLOR_ORDER in config: + cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) + if CONF_TRANSFORM in config: + transform = config[CONF_TRANSFORM] + cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( @@ -130,9 +186,17 @@ async def to_code(config): cg.add(var.set_reset_pin(reset)) if CONF_DIMENSIONS in config: - cg.add( - var.set_dimentions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]) - ) + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) rhs = None if config[CONF_COLOR_PALETTE] == "GRAYSCALE": @@ -140,8 +204,6 @@ async def to_code(config): rhs = [] for x in range(256): rhs.extend([HexInt(x), HexInt(x), HexInt(x)]) - prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.add(var.set_palette(prog_arr)) elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE": cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8_INDEXED)) from PIL import Image @@ -165,7 +227,7 @@ def load_image(filename): x = x + i.width # reduce the colors on combined image to 256. - converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256) + converted = ref_image.convert("P", palette=Image.Palette.ADAPTIVE, colors=256) # if you want to verify how the images look use # ref_image.save("ref_in.png") # converted.save("ref_out.png") @@ -179,5 +241,5 @@ def load_image(filename): prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) cg.add(var.set_palette(prog_arr)) - spi_data_rate = str(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]]) - cg.add_define("ILI9XXXDisplay_DATA_RATE", cg.RawExpression(spi_data_rate)) + if CONF_INVERT_COLORS in config: + cg.add(var.invert_colors(config[CONF_INVERT_COLORS])) diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 750f629db2d2..e292906a9330 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -7,16 +7,44 @@ namespace esphome { namespace ili9xxx { -static const char *const TAG = "ili9xxx"; +static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write +static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer + +// store a 16 bit value in a buffer, big endian. +static inline void put16_be(uint8_t *buf, uint16_t value) { + buf[0] = value >> 8; + buf[1] = value; +} + +void ILI9XXXDisplay::set_madctl() { + // custom x/y transform and color order + uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; + if (this->swap_xy_) + mad |= MADCTL_MV; + if (this->mirror_x_) + mad |= MADCTL_MX; + if (this->mirror_y_) + mad |= MADCTL_MY; + this->command(ILI9XXX_MADCTL); + this->data(mad); + esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad); +} void ILI9XXXDisplay::setup() { + ESP_LOGD(TAG, "Setting up ILI9xxx"); + this->setup_pins_(); - this->initialize(); + this->init_lcd_(); + this->set_madctl(); + this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; this->y_high_ = 0; +} + +void ILI9XXXDisplay::alloc_buffer_() { if (this->buffer_color_mode_ == BITS_16) { this->init_internal_(this->get_buffer_length_() * 2); if (this->buffer_ != nullptr) { @@ -45,6 +73,8 @@ void ILI9XXXDisplay::setup_pins_() { void ILI9XXXDisplay::dump_config() { LOG_DISPLAY("", "ili9xxx", this); + ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_x_); + ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_y_); switch (this->buffer_color_mode_) { case BITS_8_INDEXED: ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed"); @@ -59,10 +89,16 @@ void ILI9XXXDisplay::dump_config() { if (this->is_18bitdisplay_) { ESP_LOGCONFIG(TAG, " 18-Bit Mode: YES"); } + ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); + ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB"); + ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_)); + ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_)); + ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_)); if (this->is_failed()) { ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!"); @@ -73,6 +109,8 @@ void ILI9XXXDisplay::dump_config() { float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWARE; } void ILI9XXXDisplay::fill(Color color) { + if (!this->check_buffer_()) + return; uint16_t new_color = 0; this->x_low_ = 0; this->y_low_ = 0; @@ -90,7 +128,6 @@ void ILI9XXXDisplay::fill(Color color) { // Upper and lower is equal can use quicker memset operation. Takes ~20ms. memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits); } else { - // Slower set of both buffers. Takes ~30ms. for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) { this->buffer_[i] = (uint8_t) (new_color >> 8); this->buffer_[i + 1] = (uint8_t) new_color; @@ -110,6 +147,8 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { return; } + if (!this->check_buffer_()) + return; uint32_t pos = (y * width_) + x; uint16_t new_color; bool updated = false; @@ -138,12 +177,14 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) } if (updated) { // low and high watermark may speed up drawing from buffer - this->x_low_ = (x < this->x_low_) ? x : this->x_low_; - this->y_low_ = (y < this->y_low_) ? y : this->y_low_; - this->x_high_ = (x > this->x_high_) ? x : this->x_high_; - this->y_high_ = (y > this->y_high_) ? y : this->y_high_; - // ESP_LOGVV(TAG, "=>>> pixel (x:%d, y:%d) (xl:%d, xh:%d, yl:%d, yh:%d", x, y, this->x_low_, this->x_high_, - // this->y_low_, this->y_high_); + if (x < this->x_low_) + this->x_low_ = x; + if (y < this->y_low_) + this->y_low_ = y; + if (x > this->x_high_) + this->x_high_ = x; + if (y > this->y_high_) + this->y_high_ = y; } } @@ -162,59 +203,80 @@ void ILI9XXXDisplay::update() { } void ILI9XXXDisplay::display_() { - // we will only update the changed window to the display - uint16_t w = this->x_high_ - this->x_low_ + 1; // NOLINT - uint16_t h = this->y_high_ - this->y_low_ + 1; // NOLINT - uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); - + uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE]; // check if something was displayed if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { - ESP_LOGV(TAG, "Nothing to display"); return; } - set_addr_window_(this->x_low_, this->y_low_, w, h); + // we will only update the changed rows to the display + size_t const w = this->x_high_ - this->x_low_ + 1; + size_t const h = this->y_high_ - this->y_low_ + 1; + size_t mhz = this->data_rate_ / 1000000; + // estimate time for a single write + size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2; + // estimate time for multiple writes + size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US; ESP_LOGV(TAG, "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " - "heigth:%d, start_pos:%d)", - this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, start_pos); - - this->start_data_(); - for (uint16_t row = 0; row < h; row++) { - uint32_t pos = start_pos + (row * width_); - uint32_t rem = w; - - while (rem > 0) { - uint32_t sz = std::min(rem, ILI9XXX_TRANSFER_BUFFER_SIZE); - // ESP_LOGVV(TAG, "Send to display(pos:%d, rem:%d, zs:%d)", pos, rem, sz); - buffer_to_transfer_(pos, sz); + "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)", + this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_, + this->is_18bitdisplay_, sw_time, mw_time); + auto now = millis(); + if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) { + // 16 bit mode maps directly to display format + ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2); + set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_); + this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); + } else { + ESP_LOGV(TAG, "Doing multiple write"); + size_t rem = h * w; // remaining number of pixels to write + set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_); + size_t idx = 0; // index into transfer_buffer + size_t pixel = 0; // pixel number offset + size_t pos = this->y_low_ * this->width_ + this->x_low_; + while (rem-- != 0) { + uint16_t color_val; + switch (this->buffer_color_mode_) { + case BITS_8: + color_val = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos++])); + break; + case BITS_8_INDEXED: + color_val = display::ColorUtil::color_to_565( + display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_)); + break; + default: // case BITS_16: + color_val = (buffer_[pos * 2] << 8) + buffer_[pos * 2 + 1]; + pos++; + break; + } if (this->is_18bitdisplay_) { - for (uint32_t i = 0; i < sz; ++i) { - uint16_t color_val = transfer_buffer_[i]; - - uint8_t red = color_val & 0x1F; - uint8_t green = (color_val & 0x7E0) >> 5; - uint8_t blue = (color_val & 0xF800) >> 11; - - uint8_t pass_buff[3]; - - pass_buff[2] = (uint8_t) ((red / 32.0) * 64) << 2; - pass_buff[1] = (uint8_t) green << 2; - pass_buff[0] = (uint8_t) ((blue / 32.0) * 64) << 2; - - this->write_array(pass_buff, sizeof(pass_buff)); - } + transfer_buffer[idx++] = (uint8_t) ((color_val & 0xF800) >> 8); // Blue + transfer_buffer[idx++] = (uint8_t) ((color_val & 0x7E0) >> 3); // Green + transfer_buffer[idx++] = (uint8_t) (color_val << 3); // Red } else { - this->write_array16(transfer_buffer_, sz); + put16_be(transfer_buffer + idx, color_val); + idx += 2; } - pos += sz; - rem -= sz; + if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) { + this->write_array(transfer_buffer, idx); + idx = 0; + App.feed_wdt(); + } + // end of line? Skip to the next. + if (++pixel == w) { + pixel = 0; + pos += this->width_ - w; + } + } + // flush any balance. + if (idx != 0) { + this->write_array(transfer_buffer, idx); } - App.feed_wdt(); } this->end_data_(); - + ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now)); // invalidate watermarks this->x_low_ = this->width_; this->y_low_ = this->height_; @@ -222,24 +284,32 @@ void ILI9XXXDisplay::display_() { this->y_high_ = 0; } -uint32_t ILI9XXXDisplay::buffer_to_transfer_(uint32_t pos, uint32_t sz) { - for (uint32_t i = 0; i < sz; ++i) { - switch (this->buffer_color_mode_) { - case BITS_8_INDEXED: - transfer_buffer_[i] = display::ColorUtil::color_to_565( - display::ColorUtil::index8_to_color_palette888(this->buffer_[pos + i], this->palette_)); - break; - case BITS_16: - transfer_buffer_[i] = ((uint16_t) this->buffer_[(pos + i) * 2] << 8) | this->buffer_[((pos + i) * 2) + 1]; - continue; - break; - default: - transfer_buffer_[i] = - display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos + i])); - break; +// note that this bypasses the buffer and writes directly to the display. +void ILI9XXXDisplay::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) { + if (w <= 0 || h <= 0) + return; + // if color mapping or software rotation is required, hand this off to the parent implementation. This will + // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not + // configured the renderer well. + if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian || + this->is_18bitdisplay_) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + this->write_array(ptr, w * h * 2); + } else { + auto stride = x_offset + w + x_pad; + for (size_t y = 0; y != h; y++) { + this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); } } - return sz; + this->end_data_(); } // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color @@ -265,20 +335,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte this->end_data_(); } -uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) { - uint8_t data = 0x10 + index; - this->send_command(0xD9, &data, 1); // Set Index Register - uint8_t result; - this->start_command_(); - this->write_byte(command_byte); - this->start_data_(); - do { - result = this->read_byte(); - } while (index--); - this->end_data_(); - return result; -} - void ILI9XXXDisplay::start_command_() { this->dc_pin_->digital_write(false); this->enable(); @@ -294,164 +350,54 @@ void ILI9XXXDisplay::end_data_() { this->disable(); } void ILI9XXXDisplay::reset_() { if (this->reset_pin_ != nullptr) { this->reset_pin_->digital_write(false); - delay(10); + delay(20); this->reset_pin_->digital_write(true); - delay(10); + delay(20); } } -void ILI9XXXDisplay::init_lcd_(const uint8_t *init_cmd) { +void ILI9XXXDisplay::init_lcd_() { uint8_t cmd, x, num_args; - const uint8_t *addr = init_cmd; - while ((cmd = progmem_read_byte(addr++)) > 0) { - x = progmem_read_byte(addr++); + const uint8_t *addr = this->init_sequence_; + while ((cmd = *addr++) > 0) { + x = *addr++; num_args = x & 0x7F; - send_command(cmd, addr, num_args); + this->send_command(cmd, addr, num_args); addr += num_args; if (x & 0x80) delay(150); // NOLINT } } -void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) { - uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1); - this->command(ILI9XXX_CASET); // Column address set - this->start_data_(); - this->write_byte(x1 >> 8); - this->write_byte(x1); - this->write_byte(x2 >> 8); - this->write_byte(x2); - this->end_data_(); - this->command(ILI9XXX_PASET); // Row address set - this->start_data_(); - this->write_byte(y1 >> 8); - this->write_byte(y1); - this->write_byte(y2 >> 8); - this->write_byte(y2); - this->end_data_(); +// Tell the display controller where we want to draw pixels. +void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { + x1 += this->offset_x_; + x2 += this->offset_x_; + y1 += this->offset_y_; + y2 += this->offset_y_; + this->command(ILI9XXX_CASET); + this->data(x1 >> 8); + this->data(x1 & 0xFF); + this->data(x2 >> 8); + this->data(x2 & 0xFF); + this->command(ILI9XXX_PASET); // Page address set + this->data(y1 >> 8); + this->data(y1 & 0xFF); + this->data(y2 >> 8); + this->data(y2 & 0xFF); this->command(ILI9XXX_RAMWR); // Write to RAM + this->start_data_(); } -void ILI9XXXDisplay::invert_display_(bool invert) { this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } - -int ILI9XXXDisplay::get_width_internal() { return this->width_; } -int ILI9XXXDisplay::get_height_internal() { return this->height_; } - -// M5Stack display -void ILI9XXXM5Stack::initialize() { - this->init_lcd_(INITCMD_M5STACK); - if (this->width_ == 0) - this->width_ = 320; - if (this->height_ == 0) - this->height_ = 240; - this->invert_display_(true); -} - -// M5CORE display // Based on the configuration settings of M5stact's M5GFX code. -void ILI9XXXM5CORE::initialize() { - this->init_lcd_(INITCMD_M5CORE); - if (this->width_ == 0) - this->width_ = 320; - if (this->height_ == 0) - this->height_ = 240; - this->invert_display_(true); -} - -// 24_TFT display -void ILI9XXXILI9341::initialize() { - this->init_lcd_(INITCMD_ILI9341); - if (this->width_ == 0) - this->width_ = 240; - if (this->height_ == 0) - this->height_ = 320; -} -// 24_TFT rotated display -void ILI9XXXILI9342::initialize() { - this->init_lcd_(INITCMD_ILI9341); - if (this->width_ == 0) { - this->width_ = 320; - } - if (this->height_ == 0) { - this->height_ = 240; - } -} - -// 35_TFT display -void ILI9XXXILI9481::initialize() { - this->init_lcd_(INITCMD_ILI9481); - if (this->width_ == 0) { - this->width_ = 480; - } - if (this->height_ == 0) { - this->height_ = 320; - } -} - -// 35_TFT display -void ILI9XXXILI9486::initialize() { - this->init_lcd_(INITCMD_ILI9486); - if (this->width_ == 0) { - this->width_ = 480; - } - if (this->height_ == 0) { - this->height_ = 320; - } -} -// 40_TFT display -void ILI9XXXILI9488::initialize() { - this->init_lcd_(INITCMD_ILI9488); - if (this->width_ == 0) { - this->width_ = 480; - } - if (this->height_ == 0) { - this->height_ = 320; - } - this->is_18bitdisplay_ = true; -} -// 40_TFT display -void ILI9XXXILI9488A::initialize() { - this->init_lcd_(INITCMD_ILI9488_A); - if (this->width_ == 0) { - this->width_ = 480; - } - if (this->height_ == 0) { - this->height_ = 320; - } - this->is_18bitdisplay_ = true; -} -// 40_TFT display -void ILI9XXXST7796::initialize() { - this->init_lcd_(INITCMD_ST7796); - if (this->width_ == 0) { - this->width_ = 320; - } - if (this->height_ == 0) { - this->height_ = 480; - } -} - -// 24_TFT rotated display -void ILI9XXXS3Box::initialize() { - this->init_lcd_(INITCMD_S3BOX); - if (this->width_ == 0) { - this->width_ = 320; - } - if (this->height_ == 0) { - this->height_ = 240; +void ILI9XXXDisplay::invert_colors(bool invert) { + this->pre_invertcolors_ = invert; + if (is_ready()) { + this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } } -// 24_TFT rotated display -void ILI9XXXS3BoxLite::initialize() { - this->init_lcd_(INITCMD_S3BOXLITE); - if (this->width_ == 0) { - this->width_ = 320; - } - if (this->height_ == 0) { - this->height_ = 240; - } - this->invert_display_(true); -} +int ILI9XXXDisplay::get_width_internal() { return this->width_; } +int ILI9XXXDisplay::get_height_internal() { return this->height_; } } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 15b08e6c7610..11a90e142f51 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -1,13 +1,15 @@ #pragma once #include "esphome/components/spi/spi.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display_color_utils.h" #include "ili9xxx_defines.h" #include "ili9xxx_init.h" namespace esphome { namespace ili9xxx { -const uint32_t ILI9XXX_TRANSFER_BUFFER_SIZE = 64; +static const char *const TAG = "ili9xxx"; +const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6 enum ILI9XXXColorMode { BITS_8 = 0x08, @@ -15,28 +17,62 @@ enum ILI9XXXColorMode { BITS_16 = 0x10, }; -#ifndef ILI9XXXDisplay_DATA_RATE -#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ -#endif // ILI9XXXDisplay_DATA_RATE - -class ILI9XXXDisplay : public PollingComponent, - public display::DisplayBuffer, +class ILI9XXXDisplay : public display::DisplayBuffer, public spi::SPIDevice { + spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> { public: + ILI9XXXDisplay() = default; + ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors) + : init_sequence_{init_sequence}, width_{width}, height_{height}, pre_invertcolors_{invert_colors} { + uint8_t cmd, num_args, bits; + const uint8_t *addr = init_sequence; + while ((cmd = *addr++) != 0) { + num_args = *addr++ & 0x7F; + bits = *addr; + esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits); + switch (cmd) { + case ILI9XXX_MADCTL: { + this->swap_xy_ = (bits & MADCTL_MV) != 0; + this->mirror_x_ = (bits & MADCTL_MX) != 0; + this->mirror_y_ = (bits & MADCTL_MY) != 0; + this->color_order_ = (bits & MADCTL_BGR) ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB; + break; + } + + case ILI9XXX_PIXFMT: { + if ((bits & 0xF) == 6) + this->is_18bitdisplay_ = true; + break; + } + + default: + break; + } + addr += num_args; + } + } + 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_palette(const uint8_t *palette) { this->palette_ = palette; } void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; } - void set_dimentions(int16_t width, int16_t height) { + void set_dimensions(int16_t width, int16_t height) { this->height_ = height; this->width_ = width; } - void command(uint8_t value); - void data(uint8_t value); + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + void invert_colors(bool invert); + virtual void command(uint8_t value); + virtual void data(uint8_t value); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); - uint8_t read_command(uint8_t command_byte, uint8_t index); + void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; } + void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; } + void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } + void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } void update() override; @@ -46,20 +82,32 @@ class ILI9XXXDisplay : public PollingComponent, void setup() override; display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + 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; protected: + inline bool check_buffer_() { + if (this->buffer_ == nullptr) { + this->alloc_buffer_(); + return !this->is_failed(); + } + return true; + } + void draw_absolute_pixel_internal(int x, int y, Color color) override; void setup_pins_(); - virtual void initialize() = 0; + virtual void set_madctl(); void display_(); - void init_lcd_(const uint8_t *init_cmd); - void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); - void invert_display_(bool invert); + void init_lcd_(); + void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void reset_(); + uint8_t const *init_sequence_{}; int16_t width_{0}; ///< Display width as modified by current rotation int16_t height_{0}; ///< Display height as modified by current rotation + int16_t offset_x_{0}; + int16_t offset_y_{0}; uint16_t x_low_{0}; uint16_t y_low_{0}; uint16_t x_high_{0}; @@ -76,10 +124,7 @@ class ILI9XXXDisplay : public PollingComponent, void end_command_(); void start_data_(); void end_data_(); - - uint16_t transfer_buffer_[ILI9XXX_TRANSFER_BUFFER_SIZE]; - - uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz); + void alloc_buffer_(); GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_{nullptr}; @@ -88,70 +133,130 @@ class ILI9XXXDisplay : public PollingComponent, bool prossing_update_ = false; bool need_update_ = false; bool is_18bitdisplay_ = false; + bool pre_invertcolors_ = false; + display::ColorOrder color_order_{display::COLOR_ORDER_BGR}; + bool swap_xy_{}; + bool mirror_x_{}; + bool mirror_y_{}; }; //----------- M5Stack display -------------- class ILI9XXXM5Stack : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXM5Stack() : ILI9XXXDisplay(INITCMD_M5STACK, 320, 240, true) {} }; //----------- M5Stack display -------------- class ILI9XXXM5CORE : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXM5CORE() : ILI9XXXDisplay(INITCMD_M5CORE, 320, 240, true) {} +}; + +//----------- ST7789V display -------------- +class ILI9XXXST7789V : public ILI9XXXDisplay { + public: + ILI9XXXST7789V() : ILI9XXXDisplay(INITCMD_ST7789V, 240, 320, false) {} }; //----------- ILI9XXX_24_TFT display -------------- class ILI9XXXILI9341 : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXILI9341() : ILI9XXXDisplay(INITCMD_ILI9341, 240, 320, false) {} }; //----------- ILI9XXX_24_TFT rotated display -------------- class ILI9XXXILI9342 : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXILI9342() : ILI9XXXDisplay(INITCMD_ILI9341, 320, 240, false) {} }; //----------- ILI9XXX_??_TFT rotated display -------------- class ILI9XXXILI9481 : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXILI9481() : ILI9XXXDisplay(INITCMD_ILI9481, 480, 320, false) {} +}; + +//----------- ILI9481 in 18 bit mode -------------- +class ILI9XXXILI948118 : public ILI9XXXDisplay { + public: + ILI9XXXILI948118() : ILI9XXXDisplay(INITCMD_ILI9481_18, 320, 480, true) {} }; //----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXILI9486 : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {} }; -//----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXILI9488 : public ILI9XXXDisplay { + public: + ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {} + protected: - void initialize() override; + void set_madctl() override { + uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; + uint8_t dfun = 0x22; + this->width_ = 320; + this->height_ = 480; + if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) { + // no transforms + } else if (this->mirror_y_ && this->mirror_x_) { + // rotate 180 + dfun = 0x42; + } else if (this->swap_xy_) { + this->width_ = 480; + this->height_ = 320; + mad |= 0x20; + if (this->mirror_x_) { + dfun = 0x02; + } else { + dfun = 0x62; + } + } + this->command(ILI9XXX_DFUNCTR); + this->data(0); + this->data(dfun); + this->command(ILI9XXX_MADCTL); + this->data(mad); + } +}; +//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */ +class WAVESHARERES35 : public ILI9XXXILI9488 { + public: + WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {} + void data(uint8_t value) override { + this->start_data_(); + this->write_byte(0); + this->write_byte(value); + this->end_data_(); + } }; //----------- ILI9XXX_35_TFT origin colors rotated display -------------- class ILI9XXXILI9488A : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXILI9488A() : ILI9XXXDisplay(INITCMD_ILI9488_A, 480, 320, true) {} }; //----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXST7796 : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 320, 480, false) {} }; class ILI9XXXS3Box : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXS3Box() : ILI9XXXDisplay(INITCMD_S3BOX, 320, 240, false) {} }; class ILI9XXXS3BoxLite : public ILI9XXXDisplay { - protected: - void initialize() override; + public: + ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {} +}; + +class ILI9XXXGC9A01A : public ILI9XXXDisplay { + public: + ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} }; } // namespace ili9xxx diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 1856fb06ab35..ea90f83f30fd 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -1,6 +1,8 @@ #pragma once #include "esphome/core/helpers.h" +#include + namespace esphome { namespace ili9xxx { @@ -94,12 +96,36 @@ static const uint8_t PROGMEM INITCMD_ILI9481[] = { ILI9XXX_IFCTR , 1, 0x83, ILI9XXX_GMCTR ,12, 0x00, 0x26, 0x21, 0x00, 0x00, 0x1F, 0x65, 0x23, 0x77, 0x00, 0x0F, 0x00, ILI9XXX_IFMODE , 1, 0x00, // CommandAccessProtect + ILI9XXX_PTLAR , 4, 0, 0, 1, 0xDF, 0xE4 , 1, 0xA0, + ILI9XXX_MADCTL , 1, MADCTL_MV | MADCTL_BGR, // Memory Access Control ILI9XXX_CSCON , 1, 0x01, + ILI9XXX_PIXFMT, 1, 0x55, // 16 bit mode + ILI9XXX_INVON, 0, ILI9XXX_DISPON, 0x80, // Set display on 0x00 // end }; +static const uint8_t PROGMEM INITCMD_ILI9481_18[] = { + ILI9XXX_SLPOUT , 0x80, // Exit sleep mode + ILI9XXX_PWSET , 3, 0x07, 0x41, 0x1D, + ILI9XXX_VMCTR , 3, 0x00, 0x1C, 0x1F, + ILI9XXX_PWSETN , 2, 0x01, 0x11, + ILI9XXX_PWCTR1 , 5, 0x10, 0x3B, 0x00, 0x02, 0x11, + ILI9XXX_VMCTR1 , 1, 0x03, + ILI9XXX_IFCTR , 1, 0x83, + ILI9XXX_GMCTR ,12, 0x00, 0x26, 0x21, 0x00, 0x00, 0x1F, 0x65, 0x23, 0x77, 0x00, 0x0F, 0x00, + ILI9XXX_IFMODE , 1, 0x00, // CommandAccessProtect + ILI9XXX_PTLAR , 4, 0, 0, 1, 0xDF, + 0xE4 , 1, 0xA0, + ILI9XXX_MADCTL , 1, MADCTL_MX| MADCTL_BGR, // Memory Access Control + ILI9XXX_CSCON , 1, 0x01, + ILI9XXX_PIXFMT, 1, 0x66, // 18 bit mode + ILI9XXX_INVON, 0, + ILI9XXX_DISPON, 0x80, // Set display on + 0x00 // end +}; + static const uint8_t PROGMEM INITCMD_ILI9486[] = { ILI9XXX_SLPOUT, 0x80, ILI9XXX_PIXFMT, 1, 0x55, @@ -115,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = { 0x00 // End of list }; -static const uint8_t PROGMEM INITCMD_ILI9488[] = { + +static const uint8_t INITCMD_ILI9488[] = { ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00, ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00, @@ -127,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = { ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot - ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan - 0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 - - ILI9XXX_MADCTL, 1, 0x28, - //ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode - - - - // 5 frames - //ILI9XXX_ETMOD, 1, 0xC6, // - - ILI9XXX_SLPOUT, 0x80, // Exit sleep mode - //ILI9XXX_INVON , 0, ILI9XXX_DISPON, 0x80, // Set display on 0x00 // end }; +static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = { + ILI9XXX_PWCTR3, 1, 0x33, + ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80, + ILI9XXX_FRMCTR1, 1, 0xA0, + ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f, + ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f, + ILI9XXX_PIXFMT, 1, 0x55, + ILI9XXX_SLPOUT, 0x80, // slpout, delay + ILI9XXX_DISPON, 0, + 0x00 // End of list +}; + static const uint8_t PROGMEM INITCMD_ILI9488_A[] = { ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, @@ -263,6 +289,87 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = { 0x00 // End of list }; +static const uint8_t PROGMEM INITCMD_ST7789V[] = { + ILI9XXX_SLPOUT , 0x80, // Exit Sleep + ILI9XXX_DISPON , 0x80, // Display on + ILI9XXX_MADCTL , 1, 0x08, // Memory Access Control, BGR + ILI9XXX_DFUNCTR, 2, 0x0A, 0x82, + ILI9XXX_PIXFMT , 1, 0x55, + ILI9XXX_FRMCTR2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33, + ILI9XXX_ETMOD, 1, 0x35, 0xBB, 1, 0x28, + ILI9XXX_PWCTR1 , 1, 0x0C, // Power control VRH[5:0] + ILI9XXX_PWCTR3 , 2, 0x01, 0xFF, + ILI9XXX_PWCTR4 , 1, 0x10, + ILI9XXX_PWCTR5 , 1, 0x20, + ILI9XXX_IFCTR , 1, 0x0F, + ILI9XXX_PWSET, 2, 0xA4, 0xA1, + ILI9XXX_GMCTRP1 , 14, + 0xd0, 0x00, 0x02, 0x07, 0x0a, + 0x28, 0x32, 0x44, 0x42, 0x06, 0x0e, + 0x12, 0x14, 0x17, + ILI9XXX_GMCTRN1 , 14, + 0xd0, 0x00, 0x02, 0x07, 0x0a, + 0x28, 0x31, 0x54, 0x47, + 0x0e, 0x1c, 0x17, 0x1b, + 0x1e, + ILI9XXX_DISPON , 0x80, // Display on + 0x00 // End of list +}; + +static const uint8_t PROGMEM INITCMD_GC9A01A[] = { + 0xEF, 0, + 0xEB, 1, 0x14, // ? + 0xFE, 0, + 0xEF, 0, + 0xEB, 1, 0x14, // ? + 0x84, 1, 0x40, // ? + 0x85, 1, 0xFF, // ? + 0x86, 1, 0xFF, // ? + 0x87, 1, 0xFF, // ? + 0x88, 1, 0x0A, // ? + 0x89, 1, 0x21, // ? + 0x8A, 1, 0x00, // ? + 0x8B, 1, 0x80, // ? + 0x8C, 1, 0x01, // ? + 0x8D, 1, 0x01, // ? + 0x8E, 1, 0xFF, // ? + 0x8F, 1, 0xFF, // ? + 0xB6, 2, 0x00, 0x00, // ? + 0x90, 4, 0x08, 0x08, 0x08, 0x08, // ? + ILI9XXX_PIXFMT , 1, 0x05, + ILI9XXX_MADCTL , 1, MADCTL_MX| MADCTL_BGR, // Memory Access Control + 0xBD, 1, 0x06, // ? + 0xBC, 1, 0x00, // ? + 0xFF, 3, 0x60, 0x01, 0x04, // ? + 0xC3, 1, 0x13, + 0xC4, 1, 0x13, + 0xF9, 1, 0x22, + 0xBE, 1, 0x11, // ? + 0xE1, 2, 0x10, 0x0E, // ? + 0xDF, 3, 0x21, 0x0c, 0x02, // ? + 0xF0, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, + 0xF1, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, + 0xF2, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, + 0xF3, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, + 0xED, 2, 0x1B, 0x0B, // ? + 0xAE, 1, 0x77, // ? + 0xCD, 1, 0x63, // ? + 0xE8, 1, 0x34, + 0x62, 12, 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, // ? + 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70, + 0x63, 12, 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, // ? + 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70, + 0x64, 7, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07, // ? + 0x66, 10, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00, // ? + 0x67, 10, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98, // ? + 0x74, 7, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00, // ? + 0x98, 2, 0x3e, 0x07, // ? + 0x35, 0, + ILI9XXX_SLPOUT , 0x80, // Exit Sleep + ILI9XXX_DISPON , 0x80, // Display on + 0x00 // End of list +}; + // clang-format on } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 392efb18a282..73dc73aa45d6 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -1,15 +1,23 @@ +from __future__ import annotations + import logging +import hashlib import io from pathlib import Path import re import requests +from magic import Magic + +from PIL import Image from esphome import core from esphome.components import font +from esphome import external_files import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( + __version__, CONF_DITHER, CONF_FILE, CONF_ICON, @@ -19,6 +27,7 @@ CONF_RESIZE, CONF_SOURCE, CONF_TYPE, + CONF_URL, ) from esphome.core import CORE, HexInt @@ -27,6 +36,7 @@ DOMAIN = "image" DEPENDENCIES = ["display"] MULTI_CONF = True +MULTI_CONF_NO_DEFAULT = True image_ns = cg.esphome_ns.namespace("image") @@ -43,34 +53,74 @@ CONF_USE_TRANSPARENCY = "use_transparency" # If the MDI file cannot be downloaded within this time, abort. -MDI_DOWNLOAD_TIMEOUT = 30 # seconds +IMAGE_DOWNLOAD_TIMEOUT = 30 # seconds SOURCE_LOCAL = "local" SOURCE_MDI = "mdi" +SOURCE_WEB = "web" + Image_ = image_ns.class_("Image") -def _compute_local_icon_path(value) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi" +def _compute_local_icon_path(value: dict) -> Path: + base_dir = external_files.compute_local_file_dir(DOMAIN) / "mdi" return base_dir / f"{value[CONF_ICON]}.svg" -def download_mdi(value): - mdi_id = value[CONF_ICON] - path = _compute_local_icon_path(value) - if path.is_file(): - return value - url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg" - _LOGGER.debug("Downloading %s MDI image from %s", mdi_id, url) +def _compute_local_image_path(value: dict) -> Path: + url = value[CONF_URL] + h = hashlib.new("sha256") + h.update(url.encode()) + key = h.hexdigest()[:8] + base_dir = external_files.compute_local_file_dir(DOMAIN) + return base_dir / key + + +def download_content(url: str, path: Path) -> None: + if not external_files.has_remote_file_changed(url, path): + _LOGGER.debug("Remote file has not changed %s", url) + return + + _LOGGER.debug( + "Remote file has changed, downloading from %s to %s", + url, + path, + ) + try: - req = requests.get(url, timeout=MDI_DOWNLOAD_TIMEOUT) + req = requests.get( + url, + timeout=IMAGE_DOWNLOAD_TIMEOUT, + headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, + ) req.raise_for_status() except requests.exceptions.RequestException as e: - raise cv.Invalid(f"Could not download MDI image {mdi_id} from {url}: {e}") + raise cv.Invalid(f"Could not download from {url}: {e}") path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(req.content) + + +def download_mdi(value): + validate_cairosvg_installed(value) + + mdi_id = value[CONF_ICON] + path = _compute_local_icon_path(value) + + url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg" + + download_content(url, path) + + return value + + +def download_image(value): + url = value[CONF_URL] + path = _compute_local_image_path(value) + + download_content(url, path) + return value @@ -139,6 +189,13 @@ def validate_file_shorthand(value): CONF_ICON: icon, } ) + if value.startswith("http://") or value.startswith("https://"): + return FILE_SCHEMA( + { + CONF_SOURCE: SOURCE_WEB, + CONF_URL: value, + } + ) return FILE_SCHEMA( { CONF_SOURCE: SOURCE_LOCAL, @@ -160,10 +217,18 @@ def validate_file_shorthand(value): download_mdi, ) +WEB_SCHEMA = cv.All( + { + cv.Required(CONF_URL): cv.string, + }, + download_image, +) + TYPED_FILE_SCHEMA = cv.typed_schema( { SOURCE_LOCAL: LOCAL_SCHEMA, SOURCE_MDI: MDI_SCHEMA, + SOURCE_WEB: WEB_SCHEMA, }, key=CONF_SOURCE, ) @@ -201,9 +266,7 @@ def _file_schema(value): CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) -def load_svg_image(file: str, resize: tuple[int, int]): - from PIL import Image - +def load_svg_image(file: bytes, resize: tuple[int, int]): # This import is only needed in case of SVG images; adding it # to the top would force configurations not using SVG to also have it # installed for no reason. @@ -212,19 +275,17 @@ def load_svg_image(file: str, resize: tuple[int, int]): if resize: req_width, req_height = resize svg_image = svg2png( - url=file, + file, output_width=req_width, output_height=req_height, ) else: - svg_image = svg2png(url=file) + svg_image = svg2png(file) return Image.open(io.BytesIO(svg_image)) async def to_code(config): - from PIL import Image - conf_file = config[CONF_FILE] if conf_file[CONF_SOURCE] == SOURCE_LOCAL: @@ -233,17 +294,26 @@ async def to_code(config): elif conf_file[CONF_SOURCE] == SOURCE_MDI: path = _compute_local_icon_path(conf_file).as_posix() + elif conf_file[CONF_SOURCE] == SOURCE_WEB: + path = _compute_local_image_path(conf_file).as_posix() + try: - resize = config.get(CONF_RESIZE) - if path.lower().endswith(".svg"): - image = load_svg_image(path, resize) - else: - image = Image.open(path) - if resize: - image.thumbnail(resize) + with open(path, "rb") as f: + file_contents = f.read() except Exception as e: raise core.EsphomeError(f"Could not load image file {path}: {e}") + mime = Magic(mime=True) + file_type = mime.from_buffer(file_contents) + + resize = config.get(CONF_RESIZE) + if "svg" in file_type: + image = load_svg_image(file_contents, resize) + else: + image = Image.open(io.BytesIO(file_contents)) + if resize: + image.thumbnail(resize) + width, height = image.size if CONF_RESIZE not in config and (width > 500 or height > 500): @@ -255,7 +325,11 @@ async def to_code(config): transparent = config[CONF_USE_TRANSPARENCY] - dither = Image.NONE if config[CONF_DITHER] == "NONE" else Image.FLOYDSTEINBERG + dither = ( + Image.Dither.NONE + if config[CONF_DITHER] == "NONE" + else Image.Dither.FLOYDSTEINBERG + ) if config[CONF_TYPE] == "GRAYSCALE": image = image.convert("LA", dither=dither) pixels = list(image.getdata()) diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index 4e869f5204d0..5f1f50a13468 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -37,6 +37,7 @@ class Image : public display::BaseImage { Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const; int get_width() const override; int get_height() const override; + const uint8_t *get_data_start() { return this->data_start_; } ImageType get_type() const; void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index f18a1061fb60..e890187d1a5b 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -21,8 +21,13 @@ std::string ImprovBase::get_formatted_next_url_() { // Ip address pos = this->next_url_.find("{{ip_address}}"); if (pos != std::string::npos) { - std::string ip = network::get_ip_address().str(); - copy.replace(pos, 14, ip); + for (auto &ip : network::get_ip_addresses()) { + if (ip.is_ip4()) { + std::string ipa = ip.str(); + copy.replace(pos, 14, ipa); + break; + } + } } return copy; diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 311256804bf1..2b377d77b881 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -1,10 +1,14 @@ -from esphome.components.logger import USB_CDC, USB_SERIAL_JTAG +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.logger import USB_CDC from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE import esphome.final_validate as fv -from esphome.components import improv_base AUTO_LOAD = ["improv_base"] CODEOWNERS = ["@esphome/core"] @@ -30,7 +34,10 @@ def validate_logger(config): 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: - if logger_conf[CONF_HARDWARE_UART] in [USB_SERIAL_JTAG, USB_CDC]: + if ( + logger_conf[CONF_HARDWARE_UART] == USB_CDC + and get_esp32_variant() == VARIANT_ESP32S3 + ): raise cv.Invalid( "improv_serial does not support the selected logger hardware_uart" ) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index fe19e2f085b6..40297bee6859 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -31,26 +31,57 @@ void ImprovSerialComponent::setup() { void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); } -int ImprovSerialComponent::available_() { +optional ImprovSerialComponent::read_byte_() { + optional byte; + uint8_t data = 0; #ifdef USE_ARDUINO - return this->hw_serial_->available(); + if (this->hw_serial_->available()) { + this->hw_serial_->readBytes(&data, 1); + byte = data; + } #endif #ifdef USE_ESP_IDF - size_t available; - uart_get_buffered_data_len(this->uart_num_, &available); - return available; -#endif -} - -uint8_t ImprovSerialComponent::read_byte_() { - uint8_t data; -#ifdef USE_ARDUINO - this->hw_serial_->readBytes(&data, 1); + switch (logger::global_logger->get_uart()) { + 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) + case logger::UART_SELECTION_UART2: +#endif // !USE_ESP32_VARIANT_ESP32C3 && !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); + if (available) { + uart_read_bytes(this->uart_num_, &data, 1, 0); + byte = data; + } + } + break; +#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) + case logger::UART_SELECTION_USB_CDC: +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + if (esp_usb_console_available_for_read()) { +#else + if (esp_usb_console_read_available()) { #endif -#ifdef USE_ESP_IDF - uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS); + esp_usb_console_read_buf((char *) &data, 1); + byte = data; + } + break; +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) + case logger::UART_SELECTION_USB_SERIAL_JTAG: { + if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) { + byte = data; + } + break; + } +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 + default: + break; + } #endif - return data; + return byte; } void ImprovSerialComponent::write_data_(std::vector &data) { @@ -59,24 +90,49 @@ void ImprovSerialComponent::write_data_(std::vector &data) { this->hw_serial_->write(data.data(), data.size()); #endif #ifdef USE_ESP_IDF - uart_write_bytes(this->uart_num_, data.data(), data.size()); + switch (logger::global_logger->get_uart()) { + 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) + case logger::UART_SELECTION_UART2: +#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 + uart_write_bytes(this->uart_num_, data.data(), data.size()); + break; +#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) + case logger::UART_SELECTION_USB_CDC: { + const char *msg = (char *) data.data(); + esp_usb_console_write_buf(msg, data.size()); + break; + } +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) + case logger::UART_SELECTION_USB_SERIAL_JTAG: + usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS); + break; +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 + default: + break; + } #endif } void ImprovSerialComponent::loop() { - const uint32_t now = millis(); - if (now - this->last_read_byte_ > 50) { + if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) { + this->last_read_byte_ = 0; this->rx_buffer_.clear(); - this->last_read_byte_ = now; + ESP_LOGV(TAG, "Improv Serial timeout"); } - while (this->available_()) { - uint8_t byte = this->read_byte_(); - if (this->parse_improv_serial_byte_(byte)) { - this->last_read_byte_ = now; + auto byte = this->read_byte_(); + while (byte.has_value()) { + if (this->parse_improv_serial_byte_(byte.value())) { + this->last_read_byte_ = millis(); } else { + this->last_read_byte_ = 0; this->rx_buffer_.clear(); } + byte = this->read_byte_(); } if (this->state_ == improv::STATE_PROVISIONING) { @@ -99,9 +155,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: urls.push_back(this->get_formatted_next_url_()); } #ifdef USE_WEBSERVER - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); - urls.push_back(webserver_url); + for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { + if (ip.is_ip4()) { + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); + urls.push_back(webserver_url); + break; + } + } #endif std::vector data = improv::build_rpc_response(command, urls, false); return data; @@ -136,7 +196,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_scanning(); + wifi::global_wifi_component->start_connecting(sta, false); this->set_state_(improv::STATE_PROVISIONING); ESP_LOGD(TAG, "Received Improv wifi 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.h b/esphome/components/improv_serial/improv_serial_component.h index 731f9f998478..8583d0762ba1 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -14,6 +14,13 @@ #endif #ifdef USE_ESP_IDF #include +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ + defined(USE_ESP32_VARIANT_ESP32H2) +#include +#endif +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include +#endif #endif namespace esphome { @@ -26,6 +33,7 @@ enum ImprovSerialType : uint8_t { TYPE_RPC_RESPONSE = 0x04 }; +static const uint16_t IMPROV_SERIAL_TIMEOUT = 100; static const uint8_t IMPROV_SERIAL_VERSION = 1; class ImprovSerialComponent : public Component, public improv_base::ImprovBase { @@ -48,8 +56,7 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase { std::vector build_rpc_settings_response_(improv::Command command); std::vector build_version_info_(); - int available_(); - uint8_t read_byte_(); + optional read_byte_(); void write_data_(std::vector &data); #ifdef USE_ARDUINO diff --git a/esphome/components/ina219/ina219.cpp b/esphome/components/ina219/ina219.cpp index 609f3d0f0864..2fb3bea356af 100644 --- a/esphome/components/ina219/ina219.cpp +++ b/esphome/components/ina219/ina219.cpp @@ -122,7 +122,7 @@ void INA219Component::setup() { this->calibration_lsb_ = lsb; auto calibration = uint32_t(0.04096f / (0.000001 * lsb * this->shunt_resistance_ohm_)); - ESP_LOGV(TAG, " Using LSB=%u calibration=%u", lsb, calibration); + ESP_LOGV(TAG, " Using LSB=%" PRIu32 " calibration=%" PRIu32, lsb, calibration); if (!this->write_byte_16(INA219_REGISTER_CALIBRATION, calibration)) { this->mark_failed(); return; diff --git a/esphome/components/ina219/ina219.h b/esphome/components/ina219/ina219.h index 31cd22375eea..a6c0f2bc4cdf 100644 --- a/esphome/components/ina219/ina219.h +++ b/esphome/components/ina219/ina219.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include + namespace esphome { namespace ina219 { diff --git a/esphome/components/ina226/__init__.py b/esphome/components/ina226/__init__.py index e69de29bb2d1..ca1f2caa31ce 100644 --- a/esphome/components/ina226/__init__.py +++ b/esphome/components/ina226/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Sergio303", "@latonita"] diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp index 1fb859da669e..7a57c118afd3 100644 --- a/esphome/components/ina226/ina226.cpp +++ b/esphome/components/ina226/ina226.cpp @@ -33,31 +33,37 @@ static const uint8_t INA226_REGISTER_POWER = 0x03; static const uint8_t INA226_REGISTER_CURRENT = 0x04; static const uint8_t INA226_REGISTER_CALIBRATION = 0x05; +static const uint16_t INA226_ADC_TIMES[] = {140, 204, 332, 588, 1100, 2116, 4156, 8244}; +static const uint16_t INA226_ADC_AVG_SAMPLES[] = {1, 4, 16, 64, 128, 256, 512, 1024}; + void INA226Component::setup() { ESP_LOGCONFIG(TAG, "Setting up INA226..."); - // Config Register - // 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset) - if (!this->write_byte_16(INA226_REGISTER_CONFIG, 0x8000)) { + + ConfigurationRegister config; + + config.reset = 1; + if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { this->mark_failed(); return; } delay(1); - uint16_t config = 0x0000; + config.raw = 0; + config.reserved = 0b100; // as per datasheet // Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples) - config |= 0b0000001000000000; + config.avg_samples = this->adc_avg_samples_; // Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms) - config |= 0b0000000100000000; + config.bus_voltage_conversion_time = this->adc_time_voltage_; // Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms) - config |= 0b0000000000100000; + config.shunt_voltage_conversion_time = this->adc_time_current_; // Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous) - config |= 0b0000000000000111; + config.mode = 0b111; - if (!this->write_byte_16(INA226_REGISTER_CONFIG, config)) { + if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { this->mark_failed(); return; } @@ -87,6 +93,10 @@ void INA226Component::dump_config() { } LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " ADC Conversion Time Bus Voltage: %d", INA226_ADC_TIMES[this->adc_time_voltage_ & 0b111]); + ESP_LOGCONFIG(TAG, " ADC Conversion Time Shunt Voltage: %d", INA226_ADC_TIMES[this->adc_time_current_ & 0b111]); + ESP_LOGCONFIG(TAG, " ADC Averaging Samples: %d", INA226_ADC_AVG_SAMPLES[this->adc_avg_samples_ & 0b111]); + LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); LOG_SENSOR(" ", "Shunt Voltage", this->shunt_voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); @@ -102,7 +112,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f; + // Convert for 2's compliment and signed value (though always positive) + float bus_voltage_v = this->twos_complement_(raw_bus_voltage, 16); + bus_voltage_v *= 0.00125f; this->bus_voltage_sensor_->publish_state(bus_voltage_v); } @@ -112,7 +124,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; + // Convert for 2's compliment and signed value + float shunt_voltage_v = this->twos_complement_(raw_shunt_voltage, 16); + shunt_voltage_v *= 0.0000025f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); } @@ -122,7 +136,9 @@ void INA226Component::update() { this->status_set_warning(); return; } - float current_ma = int16_t(raw_current) * (this->calibration_lsb_ / 1000.0f); + // Convert for 2's compliment and signed value + float current_ma = this->twos_complement_(raw_current, 16); + current_ma *= (this->calibration_lsb_ / 1000.0f); this->current_sensor_->publish_state(current_ma / 1000.0f); } @@ -139,5 +155,12 @@ void INA226Component::update() { this->status_clear_warning(); } +int32_t INA226Component::twos_complement_(int32_t val, uint8_t bits) { + if (val & ((uint32_t) 1 << (bits - 1))) { + val -= (uint32_t) 1 << bits; + } + return val; +} + } // namespace ina226 } // namespace esphome diff --git a/esphome/components/ina226/ina226.h b/esphome/components/ina226/ina226.h index a551cb34304d..61214fea0e4c 100644 --- a/esphome/components/ina226/ina226.h +++ b/esphome/components/ina226/ina226.h @@ -7,6 +7,40 @@ namespace esphome { namespace ina226 { +enum AdcTime : uint16_t { + ADC_TIME_140US = 0, + ADC_TIME_204US = 1, + ADC_TIME_332US = 2, + ADC_TIME_588US = 3, + ADC_TIME_1100US = 4, + ADC_TIME_2116US = 5, + ADC_TIME_4156US = 6, + ADC_TIME_8244US = 7 +}; + +enum AdcAvgSamples : uint16_t { + ADC_AVG_SAMPLES_1 = 0, + ADC_AVG_SAMPLES_4 = 1, + ADC_AVG_SAMPLES_16 = 2, + ADC_AVG_SAMPLES_64 = 3, + ADC_AVG_SAMPLES_128 = 4, + ADC_AVG_SAMPLES_256 = 5, + ADC_AVG_SAMPLES_512 = 6, + ADC_AVG_SAMPLES_1024 = 7 +}; + +union ConfigurationRegister { + uint16_t raw; + struct { + uint16_t mode : 3; + AdcTime shunt_voltage_conversion_time : 3; + AdcTime bus_voltage_conversion_time : 3; + AdcAvgSamples avg_samples : 3; + uint16_t reserved : 3; + uint16_t reset : 1; + } __attribute__((packed)); +}; + class INA226Component : public PollingComponent, public i2c::I2CDevice { public: void setup() override; @@ -16,6 +50,10 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } + void set_adc_time_voltage(AdcTime time) { adc_time_voltage_ = time; } + void set_adc_time_current(AdcTime time) { adc_time_current_ = time; } + void set_adc_avg_samples(AdcAvgSamples samples) { adc_avg_samples_ = samples; } + void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; } void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } @@ -24,11 +62,16 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { protected: float shunt_resistance_ohm_; float max_current_a_; + AdcTime adc_time_voltage_{AdcTime::ADC_TIME_1100US}; + AdcTime adc_time_current_{AdcTime::ADC_TIME_1100US}; + AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4}; uint32_t calibration_lsb_; sensor::Sensor *bus_voltage_sensor_{nullptr}; sensor::Sensor *shunt_voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; + + int32_t twos_complement_(int32_t val, uint8_t bits); }; } // namespace ina226 diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py index ee4036ce7ebf..0accc58c005a 100644 --- a/esphome/components/ina226/sensor.py +++ b/esphome/components/ina226/sensor.py @@ -16,15 +16,49 @@ UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, + CONF_VOLTAGE, ) DEPENDENCIES = ["i2c"] +CONF_ADC_AVERAGING = "adc_averaging" +CONF_ADC_TIME = "adc_time" + ina226_ns = cg.esphome_ns.namespace("ina226") INA226Component = ina226_ns.class_( "INA226Component", cg.PollingComponent, i2c.I2CDevice ) +AdcTime = ina226_ns.enum("AdcTime") +ADC_TIMES = { + 140: AdcTime.ADC_TIME_140US, + 204: AdcTime.ADC_TIME_204US, + 332: AdcTime.ADC_TIME_332US, + 588: AdcTime.ADC_TIME_588US, + 1100: AdcTime.ADC_TIME_1100US, + 2116: AdcTime.ADC_TIME_2116US, + 4156: AdcTime.ADC_TIME_4156US, + 8244: AdcTime.ADC_TIME_8244US, +} + +AdcAvgSamples = ina226_ns.enum("AdcAvgSamples") +ADC_AVG_SAMPLES = { + 1: AdcAvgSamples.ADC_AVG_SAMPLES_1, + 4: AdcAvgSamples.ADC_AVG_SAMPLES_4, + 16: AdcAvgSamples.ADC_AVG_SAMPLES_16, + 64: AdcAvgSamples.ADC_AVG_SAMPLES_64, + 128: AdcAvgSamples.ADC_AVG_SAMPLES_128, + 256: AdcAvgSamples.ADC_AVG_SAMPLES_256, + 512: AdcAvgSamples.ADC_AVG_SAMPLES_512, + 1024: AdcAvgSamples.ADC_AVG_SAMPLES_1024, +} + + +def validate_adc_time(value): + value = cv.positive_time_period_microseconds(value).total_microseconds + return cv.enum(ADC_TIMES, int=True)(value) + + CONFIG_SCHEMA = ( cv.Schema( { @@ -59,6 +93,18 @@ cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( cv.current, cv.Range(min=0.0) ), + cv.Optional(CONF_ADC_TIME, default="1100 us"): cv.Any( + validate_adc_time, + cv.Schema( + { + cv.Required(CONF_VOLTAGE): validate_adc_time, + cv.Required(CONF_CURRENT): validate_adc_time, + } + ), + ), + cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum( + ADC_AVG_SAMPLES, int=True + ), } ) .extend(cv.polling_component_schema("60s")) @@ -72,9 +118,18 @@ async def to_code(config): await i2c.register_i2c_device(var, config) cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE])) - cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT])) + adc_time_config = config[CONF_ADC_TIME] + if isinstance(adc_time_config, dict): + cg.add(var.set_adc_time_voltage(adc_time_config[CONF_VOLTAGE])) + cg.add(var.set_adc_time_current(adc_time_config[CONF_CURRENT])) + else: + cg.add(var.set_adc_time_voltage(adc_time_config)) + cg.add(var.set_adc_time_current(adc_time_config)) + + cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING])) + if CONF_BUS_VOLTAGE in config: sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE]) cg.add(var.set_bus_voltage_sensor(sens)) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index aa11fb31722b..cdd0b5ade590 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -3,6 +3,7 @@ from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_BATTERY_LEVEL, + CONF_EXTERNAL_TEMPERATURE, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, @@ -19,8 +20,6 @@ CODEOWNERS = ["@fkirill"] DEPENDENCIES = ["esp32_ble_tracker"] -CONF_EXTERNAL_TEMPERATURE = "external_temperature" - inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index f05169ea2ee2..58a146d2fd0f 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -7,6 +7,7 @@ CONF_ID, CONF_LAMBDA, CONF_MODEL, + CONF_OE_PIN, CONF_PAGES, CONF_WAKEUP_PIN, ) @@ -29,7 +30,6 @@ CONF_GMOD_PIN = "gmod_pin" CONF_GPIO0_ENABLE_PIN = "gpio0_enable_pin" CONF_LE_PIN = "le_pin" -CONF_OE_PIN = "oe_pin" CONF_PARTIAL_UPDATING = "partial_updating" CONF_POWERUP_PIN = "powerup_pin" CONF_SPH_PIN = "sph_pin" @@ -39,7 +39,11 @@ inkplate6_ns = cg.esphome_ns.namespace("inkplate6") Inkplate6 = inkplate6_ns.class_( - "Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer + "Inkplate6", + cg.PollingComponent, + i2c.I2CDevice, + display.Display, + display.DisplayBuffer, ) InkplateModel = inkplate6_ns.enum("InkplateModel") @@ -110,7 +114,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 92a226de8719..f4d0fedf83a3 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -55,6 +55,9 @@ void Inkplate6::setup() { this->wakeup_pin_->digital_write(false); } +/** + * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. + */ void Inkplate6::initialize_() { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 565bd74710b0..2946c89e1c86 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -17,7 +17,7 @@ enum InkplateModel : uint8_t { INKPLATE_6_V2 = 3, }; -class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { +class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice { public: const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, 0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55}; @@ -68,8 +68,9 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void set_greyscale(bool greyscale) { this->greyscale_ = greyscale; - this->initialize_(); this->block_partial_ = true; + if (this->is_ready()) + this->initialize_(); } void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index a38770826366..47f516f56850 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -14,6 +14,11 @@ uint8_t temprature_sens_read(); #ifdef USE_RP2040 #include "Arduino.h" #endif // USE_RP2040 +#ifdef USE_BK72XX +extern "C" { +uint32_t temp_single_get_current_temperature(uint32_t *temp_value); +} +#endif // USE_BK72XX namespace esphome { namespace internal_temperature { @@ -46,6 +51,18 @@ void InternalTemperatureSensor::update() { temperature = analogReadTemp(); success = (temperature != 0.0f); #endif // USE_RP2040 +#ifdef USE_BK72XX + uint32_t raw, result; + result = temp_single_get_current_temperature(&raw); + success = (result == 0); +#if defined(USE_LIBRETINY_VARIANT_BK7231N) + temperature = raw * -0.38f + 156.0f; +#elif defined(USE_LIBRETINY_VARIANT_BK7231T) + temperature = raw * 0.04f; +#else // USE_LIBRETINY_VARIANT + temperature = raw * 0.128f; +#endif // USE_LIBRETINY_VARIANT +#endif // USE_BK72XX if (success && std::isfinite(temperature)) { this->publish_state(temperature); } else { diff --git a/esphome/components/internal_temperature/sensor.py b/esphome/components/internal_temperature/sensor.py index 8d462bd80175..809d7a40b9c8 100644 --- a/esphome/components/internal_temperature/sensor.py +++ b/esphome/components/internal_temperature/sensor.py @@ -12,6 +12,9 @@ ENTITY_CATEGORY_DIAGNOSTIC, KEY_CORE, KEY_FRAMEWORK_VERSION, + PLATFORM_ESP32, + PLATFORM_RP2040, + PLATFORM_BK72XX, ) from esphome.core import CORE @@ -49,7 +52,7 @@ def validate_config(config): state_class=STATE_CLASS_MEASUREMENT, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ).extend(cv.polling_component_schema("60s")), - cv.only_on(["esp32", "rp2040"]), + cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]), validate_config, ) diff --git a/esphome/components/interval/__init__.py b/esphome/components/interval/__init__.py index 4514f80ba328..db3232c4b02e 100644 --- a/esphome/components/interval/__init__.py +++ b/esphome/components/interval/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_INTERVAL +from esphome.const import CONF_ID, CONF_INTERVAL, CONF_STARTUP_DELAY CODEOWNERS = ["@esphome/core"] interval_ns = cg.esphome_ns.namespace("interval") @@ -13,6 +13,9 @@ cv.Schema( { cv.GenerateID(): cv.declare_id(IntervalTrigger), + cv.Optional( + CONF_STARTUP_DELAY, default="0s" + ): cv.positive_time_period_milliseconds, cv.Required(CONF_INTERVAL): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA) @@ -26,3 +29,4 @@ async def to_code(config): await automation.build_automation(var, [], conf) cg.add(var.set_update_interval(conf[CONF_INTERVAL])) + cg.add(var.set_startup_delay(conf[CONF_STARTUP_DELAY])) diff --git a/esphome/components/interval/interval.h b/esphome/components/interval/interval.h index 605ac868f339..5b8bc3081f5f 100644 --- a/esphome/components/interval/interval.h +++ b/esphome/components/interval/interval.h @@ -8,8 +8,26 @@ namespace interval { class IntervalTrigger : public Trigger<>, public PollingComponent { public: - void update() override { this->trigger(); } + void update() override { + if (this->started_) + this->trigger(); + } + + void setup() override { + if (this->startup_delay_ == 0) { + this->started_ = true; + } else { + this->set_timeout(this->startup_delay_, [this] { this->started_ = true; }); + } + } + + void set_startup_delay(const uint32_t startup_delay) { this->startup_delay_ = startup_delay; } + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + uint32_t startup_delay_{0}; + bool started_{false}; }; } // namespace interval diff --git a/esphome/components/jsn_sr04t/__init__.py b/esphome/components/jsn_sr04t/__init__.py new file mode 100644 index 000000000000..ef6335f31671 --- /dev/null +++ b/esphome/components/jsn_sr04t/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Mafus1"] diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp new file mode 100644 index 000000000000..70e21a137dc0 --- /dev/null +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -0,0 +1,58 @@ +#include "jsn_sr04t.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2 + +namespace esphome { +namespace jsn_sr04t { + +static const char *const TAG = "jsn_sr04t.sensor"; + +void Jsnsr04tComponent::update() { + this->write_byte(0x55); + ESP_LOGV(TAG, "Request read out from sensor"); +} + +void Jsnsr04tComponent::loop() { + while (this->available() > 0) { + uint8_t data; + this->read_byte(&data); + + ESP_LOGV(TAG, "Read byte from sensor: %x", data); + + if (this->buffer_.empty() && data != 0xFF) + continue; + + this->buffer_.push_back(data); + if (this->buffer_.size() == 4) + this->check_buffer_(); + } +} + +void Jsnsr04tComponent::check_buffer_() { + 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) { + float meters = distance / 1000.0f; + ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters); + this->publish_state(meters); + } else { + ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + } + } else { + ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); + } + this->buffer_.clear(); +} + +void Jsnsr04tComponent::dump_config() { + LOG_SENSOR("", "JST_SR04T Sensor", this); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace jsn_sr04t +} // namespace esphome diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.h b/esphome/components/jsn_sr04t/jsn_sr04t.h new file mode 100644 index 000000000000..bd43252be8d6 --- /dev/null +++ b/esphome/components/jsn_sr04t/jsn_sr04t.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace jsn_sr04t { + +class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public uart::UARTDevice { + public: + // Nothing really public. + + // ========== INTERNAL METHODS ========== + void update() override; + void loop() override; + void dump_config() override; + + protected: + void check_buffer_(); + + std::vector buffer_; +}; + +} // namespace jsn_sr04t +} // namespace esphome diff --git a/esphome/components/jsn_sr04t/sensor.py b/esphome/components/jsn_sr04t/sensor.py new file mode 100644 index 000000000000..4b062e81e938 --- /dev/null +++ b/esphome/components/jsn_sr04t/sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + STATE_CLASS_MEASUREMENT, + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, +) + +CODEOWNERS = ["@Mafus1"] +DEPENDENCIES = ["uart"] + +jsn_sr04t_ns = cg.esphome_ns.namespace("jsn_sr04t") +Jsnsr04tComponent = jsn_sr04t_ns.class_( + "Jsnsr04tComponent", sensor.Sensor, cg.PollingComponent, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + Jsnsr04tComponent, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "jsn_sr04t", + baud_rate=9600, + require_tx=True, + require_rx=True, + data_bits=8, + parity=None, + stop_bits=1, +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/kalman_combinator/__init__.py b/esphome/components/kalman_combinator/__init__.py index 3356e61bb259..e69de29bb2d1 100644 --- a/esphome/components/kalman_combinator/__init__.py +++ b/esphome/components/kalman_combinator/__init__.py @@ -1 +0,0 @@ -CODEOWNERS = ["@Cat-Ion"] diff --git a/esphome/components/kalman_combinator/kalman_combinator.cpp b/esphome/components/kalman_combinator/kalman_combinator.cpp deleted file mode 100644 index 50d8f03a930d..000000000000 --- a/esphome/components/kalman_combinator/kalman_combinator.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "kalman_combinator.h" -#include "esphome/core/hal.h" -#include -#include - -namespace esphome { -namespace kalman_combinator { - -void KalmanCombinatorComponent::dump_config() { - ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:"); - ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_); - ESP_LOGCONFIG("kalman_combinator", " Sensors:"); - for (const auto &sensor : this->sensors_) { - auto &entity = *sensor.first; - ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str()); - } -} - -void KalmanCombinatorComponent::setup() { - for (const auto &sensor : this->sensors_) { - const auto stddev = sensor.second; - sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); }); - } -} - -void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function const &stddev) { - this->sensors_.emplace_back(sensor, stddev); -} - -void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) { - this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); -} - -void KalmanCombinatorComponent::update_variance_() { - uint32_t now = millis(); - - // Variance increases by update_variance_ each millisecond - auto dt = now - this->last_update_; - auto dv = this->update_variance_value_ * dt; - this->variance_ += dv; - this->last_update_ = now; -} - -void KalmanCombinatorComponent::correct_(float value, float stddev) { - if (std::isnan(value) || std::isinf(stddev)) { - return; - } - - if (std::isnan(this->state_) || std::isinf(this->variance_)) { - this->state_ = value; - this->variance_ = stddev * stddev; - if (this->std_dev_sensor_ != nullptr) { - this->std_dev_sensor_->publish_state(stddev); - } - return; - } - - this->update_variance_(); - - // Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu - // Use the value with the smaller variance as mu1 to prevent precision errors - const bool this_first = this->variance_ < (stddev * stddev); - const float mu1 = this_first ? this->state_ : value; - const float mu2 = this_first ? value : this->state_; - - const float var1 = this_first ? this->variance_ : stddev * stddev; - const float var2 = this_first ? stddev * stddev : this->variance_; - - const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2); - const float var = var1 - (var1 * var1) / (var1 + var2); - - // Update and publish state - this->state_ = mu; - this->variance_ = var; - - this->publish_state(mu); - if (this->std_dev_sensor_ != nullptr) { - this->std_dev_sensor_->publish_state(std::sqrt(var)); - } -} -} // namespace kalman_combinator -} // namespace esphome diff --git a/esphome/components/kalman_combinator/kalman_combinator.h b/esphome/components/kalman_combinator/kalman_combinator.h deleted file mode 100644 index afbe3ece927d..000000000000 --- a/esphome/components/kalman_combinator/kalman_combinator.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include -#include - -namespace esphome { -namespace kalman_combinator { - -class KalmanCombinatorComponent : public Component, public sensor::Sensor { - public: - KalmanCombinatorComponent() = default; - - float get_setup_priority() const override { return esphome::setup_priority::DATA; } - - void dump_config() override; - void setup() override; - - void add_source(Sensor *sensor, std::function const &stddev); - void add_source(Sensor *sensor, float stddev); - void set_process_std_dev(float process_std_dev) { - this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f; - } - void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; } - - private: - void update_variance_(); - void correct_(float value, float stddev); - - // Source sensors and their error functions - std::vector>> sensors_; - - // Optional sensor for publishing the current error - sensor::Sensor *std_dev_sensor_{nullptr}; - - // Tick of the last update - uint32_t last_update_{0}; - // Change of the variance, per ms - float update_variance_value_{0.f}; - - // Best guess for the state and its variance - float state_{NAN}; - float variance_{INFINITY}; -}; -} // namespace kalman_combinator -} // namespace esphome diff --git a/esphome/components/kalman_combinator/sensor.py b/esphome/components/kalman_combinator/sensor.py index 28b96077ccbc..eca1ba7b8524 100644 --- a/esphome/components/kalman_combinator/sensor.py +++ b/esphome/components/kalman_combinator/sensor.py @@ -1,90 +1,6 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor -from esphome.const import ( - CONF_ID, - CONF_SOURCE, - CONF_ACCURACY_DECIMALS, - CONF_DEVICE_CLASS, - CONF_ENTITY_CATEGORY, - CONF_ICON, - CONF_UNIT_OF_MEASUREMENT, -) -from esphome.core.entity_helpers import inherit_property_from - -kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator") -KalmanCombinatorComponent = kalman_combinator_ns.class_( - "KalmanCombinatorComponent", cg.Component, sensor.Sensor -) - -CONF_ERROR = "error" -CONF_SOURCES = "sources" -CONF_PROCESS_STD_DEV = "process_std_dev" -CONF_STD_DEV = "std_dev" - - -CONFIG_SCHEMA = ( - sensor.sensor_schema(KalmanCombinatorComponent) - .extend(cv.COMPONENT_SCHEMA) - .extend( - { - cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, - cv.Required(CONF_SOURCES): cv.ensure_list( - cv.Schema( - { - cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), - cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), - } - ), - ), - cv.Optional(CONF_STD_DEV): sensor.sensor_schema(), - } - ) -) - -# Inherit some sensor values from the first source, for both the state and the error value -properties_to_inherit = [ - CONF_ACCURACY_DECIMALS, - CONF_DEVICE_CLASS, - CONF_ENTITY_CATEGORY, - CONF_ICON, - CONF_UNIT_OF_MEASUREMENT, - # CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing" -] -inherit_schema_for_state = [ - inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE]) - for property in properties_to_inherit -] -inherit_schema_for_std_dev = [ - inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE]) - for property in properties_to_inherit -] -FINAL_VALIDATE_SCHEMA = cv.All( - CONFIG_SCHEMA.extend( - {cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)}, - extra=cv.ALLOW_EXTRA, - ), - *inherit_schema_for_state, - *inherit_schema_for_std_dev, +CONFIG_SCHEMA = 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" ) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await sensor.register_sensor(var, config) - - cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV])) - for source_conf in config[CONF_SOURCES]: - source = await cg.get_variable(source_conf[CONF_SOURCE]) - error = await cg.templatable( - source_conf[CONF_ERROR], - [(float, "x")], - cg.float_, - ) - cg.add(var.add_source(source, error)) - - if CONF_STD_DEV in config: - sens = await sensor.new_sensor(config[CONF_STD_DEV]) - cg.add(var.set_std_dev_sensor(sens)) diff --git a/esphome/components/kamstrup_kmp/__init__.py b/esphome/components/kamstrup_kmp/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp new file mode 100644 index 000000000000..b870d1b56d86 --- /dev/null +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp @@ -0,0 +1,301 @@ +#include "kamstrup_kmp.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace kamstrup_kmp { + +static const char *const TAG = "kamstrup_kmp"; + +void KamstrupKMPComponent::dump_config() { + ESP_LOGCONFIG(TAG, "kamstrup_kmp:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with Kamstrup meter failed!"); + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_); + LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_); + LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_); + LOG_SENSOR(" ", "Flow", this->flow_sensor_); + LOG_SENSOR(" ", "Volume", this->volume_sensor_); + + for (int i = 0; i < this->custom_sensors_.size(); i++) { + LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]); + ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]); + } + + this->check_uart_settings(1200, 2, uart::UART_CONFIG_PARITY_NONE, 8); +} + +float KamstrupKMPComponent::get_setup_priority() const { return setup_priority::DATA; } + +void KamstrupKMPComponent::update() { + if (this->heat_energy_sensor_ != nullptr) { + this->command_queue_.push(CMD_HEAT_ENERGY); + } + + if (this->power_sensor_ != nullptr) { + this->command_queue_.push(CMD_POWER); + } + + if (this->temp1_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP1); + } + + if (this->temp2_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP2); + } + + if (this->temp_diff_sensor_ != nullptr) { + this->command_queue_.push(CMD_TEMP_DIFF); + } + + if (this->flow_sensor_ != nullptr) { + this->command_queue_.push(CMD_FLOW); + } + + if (this->volume_sensor_ != nullptr) { + this->command_queue_.push(CMD_VOLUME); + } + + for (uint16_t custom_command : this->custom_commands_) { + this->command_queue_.push(custom_command); + } +} + +void KamstrupKMPComponent::loop() { + if (!this->command_queue_.empty()) { + uint16_t command = this->command_queue_.front(); + this->send_command_(command); + this->command_queue_.pop(); + } +} + +void KamstrupKMPComponent::send_command_(uint16_t command) { + uint32_t msg_len = 5; + uint8_t msg[msg_len]; + + msg[0] = 0x3F; + msg[1] = 0x10; + msg[2] = 0x01; + msg[3] = command >> 8; + msg[4] = command & 0xFF; + + this->clear_uart_rx_buffer_(); + this->send_message_(msg, msg_len); + this->read_command_(command); +} + +void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) { + int buffer_len = msg_len + 2; + uint8_t buffer[buffer_len]; + + // Prepare the basic message and appand CRC + for (int i = 0; i < msg_len; i++) { + buffer[i] = msg[i]; + } + + buffer[buffer_len - 2] = 0; + buffer[buffer_len - 1] = 0; + + uint16_t crc = crc16_ccitt(buffer, buffer_len); + buffer[buffer_len - 2] = crc >> 8; + buffer[buffer_len - 1] = crc & 0xFF; + + // Prepare actual TX message + uint8_t tx_msg[20]; + int tx_msg_len = 1; + tx_msg[0] = 0x80; // prefix + + for (int i = 0; i < buffer_len; i++) { + if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) { + tx_msg[tx_msg_len++] = 0x1b; + tx_msg[tx_msg_len++] = buffer[i] ^ 0xff; + } else { + tx_msg[tx_msg_len++] = buffer[i]; + } + } + + tx_msg[tx_msg_len++] = 0x0D; // EOM + + this->write_array(tx_msg, tx_msg_len); +} + +void KamstrupKMPComponent::clear_uart_rx_buffer_() { + uint8_t tmp; + while (this->available()) { + this->read_byte(&tmp); + } +} + +void KamstrupKMPComponent::read_command_(uint16_t command) { + uint8_t buffer[20] = {0}; + int buffer_len = 0; + int data; + int timeout = 250; // ms + + // Read the data from the UART + while (timeout > 0) { + if (this->available()) { + data = this->read(); + if (data > -1) { + if (data == 0x40) { // start of message + buffer_len = 0; + } + buffer[buffer_len++] = (uint8_t) data; + if (data == 0x0D) { + break; + } + } else { + ESP_LOGE(TAG, "Error while reading from UART"); + } + } else { + delay(1); + timeout--; + } + } + + if (timeout == 0 || buffer_len == 0) { + ESP_LOGE(TAG, "Request timed out"); + return; + } + + // Validate message (prefix and suffix) + if (buffer[0] != 0x40) { + ESP_LOGE(TAG, "Received invalid message (prefix mismatch received 0x%02X, expected 0x40)", buffer[0]); + return; + } + + if (buffer[buffer_len - 1] != 0x0D) { + ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]); + return; + } + + // Decode + uint8_t msg[20] = {0}; + int msg_len = 0; + for (int i = 1; i < buffer_len - 1; i++) { + if (buffer[i] == 0x1B) { + msg[msg_len++] = buffer[i + 1] ^ 0xFF; + i++; + } else { + msg[msg_len++] = buffer[i]; + } + } + + // Validate CRC + if (crc16_ccitt(msg, msg_len)) { + ESP_LOGE(TAG, "Received invalid message (CRC mismatch)"); + return; + } + + // All seems good. Now parse the message + this->parse_command_message_(command, msg, msg_len); +} + +void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) { + // Validate the message + if (msg_len < 8) { + ESP_LOGE(TAG, "Received invalid message (message too small)"); + return; + } + + if (msg[0] != 0x3F || msg[1] != 0x10) { + ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]); + return; + } + + uint16_t recv_command = msg[2] << 8 | msg[3]; + if (recv_command != command) { + ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)", + recv_command, command); + return; + } + + uint8_t unit_idx = msg[4]; + uint8_t mantissa_range = msg[5]; + + if (mantissa_range > 4) { + ESP_LOGE(TAG, "Received invalid message (mantissa size too large %d, expected 4)", mantissa_range); + return; + } + + // Calculate exponent + float exponent = msg[6] & 0x3F; + if (msg[6] & 0x40) { + exponent = -exponent; + } + exponent = powf(10, exponent); + if (msg[6] & 0x80) { + exponent = -exponent; + } + + // Calculate mantissa + uint32_t mantissa = 0; + for (int i = 0; i < mantissa_range; i++) { + mantissa <<= 8; + mantissa |= msg[i + 7]; + } + + // Calculate the actual value + float value = mantissa * exponent; + + // Set sensor value + this->set_sensor_value_(command, value, unit_idx); +} + +void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) { + const char *unit = UNITS[unit_idx]; + + // Standard sensors + if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) { + this->heat_energy_sensor_->publish_state(value); + } else if (command == CMD_POWER && this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(value); + } else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) { + this->temp1_sensor_->publish_state(value); + } else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) { + this->temp2_sensor_->publish_state(value); + } else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) { + this->temp_diff_sensor_->publish_state(value); + } else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) { + this->flow_sensor_->publish_state(value); + } else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) { + this->volume_sensor_->publish_state(value); + } + + // Custom sensors + for (int i = 0; i < this->custom_commands_.size(); i++) { + if (command == this->custom_commands_[i]) { + this->custom_sensors_[i]->publish_state(value); + } + } + + ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit); +} + +uint16_t crc16_ccitt(const uint8_t *buffer, int len) { + uint32_t poly = 0x1021; + uint32_t reg = 0x00; + for (int i = 0; i < len; i++) { + int mask = 0x80; + while (mask > 0) { + reg <<= 1; + if (buffer[i] & mask) { + reg |= 1; + } + mask >>= 1; + if (reg & 0x10000) { + reg &= 0xffff; + reg ^= poly; + } + } + } + return (uint16_t) reg; +} + +} // namespace kamstrup_kmp +} // namespace esphome diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.h b/esphome/components/kamstrup_kmp/kamstrup_kmp.h new file mode 100644 index 000000000000..c9cc9c5a3945 --- /dev/null +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.h @@ -0,0 +1,131 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace kamstrup_kmp { + +/* + =========================================================================== + === KAMSTRUP KMP === + =========================================================================== + + Kamstrup Meter Protocol (KMP) is a protocol used with Kamstrup district + heating meters, e.g. Kamstrup MULTICAL 403. + These devices register consumed heat from a district heating system. + It does this by measuring the incoming and outgoing water temperature + and by measuring the water flow. The temperature difference (delta T) + together with the water flow results in consumed energy, typically + in giga joule (GJ). + + The Kamstrup Multical has an optical interface just above the display. + This interface is essentially an RS-232 interface using a proprietary + protocol (Kamstrup Meter Protocol [KMP]). + + The integration uses this optical interface to periodically read the + configured values (sensors) from the meter. Supported sensors are: + - Heat Energy [GJ] + - Current Power Consumption [kW] + - Temperature 1 [°C] + - Temperature 2 [°C] + - Temperature Difference [°K] + - Water Flow [l/h] + - Volume [m3] + + Apart from these supported 'fixed' sensors, the user can configure up to + five custom sensors. The KMP command (16 bit unsigned int) has to be + provided in that case. + + Note: + The optical interface is enabled as soon as a button on the meter is pushed. + The interface stays active for a few minutes. To keep the interface 'alive' + magnets must be placed around the optical sensor. + + Units: + Units are set using the regular Sensor config in the user yaml. However, + KMP does also send the correct unit with every value. When DEBUG logging + is enabled, the received value with the received unit are logged. + + Acknowledgement: + This interface was inspired by: + - https://atomstar.tweakblogs.net/blog/19110/reading-out-kamstrup-multical-402-403-with-home-built-optical-head + - https://wiki.hal9k.dk/projects/kamstrup +*/ + +// KMP Commands +static const uint16_t CMD_HEAT_ENERGY = 0x003C; +static const uint16_t CMD_POWER = 0x0050; +static const uint16_t CMD_TEMP1 = 0x0056; +static const uint16_t CMD_TEMP2 = 0x0057; +static const uint16_t CMD_TEMP_DIFF = 0x0059; +static const uint16_t CMD_FLOW = 0x004A; +static const uint16_t CMD_VOLUME = 0x0044; + +// KMP units +static const char *const UNITS[] = { + "", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal", + "kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh", + "GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA", + "kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l", + "m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd", + "mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield", + "s", "ms", "days", "RTC-Q", "Datetime"}; + +class KamstrupKMPComponent : public PollingComponent, public uart::UARTDevice { + public: + void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; } + void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } + void set_temp1_sensor(sensor::Sensor *sensor) { this->temp1_sensor_ = sensor; } + void set_temp2_sensor(sensor::Sensor *sensor) { this->temp2_sensor_ = sensor; } + void set_temp_diff_sensor(sensor::Sensor *sensor) { this->temp_diff_sensor_ = sensor; } + void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; } + void set_volume_sensor(sensor::Sensor *sensor) { this->volume_sensor_ = sensor; } + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void loop() override; + void add_custom_sensor(sensor::Sensor *sensor, uint16_t command) { + this->custom_sensors_.push_back(sensor); + this->custom_commands_.push_back(command); + } + + protected: + // Sensors + sensor::Sensor *heat_energy_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *temp1_sensor_{nullptr}; + sensor::Sensor *temp2_sensor_{nullptr}; + sensor::Sensor *temp_diff_sensor_{nullptr}; + sensor::Sensor *flow_sensor_{nullptr}; + sensor::Sensor *volume_sensor_{nullptr}; + + // Custom sensors and commands + std::vector custom_sensors_; + std::vector custom_commands_; + + // Command queue + std::queue command_queue_; + + // Methods + + // Sends a command to the meter and receives its response + void send_command_(uint16_t command); + // Sends a message to the meter. A prefix/suffix and CRC are added + void send_message_(const uint8_t *msg, int msg_len); + // Clears and data that might be in the UART Rx buffer + void clear_uart_rx_buffer_(); + // Reads and validates the response to a send command + void read_command_(uint16_t command); + // Parses a received message + void parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len); + // Sets the received value to the correct sensor + void set_sensor_value_(uint16_t command, float value, uint8_t unit_idx); +}; + +// "true" CCITT CRC-16 +uint16_t crc16_ccitt(const uint8_t *buffer, int len); + +} // namespace kamstrup_kmp +} // namespace esphome diff --git a/esphome/components/kamstrup_kmp/sensor.py b/esphome/components/kamstrup_kmp/sensor.py new file mode 100644 index 000000000000..c9024e4a2b26 --- /dev/null +++ b/esphome/components/kamstrup_kmp/sensor.py @@ -0,0 +1,132 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_COMMAND, + CONF_CUSTOM, + CONF_FLOW, + CONF_ID, + CONF_POWER, + CONF_VOLUME, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLUME, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_CELSIUS, + UNIT_CUBIC_METER, + UNIT_EMPTY, + UNIT_KELVIN, + UNIT_KILOWATT, +) + +CODEOWNERS = ["@cfeenstra1024"] +DEPENDENCIES = ["uart"] + +kamstrup_kmp_ns = cg.esphome_ns.namespace("kamstrup_kmp") +KamstrupKMPComponent = kamstrup_kmp_ns.class_( + "KamstrupKMPComponent", cg.PollingComponent, uart.UARTDevice +) + +CONF_HEAT_ENERGY = "heat_energy" +CONF_TEMP1 = "temp1" +CONF_TEMP2 = "temp2" +CONF_TEMP_DIFF = "temp_diff" + +UNIT_GIGA_JOULE = "GJ" +UNIT_LITRE_PER_HOUR = "l/h" + +# Note: The sensor units are set automatically based un the received data from the meter +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(KamstrupKMPComponent), + cv.Optional(CONF_HEAT_ENERGY): sensor.sensor_schema( + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_GIGA_JOULE, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOWATT, + ), + cv.Optional(CONF_TEMP1): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + ), + cv.Optional(CONF_TEMP2): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + ), + cv.Optional(CONF_TEMP_DIFF): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KELVIN, + ), + cv.Optional(CONF_FLOW): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLUME, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LITRE_PER_HOUR, + ), + cv.Optional(CONF_VOLUME): sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLUME, + state_class=STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + ), + cv.Optional(CONF_CUSTOM): cv.ensure_list( + sensor.sensor_schema( + accuracy_decimals=1, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_EMPTY, + ).extend({cv.Required(CONF_COMMAND): cv.hex_uint16_t}) + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "kamstrup_kmp", baud_rate=1200, 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) + + # Standard sensors + for key in [ + CONF_HEAT_ENERGY, + CONF_POWER, + CONF_TEMP1, + CONF_TEMP2, + CONF_TEMP_DIFF, + CONF_FLOW, + CONF_VOLUME, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) + + # Custom sensors + if CONF_CUSTOM in config: + for conf in config[CONF_CUSTOM]: + sens = await sensor.new_sensor(conf) + cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND])) diff --git a/esphome/components/kmeteriso/sensor.py b/esphome/components/kmeteriso/sensor.py index e730e446ae15..082a055701d1 100644 --- a/esphome/components/kmeteriso/sensor.py +++ b/esphome/components/kmeteriso/sensor.py @@ -3,6 +3,7 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, + CONF_INTERNAL_TEMPERATURE, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -10,7 +11,6 @@ ENTITY_CATEGORY_DIAGNOSTIC, ) -CONF_INTERNAL_TEMPERATURE = "internal_temperature" DEPENDENCIES = ["i2c"] kmeteriso_ns = cg.esphome_ns.namespace("kmeteriso") diff --git a/esphome/components/lcd_base/__init__.py b/esphome/components/lcd_base/__init__.py index 92fd0b5563fe..693211c6fe2b 100644 --- a/esphome/components/lcd_base/__init__.py +++ b/esphome/components/lcd_base/__init__.py @@ -52,7 +52,6 @@ def validate_user_characters(value): async def setup_lcd_display(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])) if CONF_USER_CHARACTERS in config: diff --git a/esphome/components/lcd_menu/__init__.py b/esphome/components/lcd_menu/__init__.py index a356c85ba7e3..b57a4a0f6c1c 100644 --- a/esphome/components/lcd_menu/__init__.py +++ b/esphome/components/lcd_menu/__init__.py @@ -3,6 +3,7 @@ from esphome.const import ( CONF_ID, CONF_DIMENSIONS, + CONF_DISPLAY_ID, ) from esphome.core.entity_helpers import inherit_property_from from esphome.components import lcd_base @@ -18,8 +19,6 @@ lcd_menu_ns = cg.esphome_ns.namespace("lcd_menu") -CONF_DISPLAY_ID = "display_id" - CONF_MARK_SELECTED = "mark_selected" CONF_MARK_EDITING = "mark_editing" CONF_MARK_SUBMENU = "mark_submenu" diff --git a/esphome/components/ld2410/binary_sensor.py b/esphome/components/ld2410/binary_sensor.py index 3057480d25ef..e00ab93be2d5 100644 --- a/esphome/components/ld2410/binary_sensor.py +++ b/esphome/components/ld2410/binary_sensor.py @@ -8,13 +8,13 @@ ENTITY_CATEGORY_DIAGNOSTIC, ICON_MOTION_SENSOR, ICON_ACCOUNT, + CONF_HAS_TARGET, + CONF_HAS_MOVING_TARGET, + CONF_HAS_STILL_TARGET, ) from . import CONF_LD2410_ID, LD2410Component DEPENDENCIES = ["ld2410"] -CONF_HAS_TARGET = "has_target" -CONF_HAS_MOVING_TARGET = "has_moving_target" -CONF_HAS_STILL_TARGET = "has_still_target" CONF_OUT_PIN_PRESENCE_STATUS = "out_pin_presence_status" CONFIG_SCHEMA = { diff --git a/esphome/components/ld2410/button/__init__.py b/esphome/components/ld2410/button/__init__.py index 3567114c2c5b..34b18e8bddcf 100644 --- a/esphome/components/ld2410/button/__init__.py +++ b/esphome/components/ld2410/button/__init__.py @@ -2,6 +2,8 @@ from esphome.components import button import esphome.config_validation as cv from esphome.const import ( + CONF_FACTORY_RESET, + CONF_RESTART, DEVICE_CLASS_RESTART, ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_CONFIG, @@ -15,8 +17,6 @@ ResetButton = ld2410_ns.class_("ResetButton", button.Button) RestartButton = ld2410_ns.class_("RestartButton", button.Button) -CONF_FACTORY_RESET = "factory_reset" -CONF_RESTART = "restart" CONF_QUERY_PARAMS = "query_params" CONFIG_SCHEMA = { diff --git a/esphome/components/ld2420/__init__.py b/esphome/components/ld2420/__init__.py new file mode 100644 index 000000000000..c701423081f8 --- /dev/null +++ b/esphome/components/ld2420/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@descipher"] + +DEPENDENCIES = ["uart"] + +MULTI_CONF = True + +ld2420_ns = cg.esphome_ns.namespace("ld2420") +LD2420Component = ld2420_ns.class_("LD2420Component", cg.Component, uart.UARTDevice) + +CONF_LD2420_ID = "ld2420_id" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LD2420Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "ld2420_uart", + require_tx=True, + require_rx=True, + parity="NONE", + stop_bits=1, +) + + +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) diff --git a/esphome/components/ld2420/binary_sensor/__init__.py b/esphome/components/ld2420/binary_sensor/__init__.py new file mode 100644 index 000000000000..43e22d034885 --- /dev/null +++ b/esphome/components/ld2420/binary_sensor/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_ID, DEVICE_CLASS_OCCUPANCY, CONF_HAS_TARGET +from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID + +LD2420BinarySensor = ld2420_ns.class_( + "LD2420BinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = cv.All( + cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LD2420BinarySensor), + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY + ), + } + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + if CONF_HAS_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET]) + cg.add(var.set_presence_sensor(sens)) + ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) + cg.add(ld2420.register_listener(var)) diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp new file mode 100644 index 000000000000..c6ea0a348bcc --- /dev/null +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp @@ -0,0 +1,16 @@ +#include "ld2420_binary_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2420 { + +static const char *const TAG = "LD2420.binary_sensor"; + +void LD2420BinarySensor::dump_config() { + ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:"); + LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_); +} + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h new file mode 100644 index 000000000000..ee06439090c4 --- /dev/null +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../ld2420.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace ld2420 { + +class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor { + public: + void dump_config() override; + void set_presence_sensor(binary_sensor::BinarySensor *bsensor) { this->presence_bsensor_ = bsensor; }; + void on_presence(bool presence) override { + if (this->presence_bsensor_ != nullptr) { + if (this->presence_bsensor_->state != presence) + this->presence_bsensor_->publish_state(presence); + } + } + + protected: + binary_sensor::BinarySensor *presence_bsensor_{nullptr}; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/button/__init__.py b/esphome/components/ld2420/button/__init__.py new file mode 100644 index 000000000000..df774ad7bcc7 --- /dev/null +++ b/esphome/components/ld2420/button/__init__.py @@ -0,0 +1,69 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + CONF_FACTORY_RESET, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, + ICON_RESTART_ALERT, + ICON_DATABASE, +) +from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns + +LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button) +LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button) +LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button) +LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button) + +CONF_APPLY_CONFIG = "apply_config" +CONF_REVERT_CONFIG = "revert_config" +CONF_RESTART_MODULE = "restart_module" + + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Required(CONF_APPLY_CONFIG): button.button_schema( + LD2420ApplyConfigButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ), + cv.Optional(CONF_REVERT_CONFIG): button.button_schema( + LD2420RevertConfigButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART, + ), + cv.Optional(CONF_RESTART_MODULE): button.button_schema( + LD2420RestartModuleButton, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_DATABASE, + ), + cv.Optional(CONF_FACTORY_RESET): button.button_schema( + LD2420FactoryResetButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_DATABASE, + ), +} + + +async def to_code(config): + ld2420_component = await cg.get_variable(config[CONF_LD2420_ID]) + if apply_config := config.get(CONF_APPLY_CONFIG): + b = await button.new_button(apply_config) + await cg.register_parented(b, config[CONF_LD2420_ID]) + cg.add(ld2420_component.set_apply_config_button(b)) + if revert_config := config.get(CONF_REVERT_CONFIG): + b = await button.new_button(revert_config) + await cg.register_parented(b, config[CONF_LD2420_ID]) + cg.add(ld2420_component.set_revert_config_button(b)) + if restart_config := config.get(CONF_RESTART_MODULE): + b = await button.new_button(restart_config) + await cg.register_parented(b, config[CONF_LD2420_ID]) + cg.add(ld2420_component.set_restart_module_button(b)) + if factory_reset := config.get(CONF_FACTORY_RESET): + b = await button.new_button(factory_reset) + await cg.register_parented(b, config[CONF_LD2420_ID]) + cg.add(ld2420_component.set_factory_reset_button(b)) diff --git a/esphome/components/ld2420/button/reconfig_buttons.cpp b/esphome/components/ld2420/button/reconfig_buttons.cpp new file mode 100644 index 000000000000..3537c1d64a35 --- /dev/null +++ b/esphome/components/ld2420/button/reconfig_buttons.cpp @@ -0,0 +1,16 @@ +#include "reconfig_buttons.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +static const char *const TAG = "LD2420.button"; + +namespace esphome { +namespace 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 diff --git a/esphome/components/ld2420/button/reconfig_buttons.h b/esphome/components/ld2420/button/reconfig_buttons.h new file mode 100644 index 000000000000..4e9e7a3692bc --- /dev/null +++ b/esphome/components/ld2420/button/reconfig_buttons.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../ld2420.h" + +namespace esphome { +namespace ld2420 { + +class LD2420ApplyConfigButton : public button::Button, public Parented { + public: + LD2420ApplyConfigButton() = default; + + protected: + void press_action() override; +}; + +class LD2420RevertConfigButton : public button::Button, public Parented { + public: + LD2420RevertConfigButton() = default; + + protected: + void press_action() override; +}; + +class LD2420RestartModuleButton : public button::Button, public Parented { + public: + LD2420RestartModuleButton() = default; + + protected: + void press_action() override; +}; + +class LD2420FactoryResetButton : public button::Button, public Parented { + public: + LD2420FactoryResetButton() = default; + + protected: + void press_action() override; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp new file mode 100644 index 000000000000..e57fdbc84e78 --- /dev/null +++ b/esphome/components/ld2420/ld2420.cpp @@ -0,0 +1,778 @@ +#include "ld2420.h" +#include "esphome/core/helpers.h" + +/* +Configure commands - little endian + +No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends. + +All send command frames will have: + Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD + Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data. + Command bytes 6 - 7, uint16_t + Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes. +Receive + Error bytes 8-9 uint16_t, 0 = success, all other positive values = error + +Enable config mode: +Send: + UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01 + Command = FF 00 - uint16_t 0x00FF + Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002 +Reply: + UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01 + +Disable config mode: +Send: + UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01 + Command = FE 00 - uint16_t 0x00FE +Receive: + UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01 + +Configure system parameters: + +UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms +Command = 12 00 - uint16_t 0x0012, Param +There are three documented parameters for modes: + 00 64 = Basic status mode + This mode outputs text as presence "ON" or "OFF" and "Range XXXX" + where XXXX is a decimal value for distance in cm + 00 04 = Energy output mode + This mode outputs detailed signal energy values for each gate and the target distance. + The data format consist of the following. + Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF + HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF + F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 + 00 00 = debug output mode + This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes + The data format consist of the following. + Header HH, Doppler DD, Range RR, Footer FF + HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF + AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA + +Configure gate sensitivity parameters: +UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01 +Command = 12 00 - uint16_t 0x0007 +Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60 +Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60 +*/ + +namespace esphome { +namespace ld2420 { + +static const char *const TAG = "ld2420"; + +float LD2420Component::get_setup_priority() const { return setup_priority::BUS; } + +void LD2420Component::dump_config() { + ESP_LOGCONFIG(TAG, "LD2420:"); + ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_); + ESP_LOGCONFIG(TAG, "LD2420 Number:"); +#ifdef USE_NUMBER + LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_); + LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_); + LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_); + LOG_NUMBER(TAG, " Gate Select:", this->gate_select_number_); + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]); + LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]); + } +#endif +#ifdef USE_BUTTON + LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_); + LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_); + LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_); + LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_); +#endif + ESP_LOGCONFIG(TAG, "LD2420 Select:"); + LOG_SELECT(TAG, " Operating Mode", this->operating_selector_); + if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { + ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); + } +} + +uint8_t LD2420Component::calc_checksum(void *data, size_t size) { + uint8_t checksum = 0; + uint8_t *data_bytes = (uint8_t *) data; + for (size_t i = 0; i < size; i++) { + checksum ^= data_bytes[i]; // XOR operation + } + return checksum; +} + +int LD2420Component::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(remove(version_str.begin(), version_str.end(), '.'), version_str.end()); + int version_integer = stoi(version_str); + return version_integer; +} + +void LD2420Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up LD2420..."); + if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { + ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + this->mark_failed(); + return; + } + this->get_min_max_distances_timeout_(); +#ifdef USE_NUMBER + this->init_gate_config_numbers(); +#endif + this->get_firmware_version_(); + const char *pfw = this->ld2420_firmware_ver_; + std::string fw_str(pfw); + + for (auto &listener : listeners_) { + listener->on_fw_version(fw_str); + } + + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + delay_microseconds_safe(125); + this->get_gate_threshold_(gate); + } + + memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); + if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { + this->set_operating_mode(OP_SIMPLE_MODE_STRING); + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); + ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); + } else { + this->set_mode_(CMD_SYSTEM_MODE_ENERGY); + this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); + } +#ifdef USE_NUMBER + this->init_gate_config_numbers(); +#endif + this->set_system_mode(this->system_mode_); + this->set_config_mode(false); + ESP_LOGCONFIG(TAG, "LD2420 setup complete."); +} + +void LD2420Component::apply_config_action() { + const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); + if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { + ESP_LOGCONFIG(TAG, "No configuration change detected"); + return; + } + ESP_LOGCONFIG(TAG, "Reconfiguring LD2420..."); + if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { + ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + this->mark_failed(); + return; + } + this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout); + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + delay_microseconds_safe(125); + this->set_gate_threshold(gate); + } + memcpy(¤t_config, &new_config, sizeof(new_config)); +#ifdef USE_NUMBER + this->init_gate_config_numbers(); +#endif + this->set_system_mode(this->system_mode_); + this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm + this->set_operating_mode(OP_NORMAL_MODE_STRING); + ESP_LOGCONFIG(TAG, "LD2420 reconfig complete."); +} + +void LD2420Component::factory_reset_action() { + ESP_LOGCONFIG(TAG, "Setiing factory defaults..."); + if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { + ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + this->mark_failed(); + return; + } + this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT); +#ifdef USE_NUMBER + this->gate_timeout_number_->state = FACTORY_TIMEOUT; + this->min_gate_distance_number_->state = FACTORY_MIN_GATE; + this->max_gate_distance_number_->state = FACTORY_MAX_GATE; +#endif + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate]; + this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate]; + delay_microseconds_safe(125); + this->set_gate_threshold(gate); + } + memcpy(&this->current_config, &this->new_config, sizeof(this->new_config)); + this->set_system_mode(this->system_mode_); + this->set_config_mode(false); +#ifdef USE_NUMBER + this->init_gate_config_numbers(); + this->refresh_gate_config_numbers(); +#endif + ESP_LOGCONFIG(TAG, "LD2420 factory reset complete."); +} + +void LD2420Component::restart_module_action() { + ESP_LOGCONFIG(TAG, "Restarting LD2420 module..."); + this->send_module_restart(); + this->set_timeout(250, [this]() { + this->set_config_mode(true); + this->set_system_mode(system_mode_); + this->set_config_mode(false); + }); + ESP_LOGCONFIG(TAG, "LD2420 Restarted."); +} + +void LD2420Component::revert_config_action() { + memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); +#ifdef USE_NUMBER + this->init_gate_config_numbers(); +#endif + ESP_LOGCONFIG(TAG, "Reverted config number edits."); +} + +void LD2420Component::loop() { + // If there is a active send command do not process it here, the send command call will handle it. + if (!get_cmd_active_()) { + if (!available()) + return; + static uint8_t buffer[2048]; + static uint8_t rx_data; + while (available()) { + rx_data = read(); + this->readline_(rx_data, buffer, sizeof(buffer)); + } + } +} + +void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) { + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { + this->radar_data[gate][sample_number] = gate_energy[gate]; + } + this->total_sample_number_counter++; +} + +void LD2420Component::auto_calibrate_sensitivity() { + // Calculate average and peak values for each gate + const float move_factor = gate_move_sensitivity_factor + 1; + const float still_factor = (gate_still_sensitivity_factor / 2) + 1; + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { + uint32_t sum = 0; + uint16_t peak = 0; + + for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) { + // Calculate average + sum += this->radar_data[gate][sample_number]; + + // Calculate max value + if (this->radar_data[gate][sample_number] > peak) { + peak = this->radar_data[gate][sample_number]; + } + } + + // Store average and peak values + this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; + if (this->gate_peak[gate] < peak) + this->gate_peak[gate] = peak; + + uint32_t calculated_value = + (static_cast(this->gate_peak[gate]) + (move_factor * static_cast(this->gate_peak[gate]))); + this->new_config.move_thresh[gate] = static_cast(calculated_value <= 65535 ? calculated_value : 65535); + calculated_value = + (static_cast(this->gate_peak[gate]) + (still_factor * static_cast(this->gate_peak[gate]))); + this->new_config.still_thresh[gate] = static_cast(calculated_value <= 65535 ? calculated_value : 65535); + } +} + +void LD2420Component::report_gate_data() { + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { + // Output results + ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]); + } + ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter); +} + +void LD2420Component::set_operating_mode(const std::string &state) { + // If unsupported firmware ignore mode select + if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { + this->current_operating_mode = OP_MODE_TO_UINT.at(state); + // Entering Auto Calibrate we need to clear the privoiuos data collection + this->operating_selector_->publish_state(state); + if (current_operating_mode == OP_CALIBRATE_MODE) { + this->set_calibration_(true); + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + this->gate_avg[gate] = 0; + this->gate_peak[gate] = 0; + for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) { + this->radar_data[gate][i] = 0; + } + this->total_sample_number_counter = 0; + } + } else { + // Set the current data back so we don't have new data that can be applied in error. + if (this->get_calibration_()) + memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); + this->set_calibration_(false); + } + } else { + this->current_operating_mode = OP_SIMPLE_MODE; + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +} + +void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { + static int pos = 0; + + if (rx_data >= 0) { + if (pos < len - 1) { + buffer[pos++] = rx_data; + buffer[pos] = 0; + } else { + pos = 0; + } + if (pos >= 4) { + if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { + this->set_cmd_active_(false); // Set command state to inactive after responce. + this->handle_ack_data_(buffer, pos); + pos = 0; + } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { + this->handle_simple_mode_(buffer, pos); + pos = 0; + } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && + (get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { + this->handle_energy_mode_(buffer, pos); + pos = 0; + } + } + } +} + +void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { + uint8_t index = 6; // Start at presence byte position + uint16_t range; + const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]); + this->set_presence_(buffer[index]); + index++; + memcpy(&range, &buffer[index], sizeof(range)); + index += sizeof(range); + this->set_distance_(range); + for (uint8_t i = 0; i < elements; i++) { // NOLINT + memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0])); + index += sizeof(this->gate_energy_[0]); + } + + if (this->current_operating_mode == OP_CALIBRATE_MODE) { + this->update_radar_data(gate_energy_, sample_number_counter); + this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++; + } + + // Resonable refresh rate for home assistant database size health + const int32_t current_millis = millis(); + if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) + return; + this->last_periodic_millis = current_millis; + for (auto &listener : this->listeners_) { + listener->on_distance(get_distance_()); + listener->on_presence(get_presence_()); + listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); + } + + if (this->current_operating_mode == OP_CALIBRATE_MODE) { + this->auto_calibrate_sensitivity(); + if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) { + this->report_periodic_millis = current_millis; + this->report_gate_data(); + } + } +} + +void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { + const uint8_t bufsize = 16; + uint8_t index{0}; + uint8_t pos{0}; + char *endptr{nullptr}; + char outbuf[bufsize]{0}; + while (true) { + if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { + set_presence_(false); + } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { + set_presence_(true); + } + if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { + if (index < bufsize - 1) { + outbuf[index++] = inbuf[pos]; + pos++; + } + } else { + if (pos < len - 1) { + pos++; + } else { + break; + } + } + } + outbuf[index] = '\0'; + if (index > 1) + set_distance_(strtol(outbuf, &endptr, 10)); + + if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { + // Resonable refresh rate for home assistant database size health + const int32_t current_millis = millis(); + if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) + return; + this->last_normal_periodic_millis = current_millis; + for (auto &listener : this->listeners_) + listener->on_distance(get_distance_()); + for (auto &listener : this->listeners_) + listener->on_presence(get_presence_()); + } +} + +void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { + this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND]; + this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH]; + uint8_t reg_element = 0; + uint8_t data_element = 0; + uint16_t data_pos = 0; + if (this->cmd_reply_.length > CMD_MAX_BYTES) { + ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES); + return; + } else if (this->cmd_reply_.length < 2) { + ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes."); + return; + } + memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); + const char *result = this->cmd_reply_.error ? "failure" : "success"; + if (this->cmd_reply_.error > 0) { + return; + }; + this->cmd_reply_.ack = true; + switch ((uint16_t) this->cmd_reply_.command) { + case (CMD_ENABLE_CONF): + ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); + break; + case (CMD_DISABLE_CONF): + ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); + break; + case (CMD_READ_REGISTER): + ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result); + // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file + data_pos = 0x0A; + for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT + ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE)); + index += CMD_REG_DATA_REPLY_SIZE) { + memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE)); + byteswap(this->cmd_reply_.data[reg_element]); + reg_element++; + } + break; + case (CMD_WRITE_REGISTER): + ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); + break; + case (CMD_WRITE_ABD_PARAM): + ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); + break; + case (CMD_READ_ABD_PARAM): + ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); + data_pos = CMD_ABD_DATA_REPLY_START; + for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT + ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); + index += CMD_ABD_DATA_REPLY_SIZE) { + memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index], + sizeof(this->cmd_reply_.data[data_element])); + byteswap(this->cmd_reply_.data[data_element]); + data_element++; + } + break; + case (CMD_WRITE_SYS_PARAM): + ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); + break; + case (CMD_READ_VERSION): + memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); + ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result); + break; + default: + break; + } +} + +int LD2420Component::send_cmd_from_array(CmdFrameT frame) { + uint32_t start_millis = millis(); + uint8_t error = 0; + uint8_t ack_buffer[64]; + uint8_t cmd_buffer[64]; + this->cmd_reply_.ack = false; + if (frame.command != CMD_RESTART) + this->set_cmd_active_(true); // Restart does not reply, thus no ack state required. + uint8_t retry = 3; + while (retry) { + frame.length = 0; + uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size + + memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header)); + frame.length += sizeof(frame.header); + + memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length)); + frame.length += sizeof(frame.data_length); + + memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command)); + frame.length += sizeof(frame.command); + + for (uint16_t index = 0; index < frame.data_length; index++) { + memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index])); + frame.length += sizeof(frame.data[index]); + } + + memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); + frame.length += sizeof(frame.footer); + for (uint16_t index = 0; index < frame.length; index++) { + this->write_byte(cmd_buffer[index]); + } + + error = 0; + if (frame.command == CMD_RESTART) { + return 0; // restart does not reply exit now + } + + while (!this->cmd_reply_.ack) { + while (available()) { + this->readline_(read(), ack_buffer, sizeof(ack_buffer)); + } + delay_microseconds_safe(1450); + // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. + if ((millis() - start_millis) > 1000) { + start_millis = millis(); + error = LD2420_ERROR_TIMEOUT; + retry--; + break; + } + } + if (this->cmd_reply_.ack) + retry = 0; + if (this->cmd_reply_.error > 0) + handle_cmd_error(error); + } + return error; +} + +uint8_t LD2420Component::set_config_mode(bool enable) { + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; + if (enable) { + memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER)); + cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); + } + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); + return this->send_cmd_from_array(cmd_frame); +} + +// Sends a restart and set system running mode to normal +void LD2420Component::send_module_restart() { this->ld2420_restart(); } + +void LD2420Component::ld2420_restart() { + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_RESTART; + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command); + this->send_cmd_from_array(cmd_frame); +} + +void LD2420Component::get_reg_value_(uint16_t reg) { + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_READ_REGISTER; + cmd_frame.data[1] = reg; + cmd_frame.data_length += 2; + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); + this->send_cmd_from_array(cmd_frame); +} + +void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) { + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_WRITE_REGISTER; + memcpy(&cmd_frame.data[cmd_frame.data_length], ®, sizeof(CMD_REG_DATA_REPLY_SIZE)); + cmd_frame.data_length += 2; + memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); + cmd_frame.data_length += 2; + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); + this->send_cmd_from_array(cmd_frame); +} + +void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); } + +int LD2420Component::get_gate_threshold_(uint8_t gate) { + uint8_t error; + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_READ_ABD_PARAM; + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate])); + cmd_frame.data_length += 2; + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); + cmd_frame.data_length += 2; + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command); + error = this->send_cmd_from_array(cmd_frame); + if (error == 0) { + this->current_config.move_thresh[gate] = cmd_reply_.data[0]; + this->current_config.still_thresh[gate] = cmd_reply_.data[1]; + } + return error; +} + +int LD2420Component::get_min_max_distances_timeout_() { + uint8_t error; + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_READ_ABD_PARAM; + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG, + sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number + cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG); + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG, + sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number + cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG); + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG, + sizeof(CMD_TIMEOUT_REG)); // Register: global delay time + cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); + error = this->send_cmd_from_array(cmd_frame); + if (error == 0) { + this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; + this->current_config.max_gate = (uint16_t) cmd_reply_.data[1]; + this->current_config.timeout = (uint16_t) cmd_reply_.data[2]; + } + return error; +} + +void LD2420Component::set_system_mode(uint16_t mode) { + CmdFrameT cmd_frame; + uint16_t unknown_parm = 0x0000; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_WRITE_SYS_PARAM; + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE)); + cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE); + memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode)); + cmd_frame.data_length += sizeof(mode); + memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); + cmd_frame.data_length += sizeof(unknown_parm); + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command); + if (this->send_cmd_from_array(cmd_frame) == 0) + set_mode_(mode); +} + +void LD2420Component::get_firmware_version_() { + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_READ_VERSION; + cmd_frame.footer = CMD_FRAME_FOOTER; + + ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command); + this->send_cmd_from_array(cmd_frame); +} + +void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT + uint32_t timeout) { + // Header H, Length L, Register R, Value V, Footer F + // |Min Gate |Max Gate |Timeout | + // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF + // FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g. + + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_WRITE_ABD_PARAM; + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG, + sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number + cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG); + memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance)); + cmd_frame.data_length += sizeof(min_gate_distance); + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG, + sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number + cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG); + memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance)); + cmd_frame.data_length += sizeof(max_gate_distance); + memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG, + sizeof(CMD_TIMEOUT_REG)); // Register: global delay time + cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); + memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout)); + ; + cmd_frame.data_length += sizeof(timeout); + cmd_frame.footer = CMD_FRAME_FOOTER; + + ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); + this->send_cmd_from_array(cmd_frame); +} + +void LD2420Component::set_gate_threshold(uint8_t gate) { + // Header H, Length L, Command C, Register R, Value V, Footer F + // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF + // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01 + + uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate]; + uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate]; + CmdFrameT cmd_frame; + cmd_frame.data_length = 0; + cmd_frame.header = CMD_FRAME_HEADER; + cmd_frame.command = CMD_WRITE_ABD_PARAM; + memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate)); + cmd_frame.data_length += sizeof(move_threshold_gate); + memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate], + sizeof(this->new_config.move_thresh[gate])); + cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]); + memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate)); + cmd_frame.data_length += sizeof(still_threshold_gate); + memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate], + sizeof(this->new_config.still_thresh[gate])); + cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); + cmd_frame.footer = CMD_FRAME_FOOTER; + ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); + this->send_cmd_from_array(cmd_frame); +} + +#ifdef USE_NUMBER +void LD2420Component::init_gate_config_numbers() { + if (this->gate_timeout_number_ != nullptr) + this->gate_timeout_number_->publish_state(static_cast(this->current_config.timeout)); + if (this->gate_select_number_ != nullptr) + this->gate_select_number_->publish_state(0); + if (this->min_gate_distance_number_ != nullptr) + this->min_gate_distance_number_->publish_state(static_cast(this->current_config.min_gate)); + if (this->max_gate_distance_number_ != nullptr) + this->max_gate_distance_number_->publish_state(static_cast(this->current_config.max_gate)); + if (this->gate_move_sensitivity_factor_number_ != nullptr) + this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); + if (this->gate_still_sensitivity_factor_number_ != nullptr) + this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); + for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { + if (this->gate_still_threshold_numbers_[gate] != nullptr) { + this->gate_still_threshold_numbers_[gate]->publish_state( + static_cast(this->current_config.still_thresh[gate])); + } + if (this->gate_move_threshold_numbers_[gate] != nullptr) { + this->gate_move_threshold_numbers_[gate]->publish_state( + static_cast(this->current_config.move_thresh[gate])); + } + } +} + +void LD2420Component::refresh_gate_config_numbers() { + this->gate_timeout_number_->publish_state(this->new_config.timeout); + this->min_gate_distance_number_->publish_state(this->new_config.min_gate); + this->max_gate_distance_number_->publish_state(this->new_config.max_gate); +} + +#endif + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h new file mode 100644 index 000000000000..2b50c7a1d494 --- /dev/null +++ b/esphome/components/ld2420/ld2420.h @@ -0,0 +1,271 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif +#include +#include + +namespace esphome { +namespace ld2420 { + +// Local const's +static const uint16_t REFRESH_RATE_MS = 1000; + +// Command sets +static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04; +static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A; +static const uint16_t CMD_DISABLE_CONF = 0x00FE; +static const uint16_t CMD_ENABLE_CONF = 0x00FF; +static const uint8_t CMD_MAX_BYTES = 0x64; +static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012; +static const uint16_t CMD_PARM_LOW_TRESH = 0x0021; +static const uint16_t CMD_PROTOCOL_VER = 0x0002; +static const uint16_t CMD_READ_ABD_PARAM = 0x0008; +static const uint16_t CMD_READ_REG_ADDR = 0x0020; +static const uint16_t CMD_READ_REGISTER = 0x0002; +static const uint16_t CMD_READ_SERIAL_NUM = 0x0011; +static const uint16_t CMD_READ_SYS_PARAM = 0x0013; +static const uint16_t CMD_READ_VERSION = 0x0000; +static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02; +static const uint16_t CMD_RESTART = 0x0068; +static const uint16_t CMD_SYSTEM_MODE = 0x0000; +static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003; +static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001; +static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064; +static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000; +static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004; +static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002; +static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007; +static const uint16_t CMD_WRITE_REGISTER = 0x0001; +static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012; + +static const uint8_t LD2420_ERROR_NONE = 0x00; +static const uint8_t LD2420_ERROR_TIMEOUT = 0x02; +static const uint8_t LD2420_ERROR_UNKNOWN = 0x01; +static const uint8_t LD2420_TOTAL_GATES = 16; +static const uint8_t CALIBRATE_SAMPLES = 64; + +// Register address values +static const uint16_t CMD_MIN_GATE_REG = 0x0000; +static const uint16_t CMD_MAX_GATE_REG = 0x0001; +static const uint16_t CMD_TIMEOUT_REG = 0x0004; +static const uint16_t CMD_GATE_MOVE_THRESH[LD2420_TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, + 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, + 0x001C, 0x001D, 0x001E, 0x001F}; +static const uint16_t CMD_GATE_STILL_THRESH[LD2420_TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, + 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, + 0x002C, 0x002D, 0x002E, 0x002F}; +static const uint32_t FACTORY_MOVE_THRESH[LD2420_TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250, + 250, 250, 250, 250, 250, 250, 250, 250}; +static const uint32_t FACTORY_STILL_THRESH[LD2420_TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150, + 150, 100, 100, 100, 100, 100, 100, 100}; +static const uint16_t FACTORY_TIMEOUT = 120; +static const uint16_t FACTORY_MIN_GATE = 1; +static const uint16_t FACTORY_MAX_GATE = 12; + +// COMMAND_BYTE Header & Footer +static const uint8_t CMD_FRAME_COMMAND = 6; +static const uint8_t CMD_FRAME_DATA_LENGTH = 4; +static const uint32_t CMD_FRAME_FOOTER = 0x01020304; +static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD; +static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD; +static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA; +static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8; +static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4; +static const uint8_t CMD_FRAME_STATUS = 7; +static const uint8_t CMD_ERROR_WORD = 8; +static const uint8_t ENERGY_SENSOR_START = 9; +static const uint8_t CALIBRATE_REPORT_INTERVAL = 4; +static const int CALIBRATE_VERSION_MIN = 154; +static const std::string OP_NORMAL_MODE_STRING = "Normal"; +static const std::string OP_SIMPLE_MODE_STRING = "Simple"; + +enum OpModeStruct : uint8_t { OP_NORMAL_MODE = 1, OP_CALIBRATE_MODE = 2, OP_SIMPLE_MODE = 3 }; +static const std::map OP_MODE_TO_UINT{ + {"Normal", OP_NORMAL_MODE}, {"Calibrate", OP_CALIBRATE_MODE}, {"Simple", OP_SIMPLE_MODE}}; +static constexpr const char *ERR_MESSAGE[] = {"None", "Unknown", "Timeout"}; + +class LD2420Listener { + public: + virtual void on_presence(bool presence){}; + virtual void on_distance(uint16_t distance){}; + virtual void on_energy(uint16_t *sensor_energy, size_t size){}; + virtual void on_fw_version(std::string &fw){}; +}; + +class LD2420Component : public Component, public uart::UARTDevice { + public: + void setup() override; + void dump_config() override; + void loop() override; +#ifdef USE_SELECT + void set_operating_mode_select(select::Select *selector) { this->operating_selector_ = selector; }; +#endif +#ifdef USE_NUMBER + void set_gate_timeout_number(number::Number *number) { this->gate_timeout_number_ = number; }; + void set_gate_select_number(number::Number *number) { this->gate_select_number_ = number; }; + void set_min_gate_distance_number(number::Number *number) { this->min_gate_distance_number_ = number; }; + void set_max_gate_distance_number(number::Number *number) { this->max_gate_distance_number_ = number; }; + void set_gate_move_sensitivity_factor_number(number::Number *number) { + this->gate_move_sensitivity_factor_number_ = number; + }; + void set_gate_still_sensitivity_factor_number(number::Number *number) { + this->gate_still_sensitivity_factor_number_ = number; + }; + void set_gate_still_threshold_numbers(int gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; }; + void set_gate_move_threshold_numbers(int gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; }; + bool is_gate_select() { return gate_select_number_ != nullptr; }; + uint8_t get_gate_select_value() { return static_cast(this->gate_select_number_->state); }; + float get_min_gate_distance_value() { return min_gate_distance_number_->state; }; + float get_max_gate_distance_value() { return max_gate_distance_number_->state; }; + void publish_gate_move_threshold(uint8_t gate) { + // With gate_select we only use 1 number pointer, thus we hard code [0] + this->gate_move_threshold_numbers_[0]->publish_state(this->new_config.move_thresh[gate]); + }; + void publish_gate_still_threshold(uint8_t gate) { + this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]); + }; + void init_gate_config_numbers(); + void refresh_gate_config_numbers(); +#endif +#ifdef USE_BUTTON + void set_apply_config_button(button::Button *button) { this->apply_config_button_ = button; }; + void set_revert_config_button(button::Button *button) { this->revert_config_button_ = button; }; + void set_restart_module_button(button::Button *button) { this->restart_module_button_ = button; }; + void set_factory_reset_button(button::Button *button) { this->factory_reset_button_ = button; }; +#endif + void register_listener(LD2420Listener *listener) { this->listeners_.push_back(listener); } + + struct CmdFrameT { + uint32_t header{0}; + uint16_t length{0}; + uint16_t command{0}; + uint8_t data[18]; + uint16_t data_length{0}; + uint32_t footer{0}; + }; + + struct RegConfigT { + uint16_t min_gate{0}; + uint16_t max_gate{0}; + uint16_t timeout{0}; + uint32_t move_thresh[LD2420_TOTAL_GATES]; + uint32_t still_thresh[LD2420_TOTAL_GATES]; + }; + + void send_module_restart(); + void restart_module_action(); + void apply_config_action(); + void factory_reset_action(); + void revert_config_action(); + float get_setup_priority() const override; + 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 auto_calibrate_sensitivity(); + void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); + uint8_t calc_checksum(void *data, size_t size); + + RegConfigT current_config; + RegConfigT new_config; + int32_t last_periodic_millis = millis(); + int32_t report_periodic_millis = millis(); + int32_t monitor_periodic_millis = millis(); + int32_t last_normal_periodic_millis = millis(); + bool output_energy_state{false}; + uint8_t current_operating_mode{OP_NORMAL_MODE}; + uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES]; + uint16_t gate_avg[LD2420_TOTAL_GATES]; + uint16_t gate_peak[LD2420_TOTAL_GATES]; + uint8_t sample_number_counter{0}; + uint16_t total_sample_number_counter{0}; + float gate_move_sensitivity_factor{0.5}; + float gate_still_sensitivity_factor{0.5}; +#ifdef USE_SELECT + select::Select *operating_selector_{nullptr}; +#endif +#ifdef USE_BUTTON + button::Button *apply_config_button_{nullptr}; + button::Button *revert_config_button_{nullptr}; + button::Button *restart_module_button_{nullptr}; + button::Button *factory_reset_button_{nullptr}; +#endif + void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout); + void set_gate_threshold(uint8_t gate); + void set_reg_value(uint16_t reg, uint16_t value); + uint8_t set_config_mode(bool enable); + void set_system_mode(uint16_t mode); + void ld2420_restart(); + + protected: + struct CmdReplyT { + uint8_t command; + uint8_t status; + uint32_t data[4]; + uint8_t length; + uint16_t error; + volatile bool ack; + }; + + int get_firmware_int_(const char *version_string); + void get_firmware_version_(); + int get_gate_threshold_(uint8_t gate); + void get_reg_value_(uint16_t reg); + int get_min_max_distances_timeout_(); + uint16_t get_mode_() { return this->system_mode_; }; + void set_mode_(uint16_t mode) { this->system_mode_ = mode; }; + bool get_presence_() { return this->presence_; }; + void set_presence_(bool presence) { this->presence_ = presence; }; + uint16_t get_distance_() { return this->distance_; }; + void set_distance_(uint16_t distance) { this->distance_ = distance; }; + bool get_cmd_active_() { return this->cmd_active_; }; + void set_cmd_active_(bool active) { this->cmd_active_ = active; }; + void handle_simple_mode_(const uint8_t *inbuf, int len); + void handle_energy_mode_(uint8_t *buffer, int len); + void handle_ack_data_(uint8_t *buffer, int len); + void readline_(int rx_data, uint8_t *buffer, int len); + void set_calibration_(bool state) { this->calibration_ = state; }; + bool get_calibration_() { return this->calibration_; }; + +#ifdef USE_NUMBER + number::Number *gate_timeout_number_{nullptr}; + number::Number *gate_select_number_{nullptr}; + number::Number *min_gate_distance_number_{nullptr}; + number::Number *max_gate_distance_number_{nullptr}; + number::Number *gate_move_sensitivity_factor_number_{nullptr}; + number::Number *gate_still_sensitivity_factor_number_{nullptr}; + std::vector gate_still_threshold_numbers_ = std::vector(16); + std::vector gate_move_threshold_numbers_ = std::vector(16); +#endif + + uint16_t gate_energy_[LD2420_TOTAL_GATES]; + CmdReplyT cmd_reply_; + uint32_t max_distance_gate_; + uint32_t min_distance_gate_; + uint16_t system_mode_{CMD_SYSTEM_MODE_ENERGY}; + bool cmd_active_{false}; + char ld2420_firmware_ver_[8]{"v0.0.0"}; + bool presence_{false}; + bool calibration_{false}; + uint16_t distance_{0}; + uint8_t config_checksum_{0}; + std::vector listeners_{}; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/number/__init__.py b/esphome/components/ld2420/number/__init__.py new file mode 100644 index 000000000000..4ae08356fc13 --- /dev/null +++ b/esphome/components/ld2420/number/__init__.py @@ -0,0 +1,183 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_DISTANCE, + UNIT_SECOND, + ENTITY_CATEGORY_CONFIG, + ICON_MOTION_SENSOR, + ICON_TIMELAPSE, + ICON_SCALE, +) +from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns + +LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number) +LD2420MoveSensFactorNumber = ld2420_ns.class_( + "LD2420MoveSensFactorNumber", number.Number +) +LD2420StillSensFactorNumber = ld2420_ns.class_( + "LD2420StillSensFactorNumber", number.Number +) +LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number) +LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number) +LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number) +LD2420MoveThresholdNumbers = ld2420_ns.class_( + "LD2420MoveThresholdNumbers", number.Number +) +LD2420StillThresholdNumbers = ld2420_ns.class_( + "LD2420StillThresholdNumbers", number.Number +) +CONF_MIN_GATE_DISTANCE = "min_gate_distance" +CONF_MAX_GATE_DISTANCE = "max_gate_distance" +CONF_STILL_THRESHOLD = "still_threshold" +CONF_MOVE_THRESHOLD = "move_threshold" +CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity" +CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity" +CONF_GATE_SELECT = "gate_select" +CONF_PRESENCE_TIMEOUT = "presence_timeout" +GATE_GROUP = "gate_group" +TIMEOUT_GROUP = "timeout_group" + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema( + LD2420TimeoutNumber, + unit_of_measurement=UNIT_SECOND, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_TIMELAPSE, + ), + cv.Inclusive(CONF_MIN_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema( + LD2420MinDistanceNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Inclusive(CONF_MAX_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema( + LD2420MaxDistanceNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Inclusive(CONF_GATE_SELECT, GATE_GROUP): number.number_schema( + LD2420GateSelectNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema( + LD2420StillThresholdNumbers, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema( + LD2420MoveThresholdNumbers, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema( + LD2420MoveSensFactorNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SCALE, + ), + cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema( + LD2420StillSensFactorNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SCALE, + ), + } +) +CONFIG_SCHEMA = CONFIG_SCHEMA.extend( + { + cv.Optional(f"gate_{x}"): ( + { + cv.Required(CONF_MOVE_THRESHOLD): number.number_schema( + LD2420MoveThresholdNumbers, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Required(CONF_STILL_THRESHOLD): number.number_schema( + LD2420StillThresholdNumbers, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + } + ) + for x in range(16) + } +) + + +async def to_code(config): + LD2420_component = await cg.get_variable(config[CONF_LD2420_ID]) + if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT): + n = await number.new_number( + gate_timeout_config, min_value=0, max_value=255, step=5 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_timeout_number(n)) + if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE): + n = await number.new_number( + min_distance_gate_config, min_value=0, max_value=15, step=1 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_min_gate_distance_number(n)) + if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE): + n = await number.new_number( + max_distance_gate_config, min_value=1, max_value=15, step=1 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_max_gate_distance_number(n)) + if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY): + n = await number.new_number( + gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n)) + if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY): + n = await number.new_number( + gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n)) + if config.get(CONF_GATE_SELECT): + if gate_number := config.get(CONF_GATE_SELECT): + n = await number.new_number(gate_number, min_value=0, max_value=15, step=1) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_select_number(n)) + if gate_still_threshold := config.get(CONF_STILL_THRESHOLD): + n = cg.new_Pvariable(gate_still_threshold[CONF_ID]) + await number.register_number( + n, gate_still_threshold, min_value=0, max_value=65535, step=25 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n)) + if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD): + n = cg.new_Pvariable(gate_move_threshold[CONF_ID]) + await number.register_number( + n, gate_move_threshold, min_value=0, max_value=65535, step=25 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n)) + else: + for x in range(16): + if gate_conf := config.get(f"gate_{x}"): + move_config = gate_conf[CONF_MOVE_THRESHOLD] + n = cg.new_Pvariable(move_config[CONF_ID], x) + await number.register_number( + n, move_config, min_value=0, max_value=65535, step=25 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n)) + + still_config = gate_conf[CONF_STILL_THRESHOLD] + n = cg.new_Pvariable(still_config[CONF_ID], x) + await number.register_number( + n, still_config, min_value=0, max_value=65535, step=25 + ) + await cg.register_parented(n, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n)) diff --git a/esphome/components/ld2420/number/gate_config_number.cpp b/esphome/components/ld2420/number/gate_config_number.cpp new file mode 100644 index 000000000000..e5eaafb46d95 --- /dev/null +++ b/esphome/components/ld2420/number/gate_config_number.cpp @@ -0,0 +1,73 @@ +#include "gate_config_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +static const char *const TAG = "LD2420.number"; + +namespace esphome { +namespace ld2420 { + +void LD2420TimeoutNumber::control(float timeout) { + this->publish_state(timeout); + this->parent_->new_config.timeout = timeout; +} + +void LD2420MinDistanceNumber::control(float min_gate) { + if ((uint16_t) min_gate > this->parent_->new_config.max_gate) { + min_gate = this->parent_->get_min_gate_distance_value(); + } else { + this->parent_->new_config.min_gate = (uint16_t) min_gate; + } + this->publish_state(min_gate); +} + +void LD2420MaxDistanceNumber::control(float max_gate) { + if ((uint16_t) max_gate < this->parent_->new_config.min_gate) { + max_gate = this->parent_->get_max_gate_distance_value(); + } else { + this->parent_->new_config.max_gate = (uint16_t) max_gate; + } + this->publish_state(max_gate); +} + +void LD2420GateSelectNumber::control(float gate_select) { + const uint8_t gate = (uint8_t) gate_select; + this->publish_state(gate_select); + this->parent_->publish_gate_move_threshold(gate); + this->parent_->publish_gate_still_threshold(gate); +} + +void LD2420MoveSensFactorNumber::control(float move_factor) { + this->publish_state(move_factor); + this->parent_->gate_move_sensitivity_factor = move_factor; +} + +void LD2420StillSensFactorNumber::control(float still_factor) { + this->publish_state(still_factor); + this->parent_->gate_still_sensitivity_factor = still_factor; +} + +LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {} + +void LD2420MoveThresholdNumbers::control(float move_threshold) { + this->publish_state(move_threshold); + if (!this->parent_->is_gate_select()) { + this->parent_->new_config.move_thresh[this->gate_] = move_threshold; + } else { + this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold; + } +} + +LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {} + +void LD2420StillThresholdNumbers::control(float still_threshold) { + this->publish_state(still_threshold); + if (!this->parent_->is_gate_select()) { + this->parent_->new_config.still_thresh[this->gate_] = still_threshold; + } else { + this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold; + } +} + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/number/gate_config_number.h b/esphome/components/ld2420/number/gate_config_number.h new file mode 100644 index 000000000000..459a8026e3ee --- /dev/null +++ b/esphome/components/ld2420/number/gate_config_number.h @@ -0,0 +1,78 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2420.h" + +namespace esphome { +namespace ld2420 { + +class LD2420TimeoutNumber : public number::Number, public Parented { + public: + LD2420TimeoutNumber() = default; + + protected: + void control(float timeout) override; +}; + +class LD2420MinDistanceNumber : public number::Number, public Parented { + public: + LD2420MinDistanceNumber() = default; + + protected: + void control(float min_gate) override; +}; + +class LD2420MaxDistanceNumber : public number::Number, public Parented { + public: + LD2420MaxDistanceNumber() = default; + + protected: + void control(float max_gate) override; +}; + +class LD2420GateSelectNumber : public number::Number, public Parented { + public: + LD2420GateSelectNumber() = default; + + protected: + void control(float gate_select) override; +}; + +class LD2420MoveSensFactorNumber : public number::Number, public Parented { + public: + LD2420MoveSensFactorNumber() = default; + + protected: + void control(float move_factor) override; +}; + +class LD2420StillSensFactorNumber : public number::Number, public Parented { + public: + LD2420StillSensFactorNumber() = default; + + protected: + void control(float still_factor) override; +}; + +class LD2420StillThresholdNumbers : public number::Number, public Parented { + public: + LD2420StillThresholdNumbers() = default; + LD2420StillThresholdNumbers(uint8_t gate); + + protected: + uint8_t gate_; + void control(float still_threshold) override; +}; + +class LD2420MoveThresholdNumbers : public number::Number, public Parented { + public: + LD2420MoveThresholdNumbers() = default; + LD2420MoveThresholdNumbers(uint8_t gate); + + protected: + uint8_t gate_; + void control(float move_threshold) override; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/select/__init__.py b/esphome/components/ld2420/select/__init__.py new file mode 100644 index 000000000000..554bd4147da8 --- /dev/null +++ b/esphome/components/ld2420/select/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_CONFIG +from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns + +CONF_OPERATING_MODE = "operating_mode" +CONF_SELECTS = [ + "Normal", + "Calibrate", + "Simple", +] + +LD2420Select = ld2420_ns.class_("LD2420Select", cg.Component) + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Required(CONF_OPERATING_MODE): select.select_schema( + LD2420Select, + entity_category=ENTITY_CATEGORY_CONFIG, + ), +} + + +async def to_code(config): + LD2420_component = await cg.get_variable(config[CONF_LD2420_ID]) + if operating_mode_config := config.get(CONF_OPERATING_MODE): + sel = await select.new_select( + operating_mode_config, + options=[CONF_SELECTS], + ) + await cg.register_parented(sel, config[CONF_LD2420_ID]) + cg.add(LD2420_component.set_operating_mode_select(sel)) diff --git a/esphome/components/ld2420/select/operating_mode_select.cpp b/esphome/components/ld2420/select/operating_mode_select.cpp new file mode 100644 index 000000000000..1c59f443a588 --- /dev/null +++ b/esphome/components/ld2420/select/operating_mode_select.cpp @@ -0,0 +1,16 @@ +#include "operating_mode_select.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +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); +} + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/select/operating_mode_select.h b/esphome/components/ld2420/select/operating_mode_select.h new file mode 100644 index 000000000000..317b2af8c0f4 --- /dev/null +++ b/esphome/components/ld2420/select/operating_mode_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../ld2420.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace ld2420 { + +class LD2420Select : public Component, public select::Select, public Parented { + public: + LD2420Select() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/sensor/__init__.py b/esphome/components/ld2420/sensor/__init__.py new file mode 100644 index 000000000000..6a67d1fc4198 --- /dev/null +++ b/esphome/components/ld2420/sensor/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER +from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID + +LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component) + +CONF_MOVING_DISTANCE = "moving_distance" +CONF_GATE_ENERGY = "gate_energy" + +CONFIG_SCHEMA = cv.All( + cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LD2420Sensor), + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + } + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + if CONF_MOVING_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE]) + cg.add(var.set_distance_sensor(sens)) + if CONF_GATE_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_GATE_ENERGY]) + cg.add(var.set_energy_sensor(sens)) + ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) + cg.add(ld2420.register_listener(var)) diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.cpp b/esphome/components/ld2420/sensor/ld2420_sensor.cpp new file mode 100644 index 000000000000..97f0c594b719 --- /dev/null +++ b/esphome/components/ld2420/sensor/ld2420_sensor.cpp @@ -0,0 +1,16 @@ +#include "ld2420_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2420 { + +static const char *const TAG = "LD2420.sensor"; + +void LD2420Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "LD2420 Sensor:"); + LOG_SENSOR(" ", "Distance", this->distance_sensor_); +} + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.h b/esphome/components/ld2420/sensor/ld2420_sensor.h new file mode 100644 index 000000000000..4eebefe0e351 --- /dev/null +++ b/esphome/components/ld2420/sensor/ld2420_sensor.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../ld2420.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace ld2420 { + +class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor { + public: + void dump_config() override; + void set_distance_sensor(sensor::Sensor *sensor) { this->distance_sensor_ = sensor; } + void on_distance(uint16_t distance) override { + if (this->distance_sensor_ != nullptr) { + if (this->distance_sensor_->get_state() != distance) { + this->distance_sensor_->publish_state(distance); + } + } + } + void on_energy(uint16_t *gate_energy, size_t size) override { + for (size_t active = 0; active < size; active++) { + if (this->energy_sensors_[active] != nullptr) { + this->energy_sensors_[active]->publish_state(gate_energy[active]); + } + } + } + + protected: + sensor::Sensor *distance_sensor_{nullptr}; + std::vector energy_sensors_ = std::vector(LD2420_TOTAL_GATES); +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/text_sensor/__init__.py b/esphome/components/ld2420/text_sensor/__init__.py new file mode 100644 index 000000000000..b6d8c7c0e448 --- /dev/null +++ b/esphome/components/ld2420/text_sensor/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_CHIP, +) + +from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID + +LD2420TextSensor = ld2420_ns.class_( + "LD2420TextSensor", text_sensor.TextSensor, cg.Component +) + +CONF_FW_VERSION = "fw_version" + +CONFIG_SCHEMA = cv.All( + cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LD2420TextSensor), + cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), + cv.Optional(CONF_FW_VERSION): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP + ), + } + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + if CONF_FW_VERSION in config: + sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION]) + cg.add(var.set_fw_version_text_sensor(sens)) + ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) + cg.add(ld2420.register_listener(var)) diff --git a/esphome/components/ld2420/text_sensor/text_sensor.cpp b/esphome/components/ld2420/text_sensor/text_sensor.cpp new file mode 100644 index 000000000000..1dcdcf7d6044 --- /dev/null +++ b/esphome/components/ld2420/text_sensor/text_sensor.cpp @@ -0,0 +1,16 @@ +#include "text_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2420 { + +static const char *const TAG = "LD2420.text_sensor"; + +void LD2420TextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "LD2420 TextSensor:"); + LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); +} + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ld2420/text_sensor/text_sensor.h b/esphome/components/ld2420/text_sensor/text_sensor.h new file mode 100644 index 000000000000..073ddd5d0ffe --- /dev/null +++ b/esphome/components/ld2420/text_sensor/text_sensor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "../ld2420.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace ld2420 { + +class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor { + public: + void dump_config() override; + void set_fw_version_text_sensor(text_sensor::TextSensor *tsensor) { this->fw_version_text_sensor_ = tsensor; }; + void on_fw_version(std::string &fw) override { + if (this->fw_version_text_sensor_ != nullptr) { + this->fw_version_text_sensor_->publish_state(fw); + } + } + + protected: + text_sensor::TextSensor *fw_version_text_sensor_{nullptr}; +}; + +} // namespace ld2420 +} // namespace esphome diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index dfb84c1e7681..0533143d3741 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -96,6 +96,12 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n } #endif +#ifdef USE_ESP_IDF +constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) { + return static_cast(angle * ((1U << bit_depth) - 1) / 360.); +} +#endif // USE_ESP_IDF + void LEDCOutput::write_state(float state) { if (!initialized_) { ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); @@ -117,7 +123,8 @@ void LEDCOutput::write_state(float state) { #ifdef USE_ESP_IDF auto speed_mode = get_speed_mode(channel_); auto chan_num = static_cast(channel_ % 8); - ledc_set_duty(speed_mode, chan_num, duty); + int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); + ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint); ledc_update_duty(speed_mode, chan_num); #endif } @@ -143,8 +150,10 @@ void LEDCOutput::setup() { this->status_set_error(); return; } + 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); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = pin_->get_pin(); @@ -153,7 +162,7 @@ void LEDCOutput::setup() { chan_conf.intr_type = LEDC_INTR_DISABLE; chan_conf.timer_sel = timer_num; chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); - chan_conf.hpoint = 0; + chan_conf.hpoint = hpoint; ledc_channel_config(&chan_conf); initialized_ = true; this->status_clear_error(); @@ -165,6 +174,7 @@ void LEDCOutput::dump_config() { LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_); + ESP_LOGCONFIG(TAG, " Phase angle: %.1f°", this->phase_angle_); ESP_LOGCONFIG(TAG, " Bit depth: %u", 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", diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index 15a58954ef15..67d8d689a640 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -19,6 +19,7 @@ class LEDCOutput : public output::FloatOutput, public Component { void set_channel(uint8_t channel) { this->channel_ = channel; } void set_frequency(float frequency) { this->frequency_ = frequency; } + void set_phase_angle(float angle) { this->phase_angle_ = angle; } /// Dynamically change frequency at runtime void update_frequency(float frequency) override; @@ -35,6 +36,7 @@ class LEDCOutput : public output::FloatOutput, public Component { InternalGPIOPin *pin_; uint8_t channel_{}; uint8_t bit_depth_{}; + float phase_angle_{0.0f}; float frequency_{}; float duty_{0.0f}; bool initialized_ = false; diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 05ddc04a05d2..9df329e1cf3a 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( + CONF_PHASE_ANGLE, CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, @@ -46,6 +47,9 @@ def validate_frequency(value): cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, 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) + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -58,6 +62,8 @@ async def to_code(config): if CONF_CHANNEL in config: cg.add(var.set_channel(config[CONF_CHANNEL])) cg.add(var.set_frequency(config[CONF_FREQUENCY])) + if CONF_PHASE_ANGLE in config: + cg.add(var.set_phase_angle(config[CONF_PHASE_ANGLE])) @automation.register_action( diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 926388a1b9f3..a8034f8fab1e 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -1,14 +1,22 @@ +import json import logging +from os.path import ( + dirname, + isfile, + join, +) import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( CONF_BOARD, CONF_COMPONENT_ID, + CONF_DEBUG, CONF_FAMILY, CONF_FRAMEWORK, CONF_ID, CONF_NAME, + CONF_OPTIONS, CONF_PROJECT, CONF_SOURCE, CONF_VERSION, @@ -24,9 +32,8 @@ from .const import ( CONF_GPIO_RECOVER, CONF_LOGLEVEL, - CONF_LT_CONFIG, CONF_SDK_SILENT, - CONF_SDK_SILENT_ALL, + CONF_UART_PORT, FAMILIES, FAMILY_COMPONENT, FAMILY_FRIENDLY, @@ -35,6 +42,7 @@ KEY_COMPONENT_DATA, KEY_FAMILY, KEY_LIBRETINY, + LT_DEBUG_MODULES, LT_LOGLEVELS, LibreTinyComponent, LTComponent, @@ -51,27 +59,32 @@ def _detect_variant(value): component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] board = value[CONF_BOARD] # read board-default family if not specified - if CONF_FAMILY not in value: - if board not in component.boards: + if board not in component.boards: + if CONF_FAMILY not in value: raise cv.Invalid( - "This board is unknown, please set the family manually. " - "Also, make sure the chosen chip component is correct.", + "This board is unknown, if you are sure you want to compile with this board selection, " + f"override with option '{CONF_FAMILY}'", path=[CONF_BOARD], ) + _LOGGER.warning( + "This board is unknown. Make sure the chosen chip component is correct.", + ) + else: + family = component.boards[board][KEY_FAMILY] + if CONF_FAMILY in value and family != value[CONF_FAMILY]: + raise cv.Invalid( + f"Option '{CONF_FAMILY}' does not match selected board.", + path=[CONF_FAMILY], + ) value = value.copy() - value[CONF_FAMILY] = component.boards[board][KEY_FAMILY] + value[CONF_FAMILY] = family # read component name matching this family value[CONF_COMPONENT_ID] = FAMILY_COMPONENT[value[CONF_FAMILY]] # make sure the chosen component matches the family if value[CONF_COMPONENT_ID] != component.name: raise cv.Invalid( f"The chosen family doesn't belong to '{component.name}' component. The correct component is '{value[CONF_COMPONENT_ID]}'", - path=["variant"], - ) - # warn anyway if the board wasn't found - if board not in component.boards: - _LOGGER.warning( - "This board is unknown. Make sure the chosen chip component is correct.", + path=[CONF_FAMILY], ) return value @@ -118,6 +131,38 @@ def validator_(obj): return validator_ +def get_download_types(storage_json=None): + types = [ + { + "title": "UF2 package (recommended)", + "description": "For flashing via web_server OTA or with ltchiptool (UART)", + "file": "firmware.uf2", + "download": f"{storage_json.name}.uf2", + }, + ] + + build_dir = dirname(storage_json.firmware_bin_path) + outputs = join(build_dir, "firmware.json") + if not isfile(outputs): + return types + with open(outputs, encoding="utf-8") as f: + outputs = json.load(f) + for output in outputs: + if not output["public"]: + continue + suffix = output["filename"].partition(".")[2] + suffix = f"-{suffix}" if "." in suffix else f".{suffix}" + types.append( + { + "title": output["title"], + "description": output["description"], + "file": output["filename"], + "download": storage_json.name + suffix, + } + ) + return types + + def _notify_old_style(config): if config: raise cv.Invalid( @@ -132,9 +177,9 @@ def _notify_old_style(config): # NOTE: Keep this in mind when updating the recommended version: # * For all constants below, update platformio.ini (in this repo) ARDUINO_VERSIONS = { - "dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"), + "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"), "latest": (cv.Version(0, 0, 0), None), - "recommended": (cv.Version(1, 2, 0), None), + "recommended": (cv.Version(1, 5, 1), None), } @@ -158,14 +203,39 @@ def _check_framework_version(value): return value +def _check_debug_order(value): + debug = value[CONF_DEBUG] + if "NONE" in debug and "NONE" in debug[1:]: + raise cv.Invalid( + "'none' has to be specified before other modules, and only once", + path=[CONF_DEBUG], + ) + return value + + FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_LOGLEVEL, default="warn"): ( + cv.one_of(*LT_LOGLEVELS, upper=True) + ), + cv.Optional(CONF_DEBUG, default=[]): cv.ensure_list( + cv.one_of("NONE", *LT_DEBUG_MODULES, upper=True) + ), + cv.Optional(CONF_SDK_SILENT, default="all"): ( + cv.one_of("all", "auto", "none", lower=True) + ), + cv.Optional(CONF_UART_PORT): cv.one_of(0, 1, 2, int=True), + cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean, + cv.Optional(CONF_OPTIONS, default={}): { + cv.string_strict: cv.string, + }, } ), _check_framework_version, + _check_debug_order, ) CONFIG_SCHEMA = cv.All(_notify_old_style) @@ -176,15 +246,6 @@ def _check_framework_version(value): cv.Required(CONF_BOARD): cv.string_strict, cv.Optional(CONF_FAMILY): cv.one_of(*FAMILIES, upper=True), cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, - cv.Optional(CONF_LT_CONFIG, default={}): { - cv.string_strict: cv.string, - }, - cv.Optional(CONF_LOGLEVEL, default="warn"): cv.one_of( - *LT_LOGLEVELS, upper=True - ), - cv.Optional(CONF_SDK_SILENT, default=True): cv.boolean, - cv.Optional(CONF_SDK_SILENT_ALL, default=True): cv.boolean, - cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean, }, ) @@ -199,24 +260,11 @@ async def component_to_code(config): # setup board config cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_LIBRETINY") - cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID]}") + cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID].upper()}") 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]]) - # setup LT logger to work nicely with ESPHome logger - lt_config = dict( - LT_LOGLEVEL="LT_LEVEL_" + config[CONF_LOGLEVEL], - LT_LOGGER_CALLER=0, - LT_LOGGER_TASK=0, - LT_LOGGER_COLOR=1, - LT_DEBUG_ALL=1, - LT_UART_SILENT_ENABLED=int(config[CONF_SDK_SILENT]), - LT_UART_SILENT_ALL=int(config[CONF_SDK_SILENT_ALL]), - LT_USE_TIME=1, - ) - lt_config.update(config[CONF_LT_CONFIG]) - # force using arduino framework cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") @@ -225,7 +273,12 @@ async def component_to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") # include in every file cg.add_platformio_option("build_src_flags", "-include Arduino.h") + # dummy version code + cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)")) + # decrease web server stack size (16k words -> 4k words) + cg.add_build_flag("-DCONFIG_ASYNC_TCP_STACK_SIZE=4096") + # build framework version # if platform version is a valid version constraint, prefix the default package framework = config[CONF_FRAMEWORK] cv.platformio_version_constraint(framework[CONF_VERSION]) @@ -236,19 +289,47 @@ async def component_to_code(config): else: cg.add_platformio_option("platform", "libretiny") - # add LT configuration options - for name, value in sorted(lt_config.items()): + # apply LibreTiny options from framework: block + # setup LT logger to work nicely with ESPHome logger + lt_options = dict( + LT_LOGLEVEL="LT_LEVEL_" + framework[CONF_LOGLEVEL], + LT_LOGGER_CALLER=0, + LT_LOGGER_TASK=0, + LT_LOGGER_COLOR=1, + LT_USE_TIME=1, + ) + # enable/disable per-module debugging + for module in framework[CONF_DEBUG]: + if module == "NONE": + # disable all modules + for module in LT_DEBUG_MODULES: + lt_options[f"LT_DEBUG_{module}"] = 0 + else: + # enable one module + lt_options[f"LT_DEBUG_{module}"] = 1 + # set SDK silencing mode + if framework[CONF_SDK_SILENT] == "all": + lt_options["LT_UART_SILENT_ENABLED"] = 1 + lt_options["LT_UART_SILENT_ALL"] = 1 + elif framework[CONF_SDK_SILENT] == "auto": + lt_options["LT_UART_SILENT_ENABLED"] = 1 + lt_options["LT_UART_SILENT_ALL"] = 0 + else: + lt_options["LT_UART_SILENT_ENABLED"] = 0 + lt_options["LT_UART_SILENT_ALL"] = 0 + # set default UART port + if (uart_port := framework.get(CONF_UART_PORT, None)) is not None: + lt_options["LT_UART_DEFAULT_PORT"] = uart_port + # add custom options + lt_options.update(framework[CONF_OPTIONS]) + + # apply ESPHome options from framework: block + cg.add_define("LT_GPIO_RECOVER", int(framework[CONF_GPIO_RECOVER])) + + # build PlatformIO compiler flags + for name, value in sorted(lt_options.items()): cg.add_build_flag(f"-D{name}={value}") - # add ESPHome LT-related options - cg.add_define("LT_GPIO_RECOVER", int(config[CONF_GPIO_RECOVER])) - - # dummy version code - cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)")) - - # decrease web server stack size (16k words -> 4k words) - cg.add_build_flag("-DCONFIG_ASYNC_TCP_STACK_SIZE=4096") - # custom output firmware name and version if CONF_PROJECT in config: cg.add_platformio_option( diff --git a/esphome/components/libretiny/const.py b/esphome/components/libretiny/const.py index 8c95d3208d55..525d8b77865d 100644 --- a/esphome/components/libretiny/const.py +++ b/esphome/components/libretiny/const.py @@ -14,11 +14,10 @@ class LibreTinyComponent: CONF_LIBRETINY = "libretiny" -CONF_LT_CONFIG = "lt_config" CONF_LOGLEVEL = "loglevel" CONF_SDK_SILENT = "sdk_silent" -CONF_SDK_SILENT_ALL = "sdk_silent_all" CONF_GPIO_RECOVER = "gpio_recover" +CONF_UART_PORT = "uart_port" LT_LOGLEVELS = [ "VERBOSE", @@ -28,6 +27,19 @@ class LibreTinyComponent: "WARN", "ERROR", "FATAL", + "NONE", +] + +LT_DEBUG_MODULES = [ + "WIFI", + "CLIENT", + "SERVER", + "SSL", + "OTA", + "FDB", + "MDNS", + "LWIP", + "LWIP_ASSERT", ] KEY_LIBRETINY = "libretiny" diff --git a/esphome/components/libretiny/gpio.py b/esphome/components/libretiny/gpio.py index ba9bfffcc986..1d7b37cc9b82 100644 --- a/esphome/components/libretiny/gpio.py +++ b/esphome/components/libretiny/gpio.py @@ -186,25 +186,11 @@ def validate_gpio_usage(value): return value -BASE_PIN_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(ArduinoInternalGPIOPin), - cv.Required(CONF_NUMBER): validate_gpio_pin, - cv.Optional(CONF_MODE, default={}): cv.Schema( - { - cv.Optional(CONF_ANALOG, default=False): cv.boolean, - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, - } - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - }, -) - -BASE_PIN_SCHEMA.add_extra(validate_gpio_usage) +BASE_PIN_SCHEMA = pins.gpio_base_schema( + ArduinoInternalGPIOPin, + validate_gpio_pin, + modes=pins.GPIO_STANDARD_MODES + (CONF_ANALOG,), +).add_extra(validate_gpio_usage) async def component_pin_to_code(config): diff --git a/esphome/components/libretiny_pwm/__init__.py b/esphome/components/libretiny_pwm/__init__.py new file mode 100644 index 000000000000..4db724f8ade2 --- /dev/null +++ b/esphome/components/libretiny_pwm/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@kuba2k2"] diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp new file mode 100644 index 000000000000..92e4097c0e9a --- /dev/null +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -0,0 +1,53 @@ +#include "libretiny_pwm.h" +#include "esphome/core/log.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace libretiny_pwm { + +static const char *const TAG = "libretiny.pwm"; + +void LibreTinyPWM::write_state(float state) { + if (!this->initialized_) { + ESP_LOGW(TAG, "LibreTinyPWM output hasn't been initialized yet!"); + return; + } + + if (this->pin_->is_inverted()) + state = 1.0f - state; + + this->duty_ = state; + const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + + analogWrite(this->pin_->get_pin(), duty); // NOLINT +} + +void LibreTinyPWM::setup() { + this->update_frequency(this->frequency_); + this->turn_off(); +} + +void LibreTinyPWM::dump_config() { + ESP_LOGCONFIG(TAG, "PWM Output:"); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); +} + +void LibreTinyPWM::update_frequency(float frequency) { + this->frequency_ = frequency; + // force changing the frequency by removing PWM mode + this->pin_->pin_mode(gpio::FLAG_INPUT); + analogWriteResolution(this->bit_depth_); // NOLINT + analogWriteFrequency(frequency); // NOLINT + this->initialized_ = true; + // re-apply duty + this->write_state(this->duty_); +} + +} // namespace libretiny_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.h b/esphome/components/libretiny_pwm/libretiny_pwm.h new file mode 100644 index 000000000000..42ce40ca392e --- /dev/null +++ b/esphome/components/libretiny_pwm/libretiny_pwm.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" +#include "esphome/components/output/float_output.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace libretiny_pwm { + +class LibreTinyPWM : public output::FloatOutput, public Component { + public: + explicit LibreTinyPWM(InternalGPIOPin *pin) : pin_(pin) {} + + void set_frequency(float frequency) { this->frequency_ = frequency; } + /// Dynamically change frequency at runtime + void update_frequency(float frequency) override; + + /// Setup LibreTinyPWM. + void setup() override; + void dump_config() override; + /// HARDWARE setup priority + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + /// Override FloatOutput's write_state. + void write_state(float state) override; + + protected: + InternalGPIOPin *pin_; + uint8_t bit_depth_{10}; + float frequency_{}; + float duty_{0.0f}; + bool initialized_ = false; +}; + +template class SetFrequencyAction : public Action { + public: + SetFrequencyAction(LibreTinyPWM *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, frequency); + + void play(Ts... x) { + float freq = this->frequency_.value(x...); + this->parent_->update_frequency(freq); + } + + protected: + LibreTinyPWM *parent_; +}; + +} // namespace libretiny_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/libretiny_pwm/output.py b/esphome/components/libretiny_pwm/output.py new file mode 100644 index 000000000000..e74bc8f12917 --- /dev/null +++ b/esphome/components/libretiny_pwm/output.py @@ -0,0 +1,49 @@ +from esphome import pins, automation +from esphome.components import output +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_PIN, +) + +DEPENDENCIES = ["libretiny"] + +libretinypwm_ns = cg.esphome_ns.namespace("libretiny_pwm") +LibreTinyPWM = libretinypwm_ns.class_("LibreTinyPWM", output.FloatOutput, cg.Component) +SetFrequencyAction = libretinypwm_ns.class_("SetFrequencyAction", automation.Action) + +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, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + gpio = await cg.gpio_pin_expression(config[CONF_PIN]) + var = cg.new_Pvariable(config[CONF_ID], gpio) + await cg.register_component(var, config) + await output.register_output(var, config) + cg.add(var.set_frequency(config[CONF_FREQUENCY])) + + +@automation.register_action( + "output.libretiny_pwm.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LibreTinyPWM), + cv.Required(CONF_FREQUENCY): cv.templatable(cv.int_), + } + ), +) +async def libretiny_pwm_set_frequency_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) + template_ = await cg.templatable(config[CONF_FREQUENCY], args, float) + cg.add(var.set_frequency(template_)) + return var diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index ba3a26ebe586..fdc4676758d1 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -137,18 +137,16 @@ async def setup_light_core_(light_var, output_var, config): cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) - if CONF_DEFAULT_TRANSITION_LENGTH in config: - cg.add( - light_var.set_default_transition_length( - config[CONF_DEFAULT_TRANSITION_LENGTH] - ) - ) - if CONF_FLASH_TRANSITION_LENGTH in config: - cg.add( - light_var.set_flash_transition_length(config[CONF_FLASH_TRANSITION_LENGTH]) - ) - if CONF_GAMMA_CORRECT in config: - cg.add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT])) + if ( + default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH) + ) is not None: + cg.add(light_var.set_default_transition_length(default_transition_length)) + if ( + flash_transition_length := config.get(CONF_FLASH_TRANSITION_LENGTH) + ) is not None: + cg.add(light_var.set_flash_transition_length(flash_transition_length)) + if (gamma_correct := config.get(CONF_GAMMA_CORRECT)) is not None: + cg.add(light_var.set_gamma_correct(gamma_correct)) effects = await cg.build_registry_list( EFFECTS_REGISTRY, config.get(CONF_EFFECTS, []) ) @@ -164,15 +162,15 @@ async def setup_light_core_(light_var, output_var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) await auto.build_automation(trigger, [], conf) - if CONF_COLOR_CORRECT in config: - cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) + if (color_correct := config.get(CONF_COLOR_CORRECT)) is not None: + cg.add(output_var.set_correction(*color_correct)) - if CONF_POWER_SUPPLY in config: - var_ = await cg.get_variable(config[CONF_POWER_SUPPLY]) + if (power_supply_id := config.get(CONF_POWER_SUPPLY)) is not None: + var_ = await cg.get_variable(power_supply_id) cg.add(output_var.set_power_supply(var_)) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], light_var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, light_var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 0482cf53b986..73083a58b7e7 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -57,7 +57,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect { void start() override { this->initial_run_ = true; } void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); - if (now - this->last_run_ >= this->update_interval_) { + if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) { this->last_run_ = now; this->f_(it, current_color, this->initial_run_); this->initial_run_ = false; @@ -100,6 +100,7 @@ struct AddressableColorWipeEffectColor { uint8_t r, g, b, w; bool random; size_t num_leds; + bool gradient; }; class AddressableColorWipeEffect : public AddressableLightEffect { @@ -117,8 +118,15 @@ class AddressableColorWipeEffect : public AddressableLightEffect { it.shift_left(1); else it.shift_right(1); - const AddressableColorWipeEffectColor color = this->colors_[this->at_color_]; - const Color esp_color = Color(color.r, color.g, color.b, color.w); + const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_]; + Color esp_color = Color(color.r, color.g, color.b, color.w); + if (color.gradient) { + size_t next_color_index = (this->at_color_ + 1) % this->colors_.size(); + const AddressableColorWipeEffectColor &next_color = this->colors_[next_color_index]; + const Color next_esp_color = Color(next_color.r, next_color.g, next_color.b, next_color.w); + uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds); + esp_color = esp_color.gradient(next_esp_color, gradient); + } if (this->reverse_) it[-1] = esp_color; else diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 9211bba7c978..f7829a3f44cb 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -3,8 +3,8 @@ #include #include -#include "light_effect.h" #include "esphome/core/automation.h" +#include "light_effect.h" namespace esphome { namespace light { @@ -27,8 +27,8 @@ class PulseLightEffect : public LightEffect { auto call = this->state_->turn_on(); float out = this->on_ ? this->max_brightness : this->min_brightness; call.set_brightness_if_supported(out); + call.set_transition_length_if_supported(this->on_ ? this->transition_on_length_ : this->transition_off_length_); this->on_ = !this->on_; - call.set_transition_length_if_supported(this->transition_length_); // don't tell HA every change call.set_publish(false); call.set_save(false); @@ -37,7 +37,8 @@ class PulseLightEffect : public LightEffect { this->last_color_change_ = now; } - void set_transition_length(uint32_t transition_length) { this->transition_length_ = transition_length; } + void set_transition_on_length(uint32_t transition_length) { this->transition_on_length_ = transition_length; } + void set_transition_off_length(uint32_t transition_length) { this->transition_off_length_ = transition_length; } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } @@ -49,7 +50,8 @@ class PulseLightEffect : public LightEffect { protected: bool on_ = false; uint32_t last_color_change_{0}; - uint32_t transition_length_{}; + uint32_t transition_on_length_{}; + uint32_t transition_off_length_{}; uint32_t update_interval_{}; float min_brightness{0.0}; float max_brightness{1.0}; @@ -116,7 +118,7 @@ class LambdaLightEffect : public LightEffect { void start() override { this->initial_run_ = true; } void apply() override { const uint32_t now = millis(); - if (now - this->last_run_ >= this->update_interval_) { + if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) { this->last_run_ = now; this->f_(this->initial_run_); this->initial_run_ = false; @@ -148,6 +150,7 @@ class AutomationLightEffect : public LightEffect { struct StrobeLightEffectColor { LightColorValues color; uint32_t duration; + uint32_t transition_length; }; class StrobeLightEffect : public LightEffect { @@ -172,7 +175,7 @@ class StrobeLightEffect : public LightEffect { } call.set_publish(false); call.set_save(false); - call.set_transition_length_if_supported(0); + call.set_transition_length_if_supported(this->colors_[this->at_color_].transition_length); call.perform(); this->last_switch_ = now; } diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index c694d6f50c05..be50f6332128 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -58,6 +58,7 @@ CONF_ADD_LED_INTERVAL = "add_led_interval" CONF_REVERSE = "reverse" +CONF_GRADIENT = "gradient" CONF_MOVE_INTERVAL = "move_interval" CONF_SCAN_WIDTH = "scan_width" CONF_TWINKLE_PROBABILITY = "twinkle_probability" @@ -76,6 +77,8 @@ CONF_ADDRESSABLE_FIREWORKS = "addressable_fireworks" CONF_ADDRESSABLE_FLICKER = "addressable_flicker" CONF_AUTOMATION = "automation" +CONF_ON_LENGTH = "on_length" +CONF_OFF_LENGTH = "off_length" BINARY_EFFECTS = [] MONOCHROMATIC_EFFECTS = [] @@ -170,9 +173,15 @@ async def automation_effect_to_code(config, effect_id): PulseLightEffect, "Pulse", { - cv.Optional( - CONF_TRANSITION_LENGTH, default="1s" - ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_TRANSITION_LENGTH, default="1s"): cv.Any( + cv.positive_time_period_milliseconds, + cv.Schema( + { + cv.Required(CONF_ON_LENGTH): cv.positive_time_period_milliseconds, + cv.Required(CONF_OFF_LENGTH): cv.positive_time_period_milliseconds, + } + ), + ), cv.Optional( CONF_UPDATE_INTERVAL, default="1s" ): cv.positive_time_period_milliseconds, @@ -182,7 +191,21 @@ async def automation_effect_to_code(config, effect_id): ) async def pulse_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) - cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH])) + if isinstance(config[CONF_TRANSITION_LENGTH], dict): + cg.add( + effect.set_transition_on_length( + config[CONF_TRANSITION_LENGTH][CONF_ON_LENGTH] + ) + ) + cg.add( + effect.set_transition_off_length( + config[CONF_TRANSITION_LENGTH][CONF_OFF_LENGTH] + ) + ) + else: + transition_length = config[CONF_TRANSITION_LENGTH] + cg.add(effect.set_transition_on_length(transition_length)) + cg.add(effect.set_transition_off_length(transition_length)) cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL])) cg.add( effect.set_min_max_brightness( @@ -243,6 +266,9 @@ async def random_effect_to_code(config, effect_id): cv.Required( CONF_DURATION ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_TRANSITION_LENGTH, default="0s" + ): cv.positive_time_period_milliseconds, } ), cv.has_at_least_one_key( @@ -287,6 +313,7 @@ async def strobe_effect_to_code(config, effect_id): ), ), ("duration", color[CONF_DURATION]), + ("transition_length", color[CONF_TRANSITION_LENGTH]), ) ) cg.add(var.set_colors(colors)) @@ -364,6 +391,7 @@ async def addressable_rainbow_effect_to_code(config, effect_id): cv.Optional(CONF_WHITE, default=1.0): cv.percentage, cv.Optional(CONF_RANDOM, default=False): cv.boolean, cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)), + cv.Optional(CONF_GRADIENT, default=False): cv.boolean, } ), cv.Optional( @@ -387,6 +415,7 @@ async def addressable_color_wipe_effect_to_code(config, effect_id): ("w", int(round(color[CONF_WHITE] * 255))), ("random", color[CONF_RANDOM]), ("num_leds", color[CONF_NUM_LEDS]), + ("gradient", color[CONF_GRADIENT]), ) ) cg.add(var.set_colors(colors)) diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h index 8788246cfcf1..eedd71ab277c 100644 --- a/esphome/components/light/esp_color_correction.h +++ b/esphome/components/light/esp_color_correction.h @@ -11,54 +11,54 @@ class ESPColorCorrection { void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; } void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; } void calculate_gamma_table(float gamma); - inline Color color_correct(Color color) const ALWAYS_INLINE { + inline Color color_correct(Color color) const ESPHOME_ALWAYS_INLINE { // corrected = (uncorrected * max_brightness * local_brightness) ^ gamma return Color(this->color_correct_red(color.red), this->color_correct_green(color.green), this->color_correct_blue(color.blue), this->color_correct_white(color.white)); } - inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE { + inline uint8_t color_correct_red(uint8_t red) const ESPHOME_ALWAYS_INLINE { uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_); return this->gamma_table_[res]; } - inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE { + inline uint8_t color_correct_green(uint8_t green) const ESPHOME_ALWAYS_INLINE { uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_); return this->gamma_table_[res]; } - inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE { + inline uint8_t color_correct_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE { uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_); return this->gamma_table_[res]; } - inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE { + inline uint8_t color_correct_white(uint8_t white) const ESPHOME_ALWAYS_INLINE { uint8_t res = esp_scale8(esp_scale8(white, this->max_brightness_.white), this->local_brightness_); return this->gamma_table_[res]; } - inline Color color_uncorrect(Color color) const ALWAYS_INLINE { + inline Color color_uncorrect(Color color) const ESPHOME_ALWAYS_INLINE { // uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness) return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); } - inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE { + inline uint8_t color_uncorrect_red(uint8_t red) const ESPHOME_ALWAYS_INLINE { if (this->max_brightness_.red == 0 || this->local_brightness_ == 0) return 0; uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL; uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_; return res; } - inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE { + inline uint8_t color_uncorrect_green(uint8_t green) const ESPHOME_ALWAYS_INLINE { if (this->max_brightness_.green == 0 || this->local_brightness_ == 0) return 0; uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL; uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_; return res; } - inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE { + inline uint8_t color_uncorrect_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE { if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0) return 0; uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL; uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_; return res; } - inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE { + inline uint8_t color_uncorrect_white(uint8_t white) const ESPHOME_ALWAYS_INLINE { if (this->max_brightness_.white == 0 || this->local_brightness_ == 0) return 0; uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; diff --git a/esphome/components/light/esp_hsv_color.h b/esphome/components/light/esp_hsv_color.h index e0aa3888754c..39f5e557072d 100644 --- a/esphome/components/light/esp_hsv_color.h +++ b/esphome/components/light/esp_hsv_color.h @@ -24,11 +24,11 @@ struct ESPHSVColor { }; uint8_t raw[3]; }; - inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT + inline ESPHSVColor() ESPHOME_ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT } - inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue), - saturation(saturation), - value(value) {} + inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ESPHOME_ALWAYS_INLINE : hue(hue), + saturation(saturation), + value(value) {} Color to_rgb() const; }; diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 8dc5d4fbe7f6..c2600d05c208 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -337,9 +337,12 @@ LightColorValues LightCall::validate_() { void LightCall::transform_parameters_() { auto traits = this->parent_->get_traits(); - // Allow CWWW modes to be set with a white value and/or color temperature. This is used by HA, - // which doesn't support CWWW modes (yet?), and for compatibility with the pre-colormode model, - // as CWWW and RGBWW lights used to represent their values as white + color temperature. + // Allow CWWW modes to be set with a white value and/or color temperature. + // This is used in three cases in HA: + // - CW/WW lights, which set the "brightness" and "color_temperature" + // - RGBWW lights with color_interlock=true, which also sets "brightness" and + // "color_temperature" (without color_interlock, CW/WW are set directly) + // - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature" if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && // (*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // !(*this->color_mode_ & ColorCapability::WHITE) && // @@ -347,21 +350,17 @@ void LightCall::transform_parameters_() { traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.", this->parent_->get_name().c_str()); - auto current_values = this->parent_->remote_values; if (this->color_temperature_.has_value()) { - const float white = - this->white_.value_or(fmaxf(current_values.get_cold_white(), current_values.get_warm_white())); const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); const float ww_fraction = (color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); const float cw_fraction = 1.0f - ww_fraction; const float max_cw_ww = std::max(ww_fraction, cw_fraction); - this->cold_white_ = white * gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); - this->warm_white_ = white * gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); - } else { - const float max_cw_ww = std::max(current_values.get_warm_white(), current_values.get_cold_white()); - this->cold_white_ = *this->white_ * current_values.get_cold_white() / max_cw_ww; - this->warm_white_ = *this->white_ * current_values.get_warm_white() / max_cw_ww; + this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); + } + if (this->white_.has_value()) { + this->brightness_ = *this->white_; } } } diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 10a3c2f3354d..bad180ce6dc8 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -266,6 +266,21 @@ class LightColorValues { /// Set the color temperature property of these light color values in mired. void set_color_temperature(float color_temperature) { this->color_temperature_ = color_temperature; } + /// Get the color temperature property of these light color values in kelvin. + float get_color_temperature_kelvin() const { + if (this->color_temperature_ <= 0) { + return this->color_temperature_; + } + return 1000000.0 / this->color_temperature_; + } + /// Set the color temperature property of these light color values in kelvin. + void set_color_temperature_kelvin(float color_temperature) { + if (color_temperature <= 0) { + return; + } + this->color_temperature_ = 1000000.0 / color_temperature; + } + /// Get the cold white property of these light color values. In range 0.0 to 1.0. float get_cold_white() const { return this->cold_white_; } /// Set the cold white property of these light color values. In range 0.0 to 1.0. diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 50ebd8882b18..fe6538e65e20 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -120,6 +120,7 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { auto values = this->transformer_->apply(); + this->is_transformer_active_ = true; if (values.has_value()) { this->current_values = *values; this->output_->update_state(this); @@ -131,6 +132,7 @@ void LightState::loop() { this->current_values = this->transformer_->get_target_values(); this->transformer_->stop(); + this->is_transformer_active_ = false; this->transformer_ = nullptr; this->target_state_reached_callback_.call(); } @@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri this->gamma_correct_); } +bool LightState::is_transformer_active() { return this->is_transformer_active_; } + void LightState::start_effect_(uint32_t effect_index) { this->stop_effect_(); if (effect_index == 0) @@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { + this->is_transformer_active_ = false; this->transformer_ = nullptr; this->current_values = target; if (set_remote_values) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index ac4718ade5ed..b0aaa453b5b3 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component { void current_values_as_ct(float *color_temperature, float *white_brightness); + /** + * Indicator if a transformer (e.g. transition) is active. This is useful + * for effects e.g. at the start of the apply() method, add a check like: + * + * if (this->state_->is_transformer_active()) { + * // Something is already running. + * return; + * } + */ + bool is_transformer_active(); + protected: friend LightOutput; friend LightCall; @@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component { LightRestoreMode restore_mode_; /// List of effects for this light. std::vector effects_; + + // for effects, true if a transformer (transition) is active. + bool is_transformer_active_ = false; }; } // namespace light diff --git a/esphome/components/lightwaverf/LwRx.cpp b/esphome/components/lightwaverf/LwRx.cpp new file mode 100644 index 000000000000..2b1ad5e87005 --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.cpp @@ -0,0 +1,427 @@ +// LwRx.cpp +// +// LightwaveRF 434MHz receiver interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +#ifdef USE_ESP8266 + +#include "LwRx.h" +#include + +namespace esphome { +namespace lightwaverf { + +/** + Pin change interrupt routine that identifies 1 and 0 LightwaveRF bits + and constructs a message when a valid packet of data is received. +**/ + +void IRAM_ATTR LwRx::rx_process_bits(LwRx *args) { + uint8_t event = args->rx_pin_isr_.digital_read(); // start setting event to the current value + uint32_t curr = micros(); // the current time in microseconds + + uint16_t dur = (curr - args->rx_prev); // unsigned int + args->rx_prev = curr; + // set event based on input and duration of previous pulse + if (dur < 120) { // 120 very short + } else if (dur < 500) { // normal short pulse + event += 2; + } else if (dur < 2000) { // normal long pulse + event += 4; + } else if (dur > 5000) { // gap between messages + event += 6; + } else { // 2000 > 5000 + event = 8; // illegal gap + } + // state machine transitions + switch (args->rx_state) { + case RX_STATE_IDLE: + switch (event) { + case 7: // 1 after a message gap + args->rx_state = RX_STATE_MSGSTARTFOUND; + break; + } + break; + case RX_STATE_MSGSTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_num_bytes = 0; + args->rx_state = RX_STATE_BYTESTARTFOUND; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_BYTESTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_state = RX_STATE_GETBYTE; + args->rx_num_bits = 0; + break; + case 5: // 0 500->1500 + args->rx_state = RX_STATE_GETBYTE; + // Starts with 0 so put this into uint8_t + args->rx_num_bits = 1; + args->rx_buf[args->rx_num_bytes] = 0; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_GETBYTE: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge but do stats + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_HIGH_MAX] = std::max(args->lwrx_stats[RX_STAT_HIGH_MAX], dur); + args->lwrx_stats[RX_STAT_HIGH_MIN] = std::min(args->lwrx_stats[RX_STAT_HIGH_MIN], dur); + args->lwrx_stats[RX_STAT_HIGH_AVE] = + args->lwrx_stats[RX_STAT_HIGH_AVE] - (args->lwrx_stats[RX_STAT_HIGH_AVE] >> 4) + dur; + } + break; + case 3: // 1 160->500 + // a single 1 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 1 | 1; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW1_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW1_MAX], dur); + args->lwrx_stats[RX_STAT_LOW1_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW1_MIN], dur); + args->lwrx_stats[RX_STAT_LOW1_AVE] = + args->lwrx_stats[RX_STAT_LOW1_AVE] - (args->lwrx_stats[RX_STAT_LOW1_AVE] >> 4) + dur; + } + break; + case 5: // 1 500->1500 + // a 1 followed by a 0 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 2 | 2; + args->rx_num_bits++; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW0_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW0_MAX], dur); + args->lwrx_stats[RX_STAT_LOW0_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW0_MIN], dur); + args->lwrx_stats[RX_STAT_LOW0_AVE] = + args->lwrx_stats[RX_STAT_LOW0_AVE] - (args->lwrx_stats[RX_STAT_LOW0_AVE] >> 4) + dur; + } + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + if (args->rx_num_bits >= 8) { + args->rx_num_bytes++; + args->rx_num_bits = 0; + if (args->rx_num_bytes >= RX_MSGLEN) { + uint32_t curr_millis = millis(); + if (args->rx_repeats > 0) { + if ((curr_millis - args->rx_prevpkttime) / 100 > args->rx_timeout) { + args->rx_repeatcount = 1; + } else { + // Test message same as last one + int16_t i = RX_MSGLEN; // int + do { + i--; + } while ((i >= 0) && (args->rx_msg[i] == args->rx_buf[i])); + if (i < 0) { + args->rx_repeatcount++; + } else { + args->rx_repeatcount = 1; + } + } + } else { + args->rx_repeatcount = 0; + } + args->rx_prevpkttime = curr_millis; + // If last message hasn't been read it gets overwritten + memcpy(args->rx_msg, args->rx_buf, RX_MSGLEN); + if (args->rx_repeats == 0 || args->rx_repeatcount == args->rx_repeats) { + if (args->rx_pairtimeout != 0) { + if ((curr_millis - args->rx_pairstarttime) / 100 <= args->rx_pairtimeout) { + if (args->rx_msg[3] == RX_CMD_ON) { + args->rx_addpairfrommsg_(); + } else if (args->rx_msg[3] == RX_CMD_OFF) { + args->rx_remove_pair_(&args->rx_msg[2]); + } + } + } + if (args->rx_report_message_()) { + args->rx_msgcomplete = true; + } + args->rx_pairtimeout = 0; + } + // And cycle round for next one + args->rx_state = RX_STATE_IDLE; + } else { + args->rx_state = RX_STATE_BYTESTARTFOUND; + } + } + break; + } +} + +/** + Test if a message has arrived +**/ +bool LwRx::lwrx_message() { return (this->rx_msgcomplete); } + +/** + Set translate mode +**/ +void LwRx::lwrx_settranslate(bool rxtranslate) { this->rx_translate = rxtranslate; } +/** + Transfer a message to user buffer +**/ +bool LwRx::lwrx_getmessage(uint8_t *buf, uint8_t len) { + bool ret = true; + int16_t j = 0; // int + if (this->rx_msgcomplete && len <= RX_MSGLEN) { + for (uint8_t i = 0; ret && i < RX_MSGLEN; i++) { + if (this->rx_translate || (len != RX_MSGLEN)) { + j = this->rx_find_nibble_(this->rx_msg[i]); + if (j < 0) + ret = false; + } else { + j = this->rx_msg[i]; + } + switch (len) { + case 4: + if (i == 9) + buf[2] = j; + if (i == 2) + buf[3] = j; + case 2: + if (i == 3) + buf[0] = j; + if (i == 0) + buf[1] = j << 4; + if (i == 1) + buf[1] += j; + break; + case 10: + buf[i] = j; + break; + } + } + this->rx_msgcomplete = false; + } else { + ret = false; + } + return ret; +} + +/** + Return time in milliseconds since last packet received +**/ +uint32_t LwRx::lwrx_packetinterval() { return millis() - this->rx_prevpkttime; } + +/** + Set up repeat filtering of received messages +**/ +void LwRx::lwrx_setfilter(uint8_t repeats, uint8_t timeout) { + this->rx_repeats = repeats; + this->rx_timeout = timeout; +} + +/** + Add a pair to filter received messages + pairdata is device,dummy,5*addr,room + pairdata is held in translated form to make comparisons quicker +**/ +uint8_t LwRx::lwrx_addpair(const uint8_t *pairdata) { + if (this->rx_paircount < RX_MAXPAIRS) { + for (uint8_t i = 0; i < 8; i++) { + this->rx_pairs[rx_paircount][i] = RX_NIBBLE[pairdata[i]]; + } + this->rx_paircommit_(); + } + return this->rx_paircount; +} + +/** + Make a pair from next message successfully received +**/ +void LwRx::lwrx_makepair(uint8_t timeout) { + this->rx_pairtimeout = timeout; + this->rx_pairstarttime = millis(); +} + +/** + Get pair data (translated back to nibble form +**/ +uint8_t LwRx::lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber) { + if (pairnumber < this->rx_paircount) { + int16_t j; // int + for (uint8_t i = 0; i < 8; i++) { + j = this->rx_find_nibble_(this->rx_pairs[pairnumber][i]); + if (j >= 0) + pairdata[i] = j; + } + } + return this->rx_paircount; +} + +/** + Clear all pairing +**/ +void LwRx::lwrx_clearpairing_() { rx_paircount = 0; } + +/** + Return stats on high and low pulses +**/ +bool LwRx::lwrx_getstats_(uint16_t *stats) { // unsigned int + if (this->lwrx_stats_enable) { + memcpy(stats, this->lwrx_stats, 2 * RX_STAT_COUNT); + return true; + } else { + return false; + } +} + +/** + Set stats mode +**/ +void LwRx::lwrx_setstatsenable_(bool rx_stats_enable) { + this->lwrx_stats_enable = rx_stats_enable; + if (!this->lwrx_stats_enable) { + // clear down stats when disabling + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); + } +} +/** + Set pairs behaviour +**/ +void LwRx::lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only) { + this->rx_pairEnforce = pair_enforce; + this->rx_pairBaseOnly = pair_base_only; +} + +/** + Set things up to receive LightWaveRF 434Mhz messages + pin must be 2 or 3 to trigger interrupts + !!! For Spark, any pin will work +**/ +void LwRx::lwrx_setup(InternalGPIOPin *pin) { + // rx_pin = pin; + pin->setup(); + this->rx_pin_isr_ = pin->to_isr(); + pin->attach_interrupt(&LwRx::rx_process_bits, this, gpio::INTERRUPT_ANY_EDGE); + + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); +} + +/** + Check a message to see if it should be reported under pairing / mood / all off rules + returns -1 if none found +**/ +bool LwRx::rx_report_message_() { + if (this->rx_pairEnforce && this->rx_paircount == 0) { + return false; + } else { + bool all_devices; + // True if mood to device 15 or Off cmd with Allof paramater + all_devices = ((this->rx_msg[3] == RX_CMD_MOOD && this->rx_msg[2] == RX_DEV_15) || + (this->rx_msg[3] == RX_CMD_OFF && this->rx_msg[0] == RX_PAR0_ALLOFF)); + return (rx_check_pairs_(&this->rx_msg[2], all_devices) != -1); + } +} +/** + Find nibble from byte + returns -1 if none found +**/ +int16_t LwRx::rx_find_nibble_(uint8_t data) { // int + int16_t i = 15; // int + do { + if (RX_NIBBLE[i] == data) + break; + i--; + } while (i >= 0); + return i; +} + +/** + add pair from message buffer +**/ +void LwRx::rx_addpairfrommsg_() { + if (this->rx_paircount < RX_MAXPAIRS) { + memcpy(this->rx_pairs[this->rx_paircount], &this->rx_msg[2], 8); + this->rx_paircommit_(); + } +} + +/** + check and commit pair +**/ +void LwRx::rx_paircommit_() { + if (this->rx_paircount == 0 || this->rx_check_pairs_(this->rx_pairs[this->rx_paircount], false) < 0) { + this->rx_paircount++; + } +} + +/** + Check to see if message matches one of the pairs + if mode is pairBase only then ignore device and room + if allDevices is true then ignore the device number + Returns matching pair number, -1 if not found, -2 if no pairs defined +**/ +int16_t LwRx::rx_check_pairs_(const uint8_t *buf, bool all_devices) { // int + if (this->rx_paircount == 0) { + return -2; + } else { + int16_t pair = this->rx_paircount; // int + int16_t j = -1; // int + int16_t jstart, jend; // int + if (this->rx_pairBaseOnly) { + // skip room(8) and dev/cmd (0,1) + jstart = 7; + jend = 2; + } else { + // include room in comparison + jstart = 8; + // skip device comparison if allDevices true + jend = (all_devices) ? 2 : 0; + } + while (pair > 0 && j < 0) { + pair--; + j = jstart; + while (j > jend) { + j--; + if (j != 1) { + if (this->rx_pairs[pair][j] != buf[j]) { + j = -1; + } + } + } + } + return (j >= 0) ? pair : -1; + } +} + +/** + Remove an existing pair matching the buffer +**/ +void LwRx::rx_remove_pair_(uint8_t *buf) { + int16_t pair = this->rx_check_pairs_(buf, false); // int + if (pair >= 0) { + while (pair < this->rx_paircount - 1) { + for (uint8_t j = 0; j < 8; j++) { + this->rx_pairs[pair][j] = this->rx_pairs[pair + 1][j]; + } + pair++; + } + this->rx_paircount--; + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwRx.h b/esphome/components/lightwaverf/LwRx.h new file mode 100644 index 000000000000..7200f9a51c70 --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.h @@ -0,0 +1,142 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lightwaverf { + +// LwRx.h +// +// LightwaveRF 434MHz receiver for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +static const uint8_t RX_STAT_HIGH_AVE = 0; +static const uint8_t RX_STAT_HIGH_MAX = 1; +static const uint8_t RX_STAT_HIGH_MIN = 2; +static const uint8_t RX_STAT_LOW0_AVE = 3; +static const uint8_t RX_STAT_LOW0_MAX = 4; +static const uint8_t RX_STAT_LOW0_MIN = 5; +static const uint8_t RX_STAT_LOW1_AVE = 6; +static const uint8_t RX_STAT_LOW1_MAX = 7; +static const uint8_t RX_STAT_LOW1_MIN = 8; +static const uint8_t RX_STAT_COUNT = 9; + +// sets maximum number of pairings which can be held +static const uint8_t RX_MAXPAIRS = 10; + +static const uint8_t RX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; +static const uint8_t RX_CMD_OFF = 0xF6; // raw 0 +static const uint8_t RX_CMD_ON = 0xEE; // raw 1 +static const uint8_t RX_CMD_MOOD = 0xED; // raw 2 +static const uint8_t RX_PAR0_ALLOFF = 0x7D; // param 192-255 all off (12 in msb) +static const uint8_t RX_DEV_15 = 0x6F; // device 15 + +static const uint8_t RX_MSGLEN = 10; // expected length of rx message + +static const uint8_t RX_STATE_IDLE = 0; +static const uint8_t RX_STATE_MSGSTARTFOUND = 1; +static const uint8_t RX_STATE_BYTESTARTFOUND = 2; +static const uint8_t RX_STATE_GETBYTE = 3; + +// Gather stats for pulse widths (ave is x 16) +static const uint16_t LWRX_STATSDFLT[RX_STAT_COUNT] = {5000, 0, 5000, 20000, 0, 2500, 4000, 0, 500}; // usigned int + +class LwRx { + public: + // Seup must be called once, set up pin used to receive data + void lwrx_setup(InternalGPIOPin *pin); + + // Set translate to determine whether translating from nibbles to bytes in message + // Translate off only applies to 10char message returns + void lwrx_settranslate(bool translate); + + // Check to see whether message available + bool lwrx_message(); + + // Get a message, len controls format (2 cmd+param, 4 cmd+param+room+device),10 full message + bool lwrx_getmessage(uint8_t *buf, uint8_t len); + + // Setup repeat filter + void lwrx_setfilter(uint8_t repeats, uint8_t timeout); + + // Add pair, if no pairing set then all messages are received, returns number of pairs + uint8_t lwrx_addpair(const uint8_t *pairdata); + + // Get pair data into buffer for the pairnumber. Returns current paircount + // Use pairnumber 255 to just get current paircount + uint8_t lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber); + + // Make a pair from next message received within timeout 100mSec + // This call returns immediately whilst message checking continues + void lwrx_makepair(uint8_t timeout); + + // Set pair mode controls + void lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only); + + // Returns time from last packet received in msec + // Can be used to determine if Rx may be still receiving repeats + uint32_t lwrx_packetinterval(); + + static void rx_process_bits(LwRx *arg); + + // Pairing data + uint8_t rx_paircount = 0; + uint8_t rx_pairs[RX_MAXPAIRS][8]; + // set false to responds to all messages if no pairs set up + bool rx_pairEnforce = false; + // set false to use Address, Room and Device in pairs, true just the Address part + bool rx_pairBaseOnly = false; + + uint8_t rx_pairtimeout = 0; // 100msec units + + // Repeat filters + uint8_t rx_repeats = 2; // msg must be repeated at least this number of times + uint8_t rx_repeatcount = 0; + uint8_t rx_timeout = 20; // reset repeat window after this in 100mSecs + uint32_t rx_prevpkttime = 0; // last packet time in milliseconds + uint32_t rx_pairstarttime = 0; // last msg time in milliseconds + + // Receive mode constants and variables + uint8_t rx_msg[RX_MSGLEN]; // raw message received + uint8_t rx_buf[RX_MSGLEN]; // message buffer during reception + + uint32_t rx_prev; // time of previous interrupt in microseconds + + bool rx_msgcomplete = false; // set high when message available + bool rx_translate = true; // Set false to get raw data + + uint8_t rx_state = 0; + + uint8_t rx_num_bits = 0; // number of bits in the current uint8_t + uint8_t rx_num_bytes = 0; // number of bytes received + + uint16_t lwrx_stats[RX_STAT_COUNT]; // unsigned int + + bool lwrx_stats_enable = true; + + protected: + void lwrx_clearpairing_(); + + // Return stats on pulse timings + bool lwrx_getstats_(uint16_t *stats); + + // Enable collection of stats on pulse timings + void lwrx_setstatsenable_(bool rx_stats_enable); + + // internal support functions + bool rx_report_message_(); + int16_t rx_find_nibble_(uint8_t data); // int + void rx_addpairfrommsg_(); + void rx_paircommit_(); + void rx_remove_pair_(uint8_t *buf); + int16_t rx_check_pairs_(const uint8_t *buf, bool all_devices); // int + + ISRInternalGPIOPin rx_pin_isr_; + InternalGPIOPin *rx_pin_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/LwTx.cpp b/esphome/components/lightwaverf/LwTx.cpp new file mode 100644 index 000000000000..2f46b04b2d09 --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.cpp @@ -0,0 +1,208 @@ +// LwTx.cpp +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) +#ifdef USE_ESP8266 + +#include "LwTx.h" +#include +#include +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace lightwaverf { + +static const uint8_t TX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; + +static const uint8_t TX_STATE_IDLE = 0; +static const uint8_t TX_STATE_MSGSTART = 1; +static const uint8_t TX_STATE_BYTESTART = 2; +static const uint8_t TX_STATE_SENDBYTE = 3; +static const uint8_t TX_STATE_MSGEND = 4; +static const uint8_t TX_STATE_GAPSTART = 5; +static const uint8_t TX_STATE_GAPEND = 6; +/** + Set translate mode +**/ +void LwTx::lwtx_settranslate(bool txtranslate) { tx_translate = txtranslate; } + +static void IRAM_ATTR isr_t_xtimer(LwTx *arg) { + // Set low after toggle count interrupts + arg->tx_toggle_count--; + if (arg->tx_toggle_count == arg->tx_trail_count) { + // ESP_LOGD("lightwaverf.sensor", "timer") + arg->tx_pin->digital_write(arg->txoff); + } else if (arg->tx_toggle_count == 0) { + arg->tx_toggle_count = arg->tx_high_count; // default high pulse duration + switch (arg->tx_state) { + case TX_STATE_IDLE: + if (arg->tx_msg_active) { + arg->tx_repeat = 0; + arg->tx_state = TX_STATE_MSGSTART; + } + break; + case TX_STATE_MSGSTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_num_bytes = 0; + arg->tx_state = TX_STATE_BYTESTART; + break; + case TX_STATE_BYTESTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_bit_mask = 0x80; + arg->tx_state = TX_STATE_SENDBYTE; + break; + case TX_STATE_SENDBYTE: + if (arg->tx_buf[arg->tx_num_bytes] & arg->tx_bit_mask) { + arg->tx_pin->digital_write(arg->txon); + } else { + // toggle count for the 0 pulse + arg->tx_toggle_count = arg->tx_low_count; + } + arg->tx_bit_mask >>= 1; + if (arg->tx_bit_mask == 0) { + arg->tx_num_bytes++; + if (arg->tx_num_bytes >= esphome::lightwaverf::LwTx::TX_MSGLEN) { + arg->tx_state = TX_STATE_MSGEND; + } else { + arg->tx_state = TX_STATE_BYTESTART; + } + } + break; + case TX_STATE_MSGEND: + arg->tx_pin->digital_write(arg->txon); + arg->tx_state = TX_STATE_GAPSTART; + arg->tx_gap_repeat = arg->tx_gap_multiplier; + break; + case TX_STATE_GAPSTART: + arg->tx_toggle_count = arg->tx_gap_count; + if (arg->tx_gap_repeat == 0) { + arg->tx_state = TX_STATE_GAPEND; + } else { + arg->tx_gap_repeat--; + } + break; + case TX_STATE_GAPEND: + arg->tx_repeat++; + if (arg->tx_repeat >= arg->tx_repeats) { + // disable timer nterrupt + arg->lw_timer_stop(); + arg->tx_msg_active = false; + arg->tx_state = TX_STATE_IDLE; + } else { + arg->tx_state = TX_STATE_MSGSTART; + } + break; + } + } +} + +/** + Check for send free +**/ +bool LwTx::lwtx_free() { return !this->tx_msg_active; } + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_send(const std::vector &msg) { + if (this->tx_translate) { + for (uint8_t i = 0; i < TX_MSGLEN; i++) { + this->tx_buf[i] = TX_NIBBLE[msg[i] & 0xF]; + ESP_LOGD("lightwaverf.sensor", "%x ", msg[i]); + } + } else { + // memcpy(tx_buf, msg, tx_msglen); + } + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set 5 char address for future messages +**/ +void LwTx::lwtx_setaddr(const uint8_t *addr) { + for (uint8_t i = 0; i < 5; i++) { + this->tx_buf[i + 4] = TX_NIBBLE[addr[i] & 0xF]; + } +} + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device) { + // enable timer 2 interrupts + this->tx_buf[0] = TX_NIBBLE[parameter >> 4]; + this->tx_buf[1] = TX_NIBBLE[parameter & 0xF]; + this->tx_buf[2] = TX_NIBBLE[device & 0xF]; + this->tx_buf[3] = TX_NIBBLE[command & 0xF]; + this->tx_buf[9] = TX_NIBBLE[room & 0xF]; + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set things up to transmit LightWaveRF 434Mhz messages +**/ +void LwTx::lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec) { + pin->setup(); + tx_pin = pin; + + tx_pin->pin_mode(gpio::FLAG_OUTPUT); + tx_pin->digital_write(txoff); + + if (repeats > 0 && repeats < 40) { + this->tx_repeats = repeats; + } + if (inverted) { + this->txon = 0; + this->txoff = 1; + } else { + this->txon = 1; + this->txoff = 0; + } + + int period1 = 330; + /* + if (period > 32 && period < 1000) { + period1 = period; + } else { + // default 330 uSec + period1 = 330; + }*/ + this->espPeriod = 5 * period1; + timer1_isr_init(); +} + +void LwTx::lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count) { + this->tx_low_count = low_count; + this->tx_high_count = high_count; + this->tx_trail_count = trail_count; + this->tx_gap_count = gap_count; +} + +void LwTx::lwtx_set_gap_multiplier(uint8_t gap_multiplier) { this->tx_gap_multiplier = gap_multiplier; } + +void LwTx::lw_timer_start() { + { + InterruptLock lock; + static LwTx *arg = this; // NOLINT + timer1_attachInterrupt([] { isr_t_xtimer(arg); }); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); + timer1_write(this->espPeriod); + } +} + +void LwTx::lw_timer_stop() { + { + InterruptLock lock; + timer1_disable(); + timer1_detachInterrupt(); + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwTx.h b/esphome/components/lightwaverf/LwTx.h new file mode 100644 index 000000000000..fe7b942a3aa9 --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.h @@ -0,0 +1,94 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +#include + +namespace esphome { +namespace lightwaverf { + +// LxTx.h +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +// Include basic library header and set default TX pin +static const uint8_t TX_PIN_DEFAULT = 13; + +class LwTx { + public: + // Sets up basic parameters must be called at least once + void lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec); + + // Allows changing basic tick counts from their defaults + void lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count); + + // Allws multiplying the gap period for creating very large gaps + void lwtx_set_gap_multiplier(uint8_t gap_multiplier); + + // determines whether incoming data or should be translated from nibble data + void lwtx_settranslate(bool txtranslate); + + // Checks whether tx is free to accept a new message + bool lwtx_free(); + + // Basic send of new 10 char message, not normally needed if setaddr and cmd are used. + void lwtx_send(const std::vector &msg); + + // Sets up 5 char address which will be used to form messages for lwtx_cmd + void lwtx_setaddr(const uint8_t *addr); + + // Send Command + void lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device); + + // Allows changing basic tick counts from their defaults + void lw_timer_start(); + + // Allws multiplying the gap period for creating very large gaps + void lw_timer_stop(); + + // These set the pulse durationlws in ticks. ESP uses 330uSec base tick, else use 140uSec + uint8_t tx_low_count = 3; // total number of ticks in a low (990 uSec) + uint8_t tx_high_count = 2; // total number of ticks in a high (660 uSec) + uint8_t tx_trail_count = 1; // tick count to set line low (330 uSec) + + uint8_t tx_toggle_count = 3; + + static const uint8_t TX_MSGLEN = 10; // the expected length of the message + + // Transmit mode constants and variables + uint8_t tx_repeats = 12; // Number of repeats of message sent + uint8_t txon = 1; + uint8_t txoff = 0; + bool tx_msg_active = false; // set true to activate message sending + bool tx_translate = true; // Set false to send raw data + + uint8_t tx_buf[TX_MSGLEN]; // the message buffer during reception + uint8_t tx_repeat = 0; // counter for repeats + uint8_t tx_state = 0; + uint16_t tx_gap_repeat = 0; // unsigned int + + // Use with low repeat counts + uint8_t tx_gap_count = 33; // Inter-message gap count (10.9 msec) + uint32_t espPeriod = 0; // Holds interrupt timer0 period + uint32_t espNext = 0; // Holds interrupt next count + + // Gap multiplier byte is used to multiply gap if longer periods are needed for experimentation + // If gap is 255 (35msec) then this to give a max of 9 seconds + // Used with low repeat counts to find if device times out + uint8_t tx_gap_multiplier = 0; // Gap extension byte + + uint8_t tx_bit_mask = 0; // bit mask in current byte + uint8_t tx_num_bytes = 0; // number of bytes sent + + InternalGPIOPin *tx_pin; + + protected: + uint32_t duty_on_; + uint32_t duty_off_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/__init__.py b/esphome/components/lightwaverf/__init__.py new file mode 100644 index 000000000000..4e96dda663c8 --- /dev/null +++ b/esphome/components/lightwaverf/__init__.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome import automation + +from esphome.const import ( + CONF_READ_PIN, + CONF_ID, + CONF_NAME, + CONF_WRITE_PIN, + CONF_REPEAT, + CONF_INVERTED, + CONF_PULSE_LENGTH, + CONF_CODE, +) +from esphome.cpp_helpers import gpio_pin_expression + +CODEOWNERS = ["@max246"] + +lightwaverf_ns = cg.esphome_ns.namespace("lightwaverf") + + +LIGHTWAVERFComponent = lightwaverf_ns.class_( + "LightWaveRF", cg.Component, cg.PollingComponent +) +LightwaveRawAction = lightwaverf_ns.class_("SendRawAction", automation.Action) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LIGHTWAVERFComponent), + cv.Optional(CONF_READ_PIN, default=13): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_WRITE_PIN, default=14): pins.internal_gpio_input_pin_schema, + } +).extend(cv.polling_component_schema("1s")) + + +LIGHTWAVE_SEND_SCHEMA = cv.Any( + cv.int_range(min=1), + cv.Schema( + { + cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent), + cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t)], + cv.Length(min=10), + ), + cv.Optional(CONF_REPEAT, default=10): cv.int_, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_, + } + ), +) + + +@automation.register_action( + "lightwaverf.send_raw", + LightwaveRawAction, + LIGHTWAVE_SEND_SCHEMA, +) +async def send_raw_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) + + repeats = await cg.templatable(config[CONF_REPEAT], args, int) + inverted = await cg.templatable(config[CONF_INVERTED], args, bool) + pulse_length = await cg.templatable(config[CONF_PULSE_LENGTH], args, int) + code = config[CONF_CODE] + + cg.add(var.set_repeats(repeats)) + cg.add(var.set_inverted(inverted)) + cg.add(var.set_pulse_length(pulse_length)) + cg.add(var.set_data(code)) + return var + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + pin_read = await gpio_pin_expression(config[CONF_READ_PIN]) + pin_write = await gpio_pin_expression(config[CONF_WRITE_PIN]) + cg.add(var.set_pin(pin_write, pin_read)) diff --git a/esphome/components/lightwaverf/lightwaverf.cpp b/esphome/components/lightwaverf/lightwaverf.cpp new file mode 100644 index 000000000000..89cbdae6e1dd --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.cpp @@ -0,0 +1,67 @@ +#include "esphome/core/log.h" + +#ifdef USE_ESP8266 + +#include "lightwaverf.h" + +namespace esphome { +namespace lightwaverf { + +static const char *const TAG = "lightwaverf.sensor"; + +static const uint8_t DEFAULT_REPEAT = 10; +static const bool DEFAULT_INVERT = false; +static const uint32_t DEFAULT_TICK = 330; + +void LightWaveRF::setup() { + ESP_LOGCONFIG(TAG, "Setting up Lightwave RF..."); + + this->lwtx_.lwtx_setup(pin_tx_, DEFAULT_REPEAT, DEFAULT_INVERT, DEFAULT_TICK); + this->lwrx_.lwrx_setup(pin_rx_); +} + +void LightWaveRF::update() { this->read_tx(); } + +void LightWaveRF::read_tx() { + if (this->lwrx_.lwrx_message()) { + this->lwrx_.lwrx_getmessage(msg_, msglen_); + print_msg_(msg_, msglen_); + } +} + +void LightWaveRF::send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec) { + this->lwtx_.lwtx_setup(pin_tx_, repeats, inverted, u_sec); + + uint32_t timeout = 0; + if (this->lwtx_.lwtx_free()) { + this->lwtx_.lwtx_send(msg); + timeout = millis(); + ESP_LOGD(TAG, "[%i] msg start", timeout); + } + while (!this->lwtx_.lwtx_free() && millis() < (timeout + 1000)) { + delay(10); + } + timeout = millis() - timeout; + ESP_LOGD(TAG, "[%u] msg sent: %i", millis(), timeout); +} + +void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) { + char buffer[65]; + ESP_LOGD(TAG, " Received code (len:%i): ", len); + + for (int i = 0; i < len; i++) { + sprintf(&buffer[i * 6], "0x%02x, ", msg[i]); + } + ESP_LOGD(TAG, "[%s]", buffer); +} + +void LightWaveRF::dump_config() { + ESP_LOGCONFIG(TAG, "Lightwave RF:"); + LOG_PIN(" Pin TX: ", this->pin_tx_); + LOG_PIN(" Pin RX: ", this->pin_rx_); + LOG_UPDATE_INTERVAL(this); +} +} // namespace lightwaverf +} // namespace esphome + +#endif diff --git a/esphome/components/lightwaverf/lightwaverf.h b/esphome/components/lightwaverf/lightwaverf.h new file mode 100644 index 000000000000..b9f2abfcb3c7 --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.h @@ -0,0 +1,70 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" + +#include + +#include "LwRx.h" +#include "LwTx.h" + +namespace esphome { +namespace lightwaverf { + +#ifdef USE_ESP8266 + +class LightWaveRF : public PollingComponent { + public: + void set_pin(InternalGPIOPin *pin_tx, InternalGPIOPin *pin_rx) { + pin_tx_ = pin_tx; + pin_rx_ = pin_rx; + } + void update() override; + void setup() override; + void dump_config() override; + void read_tx(); + void send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec); + + protected: + void print_msg_(uint8_t *msg, uint8_t len); + uint8_t msg_[10]; + uint8_t msglen_ = 10; + InternalGPIOPin *pin_tx_; + InternalGPIOPin *pin_rx_; + LwRx lwrx_; + LwTx lwtx_; +}; + +template class SendRawAction : public Action { + public: + SendRawAction(LightWaveRF *parent) : parent_(parent){}; + TEMPLATABLE_VALUE(int, repeat); + TEMPLATABLE_VALUE(int, inverted); + TEMPLATABLE_VALUE(int, pulse_length); + TEMPLATABLE_VALUE(std::vector, code); + + void set_repeats(const int &data) { repeat_ = data; } + void set_inverted(const int &data) { inverted_ = data; } + void set_pulse_length(const int &data) { pulse_length_ = data; } + void set_data(const std::vector &data) { code_ = data; } + + void play(Ts... x) { + int repeats = this->repeat_.value(x...); + int inverted = this->inverted_.value(x...); + int pulse_length = this->pulse_length_.value(x...); + std::vector msg = this->code_.value(x...); + + this->parent_->send_rx(msg, repeats, inverted, pulse_length); + } + + protected: + LightWaveRF *parent_; +}; + +#endif +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lilygo_t5_47/touchscreen/__init__.py b/esphome/components/lilygo_t5_47/touchscreen/__init__.py index fe9412064489..17f726278504 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/__init__.py +++ b/esphome/components/lilygo_t5_47/touchscreen/__init__.py @@ -13,13 +13,12 @@ LilygoT547Touchscreen = lilygo_t5_47_ns.class_( "LilygoT547Touchscreen", touchscreen.Touchscreen, - cg.Component, i2c.I2CDevice, ) CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id" -CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( +CONFIG_SCHEMA = touchscreen.touchscreen_schema("250ms").extend( cv.Schema( { cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen), @@ -27,17 +26,14 @@ pins.internal_gpio_input_pin_schema ), } - ) - .extend(i2c.i2c_device_schema(0x5A)) - .extend(cv.COMPONENT_SCHEMA) + ).extend(i2c.i2c_device_schema(0x5A)) ) 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 touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) cg.add(var.set_interrupt_pin(interrupt_pin)) diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp index b89cf2a7248e..58f2a4281205 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -23,15 +23,12 @@ static const uint8_t READ_TOUCH[1] = {0x07}; return; \ } -void Store::gpio_intr(Store *store) { store->touch = true; } - void LilygoT547Touchscreen::setup() { ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen..."); this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->interrupt_pin_->setup(); - this->store_.pin = this->interrupt_pin_->to_isr(); - this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); if (this->write(nullptr, 0) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Failed to communicate!"); @@ -41,19 +38,19 @@ void LilygoT547Touchscreen::setup() { } this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); -} - -void LilygoT547Touchscreen::loop() { - if (!this->store_.touch) { - for (auto *listener : this->touch_listeners_) - listener->release(); - return; + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->x_raw_max_ = this->display_->get_native_height(); + } } - this->store_.touch = false; +} +void LilygoT547Touchscreen::update_touches() { uint8_t point = 0; uint8_t buffer[40] = {0}; - uint32_t sum_l = 0, sum_h = 0; i2c::ErrorCode err; err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1); @@ -69,102 +66,30 @@ void LilygoT547Touchscreen::loop() { point = buffer[5] & 0xF; - if (point == 0) { - for (auto *listener : this->touch_listeners_) - listener->release(); - return; - } else if (point == 1) { + if (point == 1) { err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); ERROR_CHECK(err); err = this->read(&buffer[5], 2); ERROR_CHECK(err); - sum_l = buffer[5] << 8 | buffer[6]; } else if (point > 1) { err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); ERROR_CHECK(err); err = this->read(&buffer[5], 5 * (point - 1) + 3); ERROR_CHECK(err); - - sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2]; } this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); - for (int i = 0; i < 5 * point; i++) - sum_h += buffer[i]; - - if (sum_l != sum_h) - point = 0; - - if (point) { - uint8_t offset; - for (int i = 0; i < point; i++) { - if (i == 0) { - offset = 0; - } else { - offset = 4; - } - - TouchPoint tp; - - tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F; - tp.state = buffer[i * 5 + offset] & 0x0F; - if (tp.state == 0x06) - tp.state = 0x07; - - uint16_t y = (uint16_t) ((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); - uint16_t x = (uint16_t) ((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); - - switch (this->rotation_) { - case ROTATE_0_DEGREES: - tp.y = this->display_height_ - y; - tp.x = x; - break; - case ROTATE_90_DEGREES: - tp.x = this->display_height_ - y; - tp.y = this->display_width_ - x; - break; - case ROTATE_180_DEGREES: - tp.y = y; - tp.x = this->display_width_ - x; - break; - case ROTATE_270_DEGREES: - tp.x = y; - tp.y = x; - break; - } - - this->defer([this, tp]() { this->send_touch_(tp); }); - } - } else { - TouchPoint tp; - tp.id = (buffer[0] >> 4) & 0x0F; - tp.state = 0x06; - - uint16_t y = (uint16_t) ((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); - uint16_t x = (uint16_t) ((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); - - switch (this->rotation_) { - case ROTATE_0_DEGREES: - tp.y = this->display_height_ - y; - tp.x = x; - break; - case ROTATE_90_DEGREES: - tp.x = this->display_height_ - y; - tp.y = this->display_width_ - x; - break; - case ROTATE_180_DEGREES: - tp.y = y; - tp.x = this->display_width_ - x; - break; - case ROTATE_270_DEGREES: - tp.x = y; - tp.y = x; - break; - } + if (point == 0) + point = 1; - this->defer([this, tp]() { this->send_touch_(tp); }); + uint16_t id, x_raw, y_raw; + for (uint8_t i = 0; i < point; i++) { + id = (buffer[i * 5] >> 4) & 0x0F; + y_raw = (uint16_t) ((buffer[i * 5 + 1] << 4) | ((buffer[i * 5 + 3] >> 4) & 0x0F)); + x_raw = (uint16_t) ((buffer[i * 5 + 2] << 4) | (buffer[i * 5 + 3] & 0x0F)); + this->add_raw_touch_position_(id, x_raw, y_raw); } this->status_clear_warning(); diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h index 3d00e0b11737..6767bf0a71c8 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h @@ -6,29 +6,25 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include + namespace esphome { namespace lilygo_t5_47 { -struct Store { - volatile bool touch; - ISRInternalGPIOPin pin; - - static void gpio_intr(Store *store); -}; - using namespace touchscreen; -class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { +class LilygoT547Touchscreen : public Touchscreen, public i2c::I2CDevice { public: void setup() override; - void loop() override; + void dump_config() override; void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } protected: + void update_touches() override; + InternalGPIOPin *interrupt_pin_; - Store store_; }; } // namespace lilygo_t5_47 diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py index f659c48a6e9f..457ffa278a4f 100644 --- a/esphome/components/lock/__init__.py +++ b/esphome/components/lock/__init__.py @@ -57,8 +57,8 @@ async def setup_lock_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if mqtt_id := config.get(CONF_MQTT_ID): + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 5225a227ec2c..7ca6ad8958ee 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -32,6 +32,12 @@ VARIANT_ESP32S3, VARIANT_ESP32C2, VARIANT_ESP32C6, + VARIANT_ESP32H2, +) +from esphome.components.libretiny import get_libretiny_component, get_libretiny_family +from esphome.components.libretiny.const import ( + COMPONENT_BK72XX, + COMPONENT_RTL87XX, ) from esphome.components.libretiny import get_libretiny_component, get_libretiny_family from esphome.components.libretiny.const import ( @@ -83,9 +89,10 @@ 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_SERIAL_JTAG], + VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C2: [UART0, UART1], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], + VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], } UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] @@ -95,7 +102,7 @@ COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2], } -ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] +ESP_ARDUINO_UNSUPPORTED_USB_UARTS = [USB_SERIAL_JTAG] UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1] @@ -122,9 +129,13 @@ def uart_selection(value): if CORE.is_esp32: - if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf: - raise cv.Invalid(f"Only esp-idf framework supports {value}.") + if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS: + raise cv.Invalid(f"Arduino framework does not support {value}.") variant = get_esp32_variant() + if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC: + raise cv.Invalid( + f"{value} is not supported for variant {variant} when using ESP-IDF." + ) if variant in UART_SELECTION_ESP32: return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: @@ -138,6 +149,8 @@ def uart_selection(value): component = get_libretiny_component() if component in UART_SELECTION_LIBRETINY: return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value) + if CORE.is_host: + raise cv.Invalid("Uart selection not valid for host platform") raise NotImplementedError @@ -169,6 +182,11 @@ def validate_local_no_higher_than_global(value): CONF_HARDWARE_UART, esp8266=UART0, esp32=UART0, + esp32_s2=USB_CDC, + esp32_s3_arduino=USB_CDC, + esp32_s3_idf=USB_SERIAL_JTAG, + esp32_c3_arduino=USB_CDC, + esp32_c3_idf=USB_SERIAL_JTAG, rp2040=USB_CDC, bk72xx=DEFAULT, rtl87xx=DEFAULT, @@ -256,11 +274,27 @@ async def to_code(config): if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") + if CORE.using_arduino: + if config[CONF_HARDWARE_UART] == USB_CDC: + cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") + if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3: + cg.add_build_flag("-DARDUINO_USB_MODE=1") + if CORE.using_esp_idf: if config[CONF_HARDWARE_UART] == USB_CDC: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) + try: + uart_selection(USB_SERIAL_JTAG) + cg.add_define("USE_LOGGER_USB_SERIAL_JTAG") + except cv.Invalid: + pass + try: + uart_selection(USB_CDC) + cg.add_define("USE_LOGGER_USB_CDC") + except cv.Invalid: + pass # Register at end for safe mode await cg.register_component(log, config) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index b488a3d1dc06..dac08fbbcef2 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,16 +1,9 @@ #include "logger.h" #include -#ifdef USE_ESP_IDF -#include -#include "freertos/FreeRTOS.h" -#endif // USE_ESP_IDF - -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) -#include -#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace logger { @@ -46,7 +39,23 @@ void Logger::write_header_(int level, const char *tag, int line) { const char *color = LOG_LEVEL_COLORS[level]; const char *letter = LOG_LEVEL_LETTERS[level]; - this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); +#else + void *current_task = nullptr; +#endif + if (current_task == main_task_) { + this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); + } else { + const char *thread_name = ""; +#if defined(USE_ESP32) + thread_name = pcTaskGetName(current_task); +#elif defined(USE_LIBRETINY) + thread_name = pcTaskGetTaskName(current_task); +#endif + this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, + ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color); + } } void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT @@ -103,6 +112,7 @@ int HOT Logger::level_for(const char *tag) { } return ESPHOME_LOG_LEVEL; } + void HOT Logger::log_message_(int level, const char *tag, int offset) { // remove trailing newline if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') { @@ -112,28 +122,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; + if (this->baud_rate_ > 0) { -#ifdef USE_ARDUINO - this->hw_serial_->println(msg); -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF - if ( -#if defined(USE_ESP32_VARIANT_ESP32S2) - uart_ == UART_SELECTION_USB_CDC -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) - uart_ == UART_SELECTION_USB_SERIAL_JTAG -#elif defined(USE_ESP32_VARIANT_ESP32S3) - uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG -#else - /* DISABLES CODE */ (false) // NOLINT -#endif - ) { - puts(msg); - } else { - uart_write_bytes(uart_num_, msg, strlen(msg)); - uart_write_bytes(uart_num_, "\n", 1); - } -#endif + this->write_msg_(msg); } #ifdef USE_ESP32 @@ -145,9 +136,6 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { if (xPortGetFreeHeapSize() < 2048) return; #endif -#ifdef USE_HOST - puts(msg); -#endif this->log_callback_.call(level, tag, msg); } @@ -155,159 +143,28 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) { // add 1 to buffer size for null terminator this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + this->main_task_ = xTaskGetCurrentTaskHandle(); +#endif } -#ifndef USE_LIBRETINY -void Logger::pre_setup() { - if (this->baud_rate_ > 0) { +#ifdef USE_LOGGER_USB_CDC +void Logger::loop() { #ifdef USE_ARDUINO - switch (this->uart_) { - case UART_SELECTION_UART0: -#ifdef USE_ESP8266 - case UART_SELECTION_UART0_SWAP: -#endif -#ifdef USE_RP2040 - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); -#endif -#ifdef USE_ESP8266 - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - Serial.swap(); - } - Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); -#endif - break; - case UART_SELECTION_UART1: -#ifdef USE_RP2040 - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); -#else - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); -#endif -#ifdef USE_ESP8266 - Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); -#endif - break; -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_UART2: - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); - break; -#endif -#ifdef USE_RP2040 - case UART_SELECTION_USB_CDC: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - break; -#endif - } -#endif // USE_ARDUINO -#ifdef USE_ESP_IDF - uart_num_ = UART_NUM_0; - switch (uart_) { - case UART_SELECTION_UART0: - uart_num_ = UART_NUM_0; - break; - case UART_SELECTION_UART1: - uart_num_ = UART_NUM_1; - break; -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_UART2: - uart_num_ = UART_NUM_2; - break; -#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_USB_CDC: - uart_num_ = -1; - break; -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) - case UART_SELECTION_USB_SERIAL_JTAG: - uart_num_ = -1; - break; -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 - } - if (uart_num_ >= 0) { - uart_config_t uart_config{}; - uart_config.baud_rate = (int) baud_rate_; - uart_config.data_bits = UART_DATA_8_BITS; - uart_config.parity = UART_PARITY_DISABLE; - uart_config.stop_bits = UART_STOP_BITS_1; - uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; - uart_param_config(uart_num_, &uart_config); - const int uart_buffer_size = tx_buffer_size_; - // Install UART driver using an event queue here - uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); - } -#endif // USE_ESP_IDF + if (this->uart_ != UART_SELECTION_USB_CDC) { + return; } -#ifdef USE_ESP8266 - else { - uart_set_debug(UART_NO); + static bool opened = false; + if (opened == Serial) { + return; } -#endif // USE_ESP8266 - - global_logger = this; -#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) - esp_log_set_vprintf(esp_idf_log_vprintf_); - if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { - esp_log_level_set("*", ESP_LOG_VERBOSE); + if (false == opened) { + App.schedule_dump_config(); } -#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO - - ESP_LOGI(TAG, "Log initialized"); -} -#else // USE_LIBRETINY -void Logger::pre_setup() { - if (this->baud_rate_ > 0) { - switch (this->uart_) { -#if LT_HW_UART0 - case UART_SELECTION_UART0: - this->hw_serial_ = &Serial0; - Serial0.begin(this->baud_rate_); - break; -#endif -#if LT_HW_UART1 - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - break; -#endif -#if LT_HW_UART2 - case UART_SELECTION_UART2: - this->hw_serial_ = &Serial2; - Serial2.begin(this->baud_rate_); - break; + opened = !opened; #endif - default: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - if (this->uart_ != UART_SELECTION_DEFAULT) { - ESP_LOGW(TAG, " The chosen logger UART port is not available on this board." - "The default port was used instead."); - } - break; - } - - // change lt_log() port to match default Serial - if (this->uart_ == UART_SELECTION_DEFAULT) { - this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1); - lt_log_set_port(LT_UART_DEFAULT_SERIAL); - } else { - lt_log_set_port(this->uart_ - 1); - } - } - - global_logger = this; - ESP_LOGI(TAG, "Log initialized"); } -#endif // USE_LIBRETINY +#endif void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_log_level(const std::string &tag, int log_level) { @@ -323,38 +180,13 @@ void Logger::add_on_log_callback(std::functionbaud_rate_); -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) - ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); + ESP_LOGCONFIG(TAG, " Hardware UART: %s", get_uart_selection_()); #endif for (auto &it : this->log_levels_) { diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 4a7a43c7c26b..b55cfb0771e2 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -34,39 +34,31 @@ enum UARTSelection { #ifdef USE_LIBRETINY UART_SELECTION_DEFAULT = 0, UART_SELECTION_UART0, - UART_SELECTION_UART1, - UART_SELECTION_UART2, #else UART_SELECTION_UART0 = 0, +#endif UART_SELECTION_UART1, -#if defined(USE_ESP32) -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ - !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32) UART_SELECTION_UART2, -#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 -#ifdef USE_ESP_IDF -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#endif +#ifdef USE_LOGGER_USB_CDC UART_SELECTION_USB_CDC, -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG UART_SELECTION_USB_SERIAL_JTAG, -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 -#endif // USE_ESP_IDF -#endif // USE_ESP32 +#endif #ifdef USE_ESP8266 UART_SELECTION_UART0_SWAP, #endif // USE_ESP8266 -#ifdef USE_RP2040 - UART_SELECTION_USB_CDC, -#endif // USE_RP2040 -#endif // USE_LIBRETINY }; #endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY class Logger : public Component { public: explicit Logger(uint32_t baud_rate, size_t tx_buffer_size); - +#ifdef USE_LOGGER_USB_CDC + void loop() override; +#endif /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } @@ -107,6 +99,7 @@ class Logger : public Component { void write_header_(int level, const char *tag, int line); void write_footer_(); void log_message_(int level, const char *tag, int offset = 0); + void write_msg_(const char *msg); inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; } inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; } @@ -146,6 +139,10 @@ class Logger : public Component { va_end(arg); } +#ifndef USE_HOST + const char *get_uart_selection_(); +#endif + uint32_t baud_rate_; char *tx_buffer_{nullptr}; int tx_buffer_at_{0}; @@ -170,6 +167,7 @@ class Logger : public Component { CallbackManager log_callback_{}; /// Prevents recursive log calls, if true a log message is already being processed. bool recursion_guard_ = false; + void *main_task_ = nullptr; }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp new file mode 100644 index 000000000000..b0f1051d34cd --- /dev/null +++ b/esphome/components/logger/logger_esp32.cpp @@ -0,0 +1,199 @@ +#ifdef USE_ESP32 +#include "logger.h" + +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#include +#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF + +#ifdef USE_ESP_IDF +#include + +#ifdef USE_LOGGER_USB_SERIAL_JTAG +#include +#include +#include +#endif + +#include "freertos/FreeRTOS.h" +#include "esp_idf_version.h" + +#include +#include +#include + +#endif // USE_ESP_IDF + +#include "esphome/core/log.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +#ifdef USE_ESP_IDF + +#ifdef USE_LOGGER_USB_SERIAL_JTAG +static void init_usb_serial_jtag_() { + setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin + + // Minicom, screen, idf_monitor send CR when ENTER key is pressed + esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + // Move the caret to the beginning of the next line on '\n' + esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // Enable non-blocking mode on stdin and stdout + fcntl(fileno(stdout), F_SETFL, 0); + fcntl(fileno(stdin), F_SETFL, 0); + + usb_serial_jtag_driver_config_t usb_serial_jtag_config{}; + usb_serial_jtag_config.rx_buffer_size = 512; + usb_serial_jtag_config.tx_buffer_size = 512; + + esp_err_t ret = ESP_OK; + // Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes + ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config); + if (ret != ESP_OK) { + return; + } + + // Tell vfs to use usb-serial-jtag driver + esp_vfs_usb_serial_jtag_use_driver(); +} +#endif + +void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) { + uart_config_t uart_config{}; + uart_config.baud_rate = (int) baud_rate; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + uart_config.source_clk = UART_SCLK_DEFAULT; +#endif + uart_param_config(uart_num, &uart_config); + const int uart_buffer_size = tx_buffer_size; + // Install UART driver using an event queue here + uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); +} + +#endif // USE_ESP_IDF + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { +#ifdef USE_ARDUINO + switch (this->uart_) { + case UART_SELECTION_UART0: +#if ARDUINO_USB_CDC_ON_BOOT + this->hw_serial_ = &Serial0; + Serial0.begin(this->baud_rate_); +#else + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); +#endif + break; + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; +#ifdef USE_ESP32_VARIANT_ESP32 + case UART_SELECTION_UART2: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; +#endif + +#ifdef USE_LOGGER_USB_CDC + case UART_SELECTION_USB_CDC: + this->hw_serial_ = &Serial; +#if ARDUINO_USB_CDC_ON_BOOT + Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection +#endif + Serial.begin(this->baud_rate_); + break; +#endif + } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF + this->uart_num_ = UART_NUM_0; + switch (this->uart_) { + case UART_SELECTION_UART0: + this->uart_num_ = UART_NUM_0; + init_uart(this->uart_num_, baud_rate_, tx_buffer_size_); + break; + case UART_SELECTION_UART1: + this->uart_num_ = UART_NUM_1; + init_uart(this->uart_num_, baud_rate_, tx_buffer_size_); + break; +#ifdef USE_ESP32_VARIANT_ESP32 + case UART_SELECTION_UART2: + this->uart_num_ = UART_NUM_2; + init_uart(this->uart_num_, baud_rate_, tx_buffer_size_); + break; +#endif +#ifdef USE_LOGGER_USB_CDC + case UART_SELECTION_USB_CDC: + break; +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG + case UART_SELECTION_USB_SERIAL_JTAG: + init_usb_serial_jtag_(); + break; +#endif + } +#endif // USE_ESP_IDF + } + + global_logger = this; +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) + esp_log_set_vprintf(esp_idf_log_vprintf_); + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { + esp_log_level_set("*", ESP_LOG_VERBOSE); + } +#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO + + ESP_LOGI(TAG, "Log initialized"); +} + +#ifdef USE_ESP_IDF +void HOT Logger::write_msg_(const char *msg) { + if ( +#if defined(USE_ESP32_VARIANT_ESP32S2) + this->uart_ == UART_SELECTION_USB_CDC +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) + this->uart_ == UART_SELECTION_USB_SERIAL_JTAG +#elif defined(USE_ESP32_VARIANT_ESP32S3) + this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG +#else + /* DISABLES CODE */ (false) // NOLINT +#endif + ) { + puts(msg); + } else { + uart_write_bytes(this->uart_num_, msg, strlen(msg)); + uart_write_bytes(this->uart_num_, "\n", 1); + } +} +#else +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } +#endif + +const char *const UART_SELECTIONS[] = { + "UART0", "UART1", +#ifdef USE_ESP32_VARIANT_ESP32 + "UART2", +#endif +#ifdef USE_LOGGER_USB_CDC + "USB_CDC", +#endif +#ifdef USE_LOGGER_USB_SERIAL_JTAG + "USB_SERIAL_JTAG", +#endif +}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp new file mode 100644 index 000000000000..5bfeb21007f4 --- /dev/null +++ b/esphome/components/logger/logger_esp8266.cpp @@ -0,0 +1,45 @@ +#ifdef USE_ESP8266 +#include "logger.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace 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); + } + + global_logger = this; + + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp new file mode 100644 index 000000000000..edffb1a8c3bf --- /dev/null +++ b/esphome/components/logger/logger_host.cpp @@ -0,0 +1,24 @@ +#if defined(USE_HOST) +#include "logger.h" + +namespace esphome { +namespace logger { + +void HOT Logger::write_msg_(const char *msg) { + time_t rawtime; + struct tm *timeinfo; + char buffer[80]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); + fputs(buffer, stdout); + puts(msg); +} + +void Logger::pre_setup() { global_logger = this; } + +} // namespace logger +} // namespace esphome + +#endif diff --git a/esphome/components/logger/logger_libretiny.cpp b/esphome/components/logger/logger_libretiny.cpp new file mode 100644 index 000000000000..12e55b7cef35 --- /dev/null +++ b/esphome/components/logger/logger_libretiny.cpp @@ -0,0 +1,62 @@ +#ifdef USE_LIBRETINY +#include "logger.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { +#if LT_HW_UART0 + case UART_SELECTION_UART0: + this->hw_serial_ = &Serial0; + Serial0.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART1 + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART2 + case UART_SELECTION_UART2: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; +#endif + default: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ != UART_SELECTION_DEFAULT) { + ESP_LOGW(TAG, " The chosen logger UART port is not available on this board." + "The default port was used instead."); + } + break; + } + + // change lt_log() port to match default Serial + if (this->uart_ == UART_SELECTION_DEFAULT) { + this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1); + lt_log_set_port(LT_UART_DEFAULT_SERIAL); + } else { + lt_log_set_port(this->uart_ - 1); + } + } + + global_logger = this; + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp new file mode 100644 index 000000000000..2783d77ac128 --- /dev/null +++ b/esphome/components/logger/logger_rp2040.cpp @@ -0,0 +1,39 @@ +#ifdef USE_RP2040 +#include "logger.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace logger { + +static const char *const TAG = "logger"; + +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { + case UART_SELECTION_UART0: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; + case UART_SELECTION_USB_CDC: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + break; + } + } + global_logger = this; + ESP_LOGI(TAG, "Log initialized"); +} + +void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } + +const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; + +const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } + +} // namespace logger +} // namespace esphome +#endif // USE_RP2040 diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index 959af6823546..4eb1ff2c4683 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -8,10 +8,26 @@ namespace ltr390 { static const char *const TAG = "ltr390"; +static const uint8_t LTR390_WAKEUP_TIME = 10; +static const uint8_t LTR390_SETTLE_TIME = 5; + +static const uint8_t LTR390_MAIN_CTRL = 0x00; +static const uint8_t LTR390_MEAS_RATE = 0x04; +static const uint8_t LTR390_GAIN = 0x05; +static const uint8_t LTR390_PART_ID = 0x06; +static const uint8_t LTR390_MAIN_STATUS = 0x07; + static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; + +// Request fastest measurement rate - will be slowed by device if conversion rate is slower. +static const float RESOLUTION_SETTING[6] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50}; static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; +static const float SENSITIVITY_MAX = 2300; +static const float INTG_MAX = RESOLUTIONVALUE[0] * 100; +static const int GAIN_MAX = GAINVALUES[4]; + uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { uint32_t value = 0; @@ -58,7 +74,7 @@ void LTR390Component::read_als_() { uint32_t als = *val; if (this->light_sensor_ != nullptr) { - float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_; + float lux = ((0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_])) * this->wfac_; this->light_sensor_->publish_state(lux); } @@ -74,7 +90,7 @@ void LTR390Component::read_uvs_() { uint32_t uv = *val; if (this->uvi_sensor_ != nullptr) { - this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_); + this->uvi_sensor_->publish_state((uv / this->sensitivity_) * this->wfac_); } if (this->uv_sensor_ != nullptr) { @@ -88,21 +104,27 @@ void LTR390Component::read_mode_(int mode_index) { std::bitset<8> ctrl = this->reg(LTR390_MAIN_CTRL).get(); ctrl[LTR390_CTRL_MODE] = mode; + ctrl[LTR390_CTRL_EN] = true; this->reg(LTR390_MAIN_CTRL) = ctrl.to_ulong(); // After the sensor integration time do the following - this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100, [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); - } else { - this->reading_ = false; - } - }); + this->set_timeout(((uint32_t) RESOLUTIONVALUE[this->res_]) * 100 + 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); + } 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; + } + }); } void LTR390Component::setup() { @@ -132,12 +154,13 @@ void LTR390Component::setup() { // Set gain this->reg(LTR390_GAIN) = gain_; - // Set resolution - uint8_t res = this->reg(LTR390_MEAS_RATE).get(); - // resolution is in bits 5-7 - res &= ~0b01110000; - res |= res << 4; - this->reg(LTR390_MEAS_RATE) = res; + // Set resolution and measurement rate + this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_]; + + // Set sensitivity by linearly scaling against known value in the datasheet + float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX; + float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX; + this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale; // Set sensor read state this->reading_ = false; diff --git a/esphome/components/ltr390/ltr390.h b/esphome/components/ltr390/ltr390.h index 1bb7a8fa2296..bc98518fe98c 100644 --- a/esphome/components/ltr390/ltr390.h +++ b/esphome/components/ltr390/ltr390.h @@ -17,14 +17,6 @@ enum LTR390CTRL { }; // enums from https://github.com/adafruit/Adafruit_LTR390/ - -static const uint8_t LTR390_MAIN_CTRL = 0x00; -static const uint8_t LTR390_MEAS_RATE = 0x04; -static const uint8_t LTR390_GAIN = 0x05; -static const uint8_t LTR390_PART_ID = 0x06; -static const uint8_t LTR390_MAIN_STATUS = 0x07; -static const float LTR390_SENSITIVITY = 2300.0; - // Sensing modes enum LTR390MODE { LTR390_MODE_ALS, @@ -81,6 +73,7 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice { LTR390GAIN gain_; LTR390RESOLUTION res_; + float sensitivity_; float wfac_; sensor::Sensor *light_sensor_{nullptr}; diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index 0a765dbe3d4d..fe8cad00b675 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -8,6 +8,7 @@ CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5, + DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, ) @@ -61,22 +62,22 @@ unit_of_measurement=UNIT_COUNTS, icon=ICON_BRIGHTNESS_5, accuracy_decimals=1, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( unit_of_measurement=UNIT_UVI, icon=ICON_BRIGHTNESS_5, accuracy_decimals=5, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), cv.Optional(CONF_UV): sensor.sensor_schema( unit_of_measurement=UNIT_COUNTS, icon=ICON_BRIGHTNESS_5, accuracy_decimals=1, - device_class=DEVICE_CLASS_ILLUMINANCE, + device_class=DEVICE_CLASS_EMPTY, ), - cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), - cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), + cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS), + cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS), cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( min=1.0 ), diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 1c549007b9e8..5250a4573229 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -21,6 +21,7 @@ CONF_KEYS = "keys" CONF_DEBOUNCE_TIME = "debounce_time" CONF_HAS_DIODES = "has_diodes" +CONF_HAS_PULLDOWNS = "has_pulldowns" def check_keys(obj): @@ -45,6 +46,7 @@ def check_keys(obj): cv.Optional(CONF_KEYS): cv.string, cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100), cv.Optional(CONF_HAS_DIODES): cv.boolean, + cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean, } ), check_keys, @@ -69,3 +71,5 @@ async def to_code(config): cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME])) if CONF_HAS_DIODES in config: cg.add(var.set_has_diodes(config[CONF_HAS_DIODES])) + if CONF_HAS_PULLDOWNS in config: + cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS])) diff --git a/esphome/components/matrix_keypad/binary_sensor/__init__.py b/esphome/components/matrix_keypad/binary_sensor/__init__.py index 9ad909f60a9c..edebf7b772c3 100644 --- a/esphome/components/matrix_keypad/binary_sensor/__init__.py +++ b/esphome/components/matrix_keypad/binary_sensor/__init__.py @@ -1,12 +1,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_KEY +from esphome.const import CONF_ID, CONF_KEY, CONF_ROW, CONF_COL from .. import MatrixKeypad, matrix_keypad_ns, CONF_KEYPAD_ID -CONF_ROW = "row" -CONF_COL = "col" - DEPENDENCIES = ["matrix_keypad"] MatrixKeypadBinarySensor = matrix_keypad_ns.class_( diff --git a/esphome/components/matrix_keypad/matrix_keypad.cpp b/esphome/components/matrix_keypad/matrix_keypad.cpp index 4f8962a7821f..902e57484608 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.cpp +++ b/esphome/components/matrix_keypad/matrix_keypad.cpp @@ -11,11 +11,16 @@ void MatrixKeypad::setup() { if (!has_diodes_) { pin->pin_mode(gpio::FLAG_INPUT); } else { - pin->digital_write(true); + pin->digital_write(!has_pulldowns_); + } + } + for (auto *pin : this->columns_) { + if (has_pulldowns_) { + pin->pin_mode(gpio::FLAG_INPUT); + } else { + pin->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); } } - for (auto *pin : this->columns_) - pin->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); } void MatrixKeypad::loop() { @@ -28,9 +33,9 @@ void MatrixKeypad::loop() { for (auto *row : this->rows_) { if (!has_diodes_) row->pin_mode(gpio::FLAG_OUTPUT); - row->digital_write(false); + row->digital_write(has_pulldowns_); for (auto *col : this->columns_) { - if (!col->digital_read()) { + if (col->digital_read() == has_pulldowns_) { if (key != -1) { error = true; } else { @@ -39,7 +44,7 @@ void MatrixKeypad::loop() { } pos++; } - row->digital_write(true); + row->digital_write(!has_pulldowns_); if (!has_diodes_) row->pin_mode(gpio::FLAG_INPUT); } diff --git a/esphome/components/matrix_keypad/matrix_keypad.h b/esphome/components/matrix_keypad/matrix_keypad.h index 9f5942be9a4a..d506040b7cea 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.h +++ b/esphome/components/matrix_keypad/matrix_keypad.h @@ -28,6 +28,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { void set_keys(std::string keys) { keys_ = std::move(keys); }; void set_debounce_time(int debounce_time) { debounce_time_ = debounce_time; }; void set_has_diodes(int has_diodes) { has_diodes_ = has_diodes; }; + void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; }; void register_listener(MatrixKeypadListener *listener); @@ -37,6 +38,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { std::string keys_; int debounce_time_ = 0; bool has_diodes_{false}; + bool has_pulldowns_{false}; int pressed_key_ = -1; std::vector listeners_{}; diff --git a/esphome/components/max31855/max31855.cpp b/esphome/components/max31855/max31855.cpp index 2578c4742d74..445e086ef620 100644 --- a/esphome/components/max31855/max31855.cpp +++ b/esphome/components/max31855/max31855.cpp @@ -47,7 +47,7 @@ void MAX31855Sensor::read_data_() { if (mem != 0xFFFFFFFF) { this->status_clear_error(); } else { - ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem); + ESP_LOGE(TAG, "No data received from MAX31855 (0x%08" PRIX32 "). Check wiring!", mem); this->publish_state(NAN); if (this->temperature_reference_) { this->temperature_reference_->publish_state(NAN); @@ -69,25 +69,25 @@ void MAX31855Sensor::read_data_() { // Check thermocouple faults if (mem & 0x00000001) { - ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08X)", mem); + ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08" PRIX32 ")", mem); this->publish_state(NAN); this->status_set_warning(); return; } if (mem & 0x00000002) { - ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08X)", mem); + ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08" PRIX32 ")", mem); this->publish_state(NAN); this->status_set_warning(); return; } if (mem & 0x00000004) { - ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08X)", mem); + ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08" PRIX32 ")", mem); this->publish_state(NAN); this->status_set_warning(); return; } if (mem & 0x00010000) { - ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08X)", mem); + ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08" PRIX32 ")", mem); this->publish_state(NAN); this->status_set_warning(); return; diff --git a/esphome/components/max31855/max31855.h b/esphome/components/max31855/max31855.h index c0ed8a467d9f..822e256587de 100644 --- a/esphome/components/max31855/max31855.h +++ b/esphome/components/max31855/max31855.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" +#include + namespace esphome { namespace max31855 { diff --git a/esphome/components/max31856/max31856.cpp b/esphome/components/max31856/max31856.cpp index 9300916fdca5..8ae4be66574d 100644 --- a/esphome/components/max31856/max31856.cpp +++ b/esphome/components/max31856/max31856.cpp @@ -188,7 +188,7 @@ uint32_t MAX31856Sensor::read_register24_(uint8_t reg) { ESP_LOGVV(TAG, "read_byte lsb=0x%02X", lsb); this->disable(); const uint32_t value((msb << 16) | (mid << 8) | lsb); - ESP_LOGV(TAG, "read_register_24_ reg=0x%02X: value=0x%06X", reg, value); + ESP_LOGV(TAG, "read_register_24_ reg=0x%02X: value=0x%06" PRIX32, reg, value); return value; } diff --git a/esphome/components/max31856/max31856.h b/esphome/components/max31856/max31856.h index 157aad433c1a..4deb6bc85585 100644 --- a/esphome/components/max31856/max31856.h +++ b/esphome/components/max31856/max31856.h @@ -4,6 +4,8 @@ #include "esphome/components/spi/spi.h" #include "esphome/core/component.h" +#include + namespace esphome { namespace max31856 { diff --git a/esphome/components/max6675/max6675.h b/esphome/components/max6675/max6675.h index 09bd9df3b83a..ab0f06b041b7 100644 --- a/esphome/components/max6675/max6675.h +++ b/esphome/components/max6675/max6675.h @@ -10,7 +10,7 @@ namespace max6675 { class MAX6675Sensor : public sensor::Sensor, public PollingComponent, public spi::SPIDevice { + spi::DATA_RATE_1MHZ> { public: void setup() override; void dump_config() override; diff --git a/esphome/components/max6956/__init__.py b/esphome/components/max6956/__init__.py index 77e0d37e764f..bb71dba8bf16 100644 --- a/esphome/components/max6956/__init__.py +++ b/esphome/components/max6956/__init__.py @@ -74,20 +74,14 @@ def validate_mode(value): CONF_MAX6956 = "max6956" -MAX6956_PIN_SCHEMA = cv.All( +MAX6956_PIN_SCHEMA = pins.gpio_base_schema( + MAX6956GPIOPin, + cv.int_range(min=4, max=31), + modes=[CONF_INPUT, CONF_PULLUP, CONF_OUTPUT], + mode_validator=validate_mode, +).extend( { - cv.GenerateID(): cv.declare_id(MAX6956GPIOPin), cv.Required(CONF_MAX6956): cv.use_id(MAX6956), - cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) diff --git a/esphome/components/max7219/display.py b/esphome/components/max7219/display.py index 391d033f2407..13807b0dbd13 100644 --- a/esphome/components/max7219/display.py +++ b/esphome/components/max7219/display.py @@ -29,7 +29,6 @@ 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) await display.register_display(var, config) diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 38b4a165cbb6..d3cf6f5c48c6 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -164,6 +164,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { this->disable(); } void MAX7219Component::update() { + if (this->intensity_changed_) { + this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_); + this->intensity_changed_ = false; + } for (uint8_t i = 0; i < this->num_chips_ * 8; i++) this->buffer_[i] = 0; if (this->writer_.has_value()) @@ -218,8 +222,11 @@ uint8_t MAX7219Component::printf(const char *format, ...) { } void MAX7219Component::set_writer(max7219_writer_t &&writer) { this->writer_ = writer; } void MAX7219Component::set_intensity(uint8_t intensity) { - this->intensity_ = intensity; - this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_); + intensity &= 0xF; + if (intensity != this->intensity_) { + this->intensity_changed_ = true; + this->intensity_ = intensity; + } } void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; } diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index 1b724cef692d..270edf3282fc 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -52,7 +52,8 @@ class MAX7219Component : public PollingComponent, void send_byte_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data); - uint8_t intensity_{15}; /// Intensity of the display from 0 to 15 (most) + uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most) + bool intensity_changed_{}; // True if we need to re-send the intensity uint8_t num_chips_{1}; uint8_t *buffer_; bool reverse_{false}; diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index 8db9123a399e..779e385ab1f0 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -39,7 +39,7 @@ max7219_ns = cg.esphome_ns.namespace("max7219digit") MAX7219Component = max7219_ns.class_( - "MAX7219Component", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer + "MAX7219Component", spi.SPIDevice, display.DisplayBuffer, cg.PollingComponent ) MAX7219ComponentRef = MAX7219Component.operator("ref") @@ -78,7 +78,6 @@ 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) await display.register_display(var, config) diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 93d2af21f9eb..ead803380350 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -25,8 +25,7 @@ class MAX7219Component; using max7219_writer_t = std::function; -class MAX7219Component : public PollingComponent, - public display::DisplayBuffer, +class MAX7219Component : public display::DisplayBuffer, public spi::SPIDevice { public: diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index c1209a96279e..55722e3ae078 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -45,19 +45,15 @@ def validate_mode(value): CONF_MCP23016 = "mcp23016" -MCP23016_PIN_SCHEMA = cv.All( +MCP23016_PIN_SCHEMA = pins.gpio_base_schema( + MCP23016GPIOPin, + cv.int_range(min=0, max=15), + modes=[CONF_INPUT, CONF_OUTPUT], + mode_validator=validate_mode, + invertable=True, +).extend( { - cv.GenerateID(): cv.declare_id(MCP23016GPIOPin), cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index 7bcd5c84fc66..1e41a8ddff69 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -54,20 +54,16 @@ def validate_mode(value): CONF_MCP23XXX = "mcp23xxx" -MCP23XXX_PIN_SCHEMA = cv.All( + +MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema( + MCP23XXXGPIOPin, + cv.int_range(min=0, max=15), + modes=[CONF_INPUT, CONF_OUTPUT, CONF_PULLUP], + mode_validator=validate_mode, + invertable=True, +).extend( { - cv.GenerateID(): cv.declare_id(MCP23XXXGPIOPin), cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( MCP23XXX_INTERRUPT_MODES, upper=True ), diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index 81abc4f0125e..aed48456b20f 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -1,4 +1,6 @@ #include "mcp3008.h" + +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -32,28 +34,10 @@ float MCP3008::read_data(uint8_t pin) { this->disable(); - int data = data_msb << 8 | data_lsb; + uint16_t data = encode_uint16(data_msb, data_lsb); return data / 1023.0f; } -MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) {} - -float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } - -void MCP3008Sensor::setup() { LOG_SENSOR("", "Setting up MCP3008 Sensor '%s'...", this); } -void MCP3008Sensor::dump_config() { - ESP_LOGCONFIG(TAG, "MCP3008Sensor:"); - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); - ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); -} -float MCP3008Sensor::sample() { - float value_v = this->parent_->read_data(pin_); - value_v = (value_v * this->reference_voltage_); - return value_v; -} -void MCP3008Sensor::update() { this->publish_state(this->sample()); } - } // namespace mcp3008 } // namespace esphome diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 5d8b823111ec..baf8d7c152d8 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -1,10 +1,8 @@ #pragma once +#include "esphome/components/spi/spi.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/spi/spi.h" -#include "esphome/components/voltage_sampler/voltage_sampler.h" namespace esphome { namespace mcp3008 { @@ -14,31 +12,10 @@ class MCP3008 : public Component, spi::DATA_RATE_75KHZ> { // Running at the slowest max speed supported by the // mcp3008. 2.7v = 75ksps public: - MCP3008() = default; - void setup() override; void dump_config() override; float get_setup_priority() const override; float read_data(uint8_t pin); - - protected: -}; - -class MCP3008Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { - public: - MCP3008Sensor(MCP3008 *parent, uint8_t pin, float reference_voltage); - - void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } - void setup() override; - void update() override; - void dump_config() override; - float get_setup_priority() const override; - float sample() override; - - protected: - MCP3008 *parent_; - uint8_t pin_; - float reference_voltage_; }; } // namespace mcp3008 diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py deleted file mode 100644 index dd5141484b6a..000000000000 --- a/esphome/components/mcp3008/sensor.py +++ /dev/null @@ -1,39 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_ID, CONF_NUMBER -from . import mcp3008_ns, MCP3008 - -AUTO_LOAD = ["voltage_sampler"] - -DEPENDENCIES = ["mcp3008"] - -MCP3008Sensor = mcp3008_ns.class_( - "MCP3008Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler -) -CONF_REFERENCE_VOLTAGE = "reference_voltage" -CONF_MCP3008_ID = "mcp3008_id" - -CONFIG_SCHEMA = ( - sensor.sensor_schema(MCP3008Sensor) - .extend( - { - cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, - } - ) - .extend(cv.polling_component_schema("1s")) -) - - -async def to_code(config): - parent = await cg.get_variable(config[CONF_MCP3008_ID]) - var = cg.new_Pvariable( - config[CONF_ID], - parent, - config[CONF_NUMBER], - config[CONF_REFERENCE_VOLTAGE], - ) - await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/mcp3008/sensor/__init__.py b/esphome/components/mcp3008/sensor/__init__.py new file mode 100644 index 000000000000..8ae00ef29e6f --- /dev/null +++ b/esphome/components/mcp3008/sensor/__init__.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler +from esphome.const import ( + CONF_ID, + CONF_NUMBER, + CONF_REFERENCE_VOLTAGE, + UNIT_VOLT, + STATE_CLASS_MEASUREMENT, + DEVICE_CLASS_VOLTAGE, +) + +from .. import mcp3008_ns, MCP3008 + +AUTO_LOAD = ["voltage_sampler"] + +DEPENDENCIES = ["mcp3008"] + +MCP3008Sensor = mcp3008_ns.class_( + "MCP3008Sensor", + sensor.Sensor, + cg.PollingComponent, + voltage_sampler.VoltageSampler, + cg.Parented.template(MCP3008), +) +CONF_MCP3008_ID = "mcp3008_id" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + MCP3008Sensor, + unit_of_measurement=UNIT_VOLT, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_VOLTAGE, + ) + .extend( + { + cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, + } + ) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_parented(var, config[CONF_MCP3008_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(var.set_pin(config[CONF_NUMBER])) + cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) diff --git a/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp b/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp new file mode 100644 index 000000000000..df2a8735f8aa --- /dev/null +++ b/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp @@ -0,0 +1,27 @@ +#include "mcp3008_sensor.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp3008 { + +static const char *const TAG = "mcp3008.sensor"; + +float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void MCP3008Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "MCP3008Sensor:"); + ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); +} + +float MCP3008Sensor::sample() { + float value_v = this->parent_->read_data(pin_); + value_v = (value_v * this->reference_voltage_); + return value_v; +} + +void MCP3008Sensor::update() { this->publish_state(this->sample()); } + +} // namespace mcp3008 +} // namespace esphome diff --git a/esphome/components/mcp3008/sensor/mcp3008_sensor.h b/esphome/components/mcp3008/sensor/mcp3008_sensor.h new file mode 100644 index 000000000000..ebaeab966fc0 --- /dev/null +++ b/esphome/components/mcp3008/sensor/mcp3008_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" +#include "esphome/core/component.h" + +#include "../mcp3008.h" + +namespace esphome { +namespace mcp3008 { + +class MCP3008Sensor : public PollingComponent, + public sensor::Sensor, + public voltage_sampler::VoltageSampler, + public Parented { + public: + void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + + void update() override; + void dump_config() override; + float get_setup_priority() const override; + float sample() override; + + protected: + uint8_t pin_; + float reference_voltage_; +}; + +} // namespace mcp3008 +} // namespace esphome diff --git a/esphome/components/mcp3204/__init__.py b/esphome/components/mcp3204/__init__.py index 0536166e5684..98129fc3892e 100644 --- a/esphome/components/mcp3204/__init__.py +++ b/esphome/components/mcp3204/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_REFERENCE_VOLTAGE DEPENDENCIES = ["spi"] MULTI_CONF = True @@ -10,7 +10,6 @@ mcp3204_ns = cg.esphome_ns.namespace("mcp3204") MCP3204 = mcp3204_ns.class_("MCP3204", cg.Component, spi.SPIDevice) -CONF_REFERENCE_VOLTAGE = "reference_voltage" CONFIG_SCHEMA = cv.Schema( { diff --git a/esphome/components/mcp4728/__init__.py b/esphome/components/mcp4728/__init__.py index d130ceb73855..a0702c415cdf 100644 --- a/esphome/components/mcp4728/__init__.py +++ b/esphome/components/mcp4728/__init__.py @@ -10,6 +10,7 @@ mcp4728_ns = cg.esphome_ns.namespace("mcp4728") MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice) +CONF_MCP4728_ID = "mcp4728_id" CONFIG_SCHEMA = ( cv.Schema( diff --git a/esphome/components/mcp4728/mcp4728_output.cpp b/esphome/components/mcp4728/mcp4728.cpp similarity index 90% rename from esphome/components/mcp4728/mcp4728_output.cpp rename to esphome/components/mcp4728/mcp4728.cpp index d01196762410..1a8568a21c0a 100644 --- a/esphome/components/mcp4728/mcp4728_output.cpp +++ b/esphome/components/mcp4728/mcp4728.cpp @@ -1,4 +1,4 @@ -#include "mcp4728_output.h" +#include "mcp4728.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -110,12 +110,5 @@ void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain) this->update_ = true; } -void MCP4728Channel::write_state(float state) { - const uint16_t max_duty = 4095; - const float duty_rounded = roundf(state * max_duty); - auto duty = static_cast(duty_rounded); - this->parent_->set_channel_value_(this->channel_, duty); -} - } // namespace mcp4728 } // namespace esphome diff --git a/esphome/components/mcp4728/mcp4728_output.h b/esphome/components/mcp4728/mcp4728.h similarity index 69% rename from esphome/components/mcp4728/mcp4728_output.h rename to esphome/components/mcp4728/mcp4728.h index 55bcfdccb678..f2262f4a3586 100644 --- a/esphome/components/mcp4728/mcp4728_output.h +++ b/esphome/components/mcp4728/mcp4728.h @@ -1,7 +1,6 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/components/output/float_output.h" #include "esphome/components/i2c/i2c.h" namespace esphome { @@ -64,28 +63,5 @@ class MCP4728Component : public Component, public i2c::I2CDevice { bool update_ = false; }; -class MCP4728Channel : public output::FloatOutput { - public: - MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain, - MCP4728PwrDown pwrdown) - : parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) { - // update VREF - parent->select_vref_(channel, vref_); - // update PD - parent->select_power_down_(channel, pwrdown_); - // update GAIN - parent->select_gain_(channel, gain_); - } - - protected: - void write_state(float state) override; - - MCP4728Component *parent_; - MCP4728ChannelIdx channel_; - MCP4728Vref vref_; - MCP4728Gain gain_; - MCP4728PwrDown pwrdown_; -}; - } // namespace mcp4728 } // namespace esphome diff --git a/esphome/components/mcp4728/output.py b/esphome/components/mcp4728/output/__init__.py similarity index 96% rename from esphome/components/mcp4728/output.py rename to esphome/components/mcp4728/output/__init__.py index e0913ab98a9b..20b196ca2c3b 100644 --- a/esphome/components/mcp4728/output.py +++ b/esphome/components/mcp4728/output/__init__.py @@ -2,12 +2,11 @@ import esphome.config_validation as cv from esphome.components import output from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN -from . import MCP4728Component, mcp4728_ns +from .. import MCP4728Component, CONF_MCP4728_ID, mcp4728_ns DEPENDENCIES = ["mcp4728"] MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput) -CONF_MCP4728_ID = "mcp4728_id" CONF_VREF = "vref" CONF_POWER_DOWN = "power_down" diff --git a/esphome/components/mcp4728/output/mcp4728_output.cpp b/esphome/components/mcp4728/output/mcp4728_output.cpp new file mode 100644 index 000000000000..b587e8801b1f --- /dev/null +++ b/esphome/components/mcp4728/output/mcp4728_output.cpp @@ -0,0 +1,17 @@ +#include "mcp4728_output.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp4728 { + +void MCP4728Channel::write_state(float state) { + const uint16_t max_duty = 4095; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + this->parent_->set_channel_value_(this->channel_, duty); +} + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mcp4728/output/mcp4728_output.h b/esphome/components/mcp4728/output/mcp4728_output.h new file mode 100644 index 000000000000..453d632f4c60 --- /dev/null +++ b/esphome/components/mcp4728/output/mcp4728_output.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../mcp4728.h" +#include "esphome/core/component.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mcp4728 { + +class MCP4728Channel : public output::FloatOutput { + public: + MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain, + MCP4728PwrDown pwrdown) + : parent_(parent), channel_(channel) { + // update VREF + parent->select_vref_(channel, vref); + // update PD + parent->select_power_down_(channel, pwrdown); + // update GAIN + parent->select_gain_(channel, gain); + } + + protected: + void write_state(float state) override; + + MCP4728Component *parent_; + MCP4728ChannelIdx channel_; +}; + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index e7d700d1494a..82cf087fdcd9 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -88,7 +88,7 @@ async def to_code(config): add_idf_component( name="mdns", repo="https://github.com/espressif/esp-protocols.git", - ref="mdns-v1.0.9", + ref="mdns-v1.2.5", path="components/mdns", ) diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 4ccfe42baa75..5ff1b8634116 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -13,8 +13,7 @@ namespace mdns { void MDNSComponent::setup() { this->compile_records_(); - network::IPAddress addr = network::get_ip_address(); - MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + MDNS.begin(this->hostname_.c_str()); for (const auto &service : this->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 b30a3a4ee7df..56afd6f5e17f 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -13,8 +13,7 @@ namespace mdns { void MDNSComponent::setup() { this->compile_records_(); - network::IPAddress addr = network::get_ip_address(); - MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + MDNS.begin(this->hostname_.c_str()); for (const auto &service : this->services_) { // Strip the leading underscore from the proto and service_type. While it is diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 80f5fc558a2c..320014e355b2 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -3,7 +3,13 @@ import esphome.codegen as cg from esphome.automation import maybe_simple_id -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID +from esphome.const import ( + CONF_ID, + CONF_ON_STATE, + CONF_TRIGGER_ID, + CONF_VOLUME, + CONF_ON_IDLE, +) from esphome.core import CORE from esphome.coroutine import coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -43,16 +49,18 @@ ) -CONF_VOLUME = "volume" -CONF_ON_IDLE = "on_idle" CONF_ON_PLAY = "on_play" CONF_ON_PAUSE = "on_pause" +CONF_ON_ANNOUNCEMENT = "on_announcement" CONF_MEDIA_URL = "media_url" StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template()) IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template()) PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template()) PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template()) +AnnoucementTrigger = media_player_ns.class_( + "AnnouncementTrigger", automation.Trigger.template() +) IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) @@ -71,6 +79,9 @@ async def setup_media_player_core_(var, config): for conf in config.get(CONF_ON_PAUSE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_ANNOUNCEMENT, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) async def register_media_player(var, config): @@ -102,6 +113,11 @@ async def register_media_player(var, config): cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), } ), + cv.Optional(CONF_ON_ANNOUNCEMENT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(AnnoucementTrigger), + } + ), } ) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index 261e93775c2d..fc3ce7a76498 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -52,6 +52,7 @@ class StateTrigger : public Trigger<> { MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED) +MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING) template class IsIdleCondition : public Condition, public Parented { public: diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 81cb6ca75133..586345ac9fb8 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -15,6 +15,8 @@ const char *media_player_state_to_string(MediaPlayerState state) { return "PLAYING"; case MEDIA_PLAYER_STATE_PAUSED: return "PAUSED"; + case MEDIA_PLAYER_STATE_ANNOUNCING: + return "ANNOUNCING"; case MEDIA_PLAYER_STATE_NONE: default: return "UNKNOWN"; @@ -68,6 +70,9 @@ void MediaPlayerCall::perform() { if (this->volume_.has_value()) { ESP_LOGD(TAG, " Volume: %.2f", this->volume_.value()); } + if (this->announcement_.has_value()) { + ESP_LOGD(TAG, " Announcement: %s", this->announcement_.value() ? "yes" : "no"); + } this->parent_->control(*this); } @@ -108,6 +113,11 @@ MediaPlayerCall &MediaPlayerCall::set_volume(float volume) { return *this; } +MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) { + this->announcement_ = announce; + return *this; +} + void MediaPlayer::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 88114d533769..77746e1808ce 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -10,7 +10,8 @@ enum MediaPlayerState : uint8_t { MEDIA_PLAYER_STATE_NONE = 0, MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_PLAYING = 2, - MEDIA_PLAYER_STATE_PAUSED = 3 + MEDIA_PLAYER_STATE_PAUSED = 3, + MEDIA_PLAYER_STATE_ANNOUNCING = 4 }; const char *media_player_state_to_string(MediaPlayerState state); @@ -51,12 +52,14 @@ class MediaPlayerCall { MediaPlayerCall &set_media_url(const std::string &url); MediaPlayerCall &set_volume(float volume); + MediaPlayerCall &set_announcement(bool announce); void perform(); const optional &get_command() const { return command_; } const optional &get_media_url() const { return media_url_; } const optional &get_volume() const { return volume_; } + const optional &get_announcement() const { return announcement_; } protected: void validate_(); @@ -64,6 +67,7 @@ class MediaPlayerCall { optional command_; optional media_url_; optional volume_; + optional announcement_; }; class MediaPlayer : public EntityBase { diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index db3ad50851e1..019f6cee51ad 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -29,6 +29,14 @@ void MHZ19Component::setup() { } void MHZ19Component::update() { + uint32_t now_ms = millis(); + uint32_t warmup_ms = this->warmup_seconds_ * 1000; + if (now_ms < warmup_ms) { + ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000); + this->status_set_warning(); + return; + } + uint8_t response[MHZ19_RESPONSE_LENGTH]; if (!this->mhz19_write_command_(MHZ19_COMMAND_GET_PPM, response)) { ESP_LOGW(TAG, "Reading data from MHZ19 failed!"); @@ -101,6 +109,8 @@ void MHZ19Component::dump_config() { } else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) { ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot"); } + + ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_); } } // namespace mhz19 diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index 151351be4cd7..ec38f2cd2fc4 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -25,6 +25,7 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { 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; } protected: bool mhz19_write_command_(const uint8_t *command, uint8_t *response); @@ -32,6 +33,7 @@ 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_; }; template class MHZ19CalibrateZeroAction : public Action { diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 0081f429528c..3956727981af 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -18,6 +18,7 @@ DEPENDENCIES = ["uart"] CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration" +CONF_WARMUP_TIME = "warmup_time" mhz19_ns = cg.esphome_ns.namespace("mhz19") MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) @@ -45,6 +46,9 @@ state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean, + cv.Optional( + CONF_WARMUP_TIME, default="75s" + ): cv.positive_time_period_seconds, } ) .extend(cv.polling_component_schema("60s")) @@ -68,6 +72,8 @@ async def to_code(config): if CONF_AUTOMATIC_BASELINE_CALIBRATION in config: cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION])) + cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME])) + CALIBRATION_ACTION_SCHEMA = maybe_simple_id( { diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py new file mode 100644 index 000000000000..9073d103f1ec --- /dev/null +++ b/esphome/components/micro_wake_word/__init__.py @@ -0,0 +1,367 @@ +import logging + +import json +import hashlib +from urllib.parse import urljoin +from pathlib import Path +import requests + +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.core import CORE, HexInt, EsphomeError + +from esphome.components import esp32, microphone +from esphome import automation, git, external_files +from esphome.automation import register_action, register_condition + + +from esphome.const import ( + __version__, + CONF_ID, + CONF_MICROPHONE, + CONF_MODEL, + CONF_URL, + CONF_FILE, + CONF_PATH, + CONF_REF, + CONF_REFRESH, + CONF_TYPE, + CONF_USERNAME, + CONF_PASSWORD, + CONF_RAW_DATA_ID, + TYPE_GIT, + TYPE_LOCAL, +) + + +_LOGGER = logging.getLogger(__name__) + +CODEOWNERS = ["@kahrendt", "@jesserockz"] +DEPENDENCIES = ["microphone"] +DOMAIN = "micro_wake_word" + +CONF_PROBABILITY_CUTOFF = "probability_cutoff" +CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size" +CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" + +TYPE_HTTP = "http" + +micro_wake_word_ns = cg.esphome_ns.namespace("micro_wake_word") + +MicroWakeWord = micro_wake_word_ns.class_("MicroWakeWord", cg.Component) + +StartAction = micro_wake_word_ns.class_("StartAction", automation.Action) +StopAction = micro_wake_word_ns.class_("StopAction", automation.Action) + +IsRunningCondition = micro_wake_word_ns.class_( + "IsRunningCondition", automation.Condition +) + + +def _validate_json_filename(value): + value = cv.string(value) + if not value.endswith(".json"): + raise cv.Invalid("Manifest filename must end with .json") + return value + + +def _process_git_source(config): + repo_dir, _ = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=config[CONF_REFRESH], + domain=DOMAIN, + username=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), + ) + + if not (repo_dir / config[CONF_FILE]).exists(): + raise cv.Invalid("File does not exist in repository") + + return config + + +CV_GIT_SCHEMA = cv.GIT_SCHEMA +if isinstance(CV_GIT_SCHEMA, dict): + CV_GIT_SCHEMA = cv.Schema(CV_GIT_SCHEMA) + +GIT_SCHEMA = cv.All( + CV_GIT_SCHEMA.extend( + { + cv.Required(CONF_FILE): _validate_json_filename, + cv.Optional(CONF_REFRESH, default="1d"): cv.All( + cv.string, cv.source_refresh + ), + } + ), + _process_git_source, +) + +KEY_WAKE_WORD = "wake_word" +KEY_AUTHOR = "author" +KEY_WEBSITE = "website" +KEY_VERSION = "version" +KEY_MICRO = "micro" +KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version" + +MANIFEST_SCHEMA_V1 = cv.Schema( + { + cv.Required(CONF_TYPE): "micro", + cv.Required(KEY_WAKE_WORD): cv.string, + cv.Required(KEY_AUTHOR): cv.string, + cv.Required(KEY_WEBSITE): cv.url, + cv.Required(KEY_VERSION): cv.All(cv.int_, 1), + cv.Required(CONF_MODEL): cv.string, + cv.Required(KEY_MICRO): cv.Schema( + { + cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_, + cv.Required(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, + cv.Optional(KEY_MINIMUM_ESPHOME_VERSION): cv.All( + cv.version_number, cv.validate_esphome_version + ), + } + ), + } +) + + +def _compute_local_file_path(config: dict) -> Path: + url = config[CONF_URL] + h = hashlib.new("sha256") + h.update(url.encode()) + key = h.hexdigest()[:8] + base_dir = external_files.compute_local_file_dir(DOMAIN) + return base_dir / key + + +def _download_file(url: str, path: Path) -> bytes: + if not external_files.has_remote_file_changed(url, path): + _LOGGER.debug("Remote file has not changed, skipping download") + return path.read_bytes() + + try: + req = requests.get( + url, + timeout=external_files.NETWORK_TIMEOUT, + headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, + ) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download file from {url}: {e}") from e + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(req.content) + return req.content + + +def _process_http_source(config): + url = config[CONF_URL] + path = _compute_local_file_path(config) + + json_path = path / "manifest.json" + + json_contents = _download_file(url, json_path) + + manifest_data = json.loads(json_contents) + if not isinstance(manifest_data, dict): + raise cv.Invalid("Manifest file must contain a JSON object") + + try: + MANIFEST_SCHEMA_V1(manifest_data) + except cv.Invalid as e: + raise cv.Invalid(f"Invalid manifest file: {e}") from e + + model = manifest_data[CONF_MODEL] + model_url = urljoin(url, model) + + model_path = path / model + + _download_file(str(model_url), model_path) + + return config + + +HTTP_SCHEMA = cv.All( + { + cv.Required(CONF_URL): cv.url, + }, + _process_http_source, +) + +LOCAL_SCHEMA = cv.Schema( + { + cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_), + } +) + + +def _validate_source_model_name(value): + if not isinstance(value, str): + raise cv.Invalid("Model name must be a string") + + if value.endswith(".json"): + raise cv.Invalid("Model name must not end with .json") + + return MODEL_SOURCE_SCHEMA( + { + CONF_TYPE: TYPE_HTTP, + CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json", + } + ) + + +def _validate_source_shorthand(value): + if not isinstance(value, str): + raise cv.Invalid("Shorthand only for strings") + + try: # Test for model name + return _validate_source_model_name(value) + except cv.Invalid: + pass + + try: # Test for local path + return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value}) + except cv.Invalid: + pass + + try: # Test for http url + return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_HTTP, CONF_URL: value}) + except cv.Invalid: + pass + + git_file = git.GitFile.from_shorthand(value) + + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: git_file.git_url, + CONF_FILE: git_file.filename, + } + if git_file.ref: + conf[CONF_REF] = git_file.ref + + try: + return MODEL_SOURCE_SCHEMA(conf) + except cv.Invalid as e: + raise cv.Invalid( + f"Could not find file '{git_file.filename}' in the repository. Please make sure it exists." + ) from e + + +MODEL_SOURCE_SCHEMA = cv.Any( + _validate_source_shorthand, + cv.typed_schema( + { + TYPE_GIT: GIT_SCHEMA, + TYPE_LOCAL: LOCAL_SCHEMA, + TYPE_HTTP: HTTP_SCHEMA, + } + ), + msg="Not a valid model name, local path, http(s) url, or github shorthand", +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MicroWakeWord), + cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), + cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage, + cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, + cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation( + single=True + ), + cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA, + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_esp_idf, +) + + +def _load_model_data(manifest_path: Path): + with open(manifest_path, encoding="utf-8") as f: + manifest = json.load(f) + + try: + MANIFEST_SCHEMA_V1(manifest) + except cv.Invalid as e: + raise EsphomeError(f"Invalid manifest file: {e}") from e + + model_path = manifest_path.parent / manifest[CONF_MODEL] + + with open(model_path, "rb") as f: + model = f.read() + + return manifest, model + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + mic = await cg.get_variable(config[CONF_MICROPHONE]) + cg.add(var.set_microphone(mic)) + + if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED): + await automation.build_automation( + var.get_wake_word_detected_trigger(), + [(cg.std_string, "wake_word")], + on_wake_word_detection_config, + ) + + esp32.add_idf_component( + name="esp-tflite-micro", + repo="https://github.com/espressif/esp-tflite-micro", + ) + + cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") + cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") + cg.add_build_flag("-DESP_NN") + + model_config = config.get(CONF_MODEL) + data = [] + if model_config[CONF_TYPE] == TYPE_GIT: + # compute path to model file + key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}" + base_dir = Path(CORE.data_dir) / DOMAIN + h = hashlib.new("sha256") + h.update(key.encode()) + file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE] + + elif model_config[CONF_TYPE] == TYPE_LOCAL: + file = model_config[CONF_PATH] + + elif model_config[CONF_TYPE] == TYPE_HTTP: + file = _compute_local_file_path(model_config) / "manifest.json" + + manifest, data = _load_model_data(file) + + rhs = [HexInt(x) for x in data] + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + cg.add(var.set_model_start(prog_arr)) + + probability_cutoff = config.get( + CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF] + ) + cg.add(var.set_probability_cutoff(probability_cutoff)) + sliding_window_average_size = config.get( + CONF_SLIDING_WINDOW_AVERAGE_SIZE, + manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE], + ) + cg.add(var.set_sliding_window_average_size(sliding_window_average_size)) + + cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD])) + + +MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)}) + + +@register_action("micro_wake_word.start", StartAction, MICRO_WAKE_WORD_ACTION_SCHEMA) +@register_action("micro_wake_word.stop", StopAction, MICRO_WAKE_WORD_ACTION_SCHEMA) +@register_condition( + "micro_wake_word.is_running", IsRunningCondition, MICRO_WAKE_WORD_ACTION_SCHEMA +) +async def micro_wake_word_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/micro_wake_word/audio_preprocessor_int8_model_data.h b/esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h new file mode 100644 index 000000000000..918e76045f71 --- /dev/null +++ b/esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h @@ -0,0 +1,493 @@ +#pragma once + +#ifdef USE_ESP_IDF + +// Converted audio_preprocessor_int8.tflite +// From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed +// January 2024 +// +// Copyright 2023 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace esphome { +namespace micro_wake_word { + +const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = { + 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00, + 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff, + 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, + 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, + 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48, + 0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00, + 0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00, + 0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01, + 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c, + 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, + 0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, + 0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00, + 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94, + 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, + 0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00, + 0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc, + 0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff, + 0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff, + 0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2, + 0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28, + 0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff, + 0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, + 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41, + 0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05, + 0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f, + 0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48, + 0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7, + 0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09, + 0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03, + 0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87, + 0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a, + 0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9, + 0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03, + 0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99, + 0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d, + 0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1, + 0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c, + 0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f, + 0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00, + 0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92, + 0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08, + 0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a, + 0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03, + 0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e, + 0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00, + 0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e, + 0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07, + 0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03, + 0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42, + 0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6, + 0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a, + 0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5, + 0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, + 0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18, + 0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07, + 0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e, + 0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66, + 0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04, + 0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0, + 0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04, + 0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61, + 0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf, + 0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08, + 0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8, + 0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c, + 0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, + 0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c, + 0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10, + 0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00, + 0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a, + 0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00, + 0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, + 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00, + 0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44, + 0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, + 0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8, + 0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04, + 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00, + 0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6, + 0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef, + 0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00, + 0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13, + 0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4, + 0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00, + 0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3, + 0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00, + 0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, + 0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00, + 0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51, + 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00, + 0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19, + 0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01, + 0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e, + 0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03, + 0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd, + 0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04, + 0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad, + 0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06, + 0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2, + 0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08, + 0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d, + 0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a, + 0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e, + 0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c, + 0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28, + 0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d, + 0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81, + 0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f, + 0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73, + 0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f, + 0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0, + 0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0, + 0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f, + 0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73, + 0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f, + 0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81, + 0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d, + 0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28, + 0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c, + 0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e, + 0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a, + 0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d, + 0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08, + 0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2, + 0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06, + 0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad, + 0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04, + 0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd, + 0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03, + 0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e, + 0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01, + 0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19, + 0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00, + 0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51, + 0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00, + 0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c, + 0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00, + 0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70, + 0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00, + 0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00, + 0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00, + 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, + 0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, + 0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c, + 0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, + 0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e, + 0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, + 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, + 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, + 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, + 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, + 0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29, + 0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05, + 0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff, + 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00, + 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10, + 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05, + 0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, + 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, + 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a, + 0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88, + 0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00, + 0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00, + 0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05, + 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98, + 0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, + 0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, + 0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01, + 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe, + 0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff, + 0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0, + 0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61, + 0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, + 0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03, + 0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, + 0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, + 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34, + 0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, + 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, + 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8, + 0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00, + 0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda, + 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, + 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00, + 0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe, + 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff, + 0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, + 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, + 0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00, + 0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, + 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, + 0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, + 0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, + 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, + 0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68, + 0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, + 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, + 0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0, + 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a, + 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, + 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, + 0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd, + 0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, + 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, + 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff, + 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, + 0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8, + 0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00, + 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, + 0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, + 0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, + 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, + 0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, + 0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13, + 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, + 0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe, + 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, + 0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff, + 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72, + 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a, + 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, + 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00}; + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp new file mode 100644 index 000000000000..f637f8b2bb08 --- /dev/null +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -0,0 +1,507 @@ +#include "micro_wake_word.h" + +/** + * This is a workaround until we can figure out a way to get + * the tflite-micro idf component code available in CI + * + * */ +// +#ifndef CLANG_TIDY + +#ifdef USE_ESP_IDF + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include "audio_preprocessor_int8_model_data.h" + +#include +#include +#include + +#include + +namespace esphome { +namespace micro_wake_word { + +static const char *const TAG = "micro_wake_word"; + +static const size_t SAMPLE_RATE_HZ = 16000; // 16 kHz +static const size_t BUFFER_LENGTH = 500; // 0.5 seconds +static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH; +static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms + +float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +static const LogString *micro_wake_word_state_to_string(State state) { + switch (state) { + case State::IDLE: + return LOG_STR("IDLE"); + case State::START_MICROPHONE: + return LOG_STR("START_MICROPHONE"); + case State::STARTING_MICROPHONE: + return LOG_STR("STARTING_MICROPHONE"); + case State::DETECTING_WAKE_WORD: + return LOG_STR("DETECTING_WAKE_WORD"); + case State::STOP_MICROPHONE: + return LOG_STR("STOP_MICROPHONE"); + case State::STOPPING_MICROPHONE: + return LOG_STR("STOPPING_MICROPHONE"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void MicroWakeWord::dump_config() { + ESP_LOGCONFIG(TAG, "microWakeWord:"); + ESP_LOGCONFIG(TAG, " Wake Word: %s", this->get_wake_word().c_str()); + ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_); + ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_average_size_); +} + +void MicroWakeWord::setup() { + ESP_LOGCONFIG(TAG, "Setting up microWakeWord..."); + + if (!this->initialize_models()) { + ESP_LOGE(TAG, "Failed to initialize models"); + this->mark_failed(); + return; + } + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t)); + if (this->input_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate input buffer"); + this->mark_failed(); + return; + } + + this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); + if (this->ring_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate ring buffer"); + this->mark_failed(); + return; + } + + ESP_LOGCONFIG(TAG, "Micro Wake Word initialized"); +} + +int MicroWakeWord::read_microphone_() { + size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); + if (bytes_read == 0) { + return 0; + } + + size_t bytes_free = this->ring_buffer_->free(); + + if (bytes_free < bytes_read) { + ESP_LOGW(TAG, + "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). " + "Resetting the ring buffer. Wake word detection accuracy will be reduced.", + bytes_free, bytes_read); + + this->ring_buffer_->reset(); + } + + return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); +} + +void MicroWakeWord::loop() { + switch (this->state_) { + case State::IDLE: + break; + case State::START_MICROPHONE: + ESP_LOGD(TAG, "Starting Microphone"); + this->microphone_->start(); + this->set_state_(State::STARTING_MICROPHONE); + this->high_freq_.start(); + break; + case State::STARTING_MICROPHONE: + if (this->microphone_->is_running()) { + this->set_state_(State::DETECTING_WAKE_WORD); + } + break; + case State::DETECTING_WAKE_WORD: + this->read_microphone_(); + if (this->detect_wake_word_()) { + ESP_LOGD(TAG, "Wake Word Detected"); + this->detected_ = true; + this->set_state_(State::STOP_MICROPHONE); + } + break; + case State::STOP_MICROPHONE: + ESP_LOGD(TAG, "Stopping Microphone"); + this->microphone_->stop(); + this->set_state_(State::STOPPING_MICROPHONE); + this->high_freq_.stop(); + break; + case State::STOPPING_MICROPHONE: + if (this->microphone_->is_stopped()) { + this->set_state_(State::IDLE); + if (this->detected_) { + this->detected_ = false; + this->wake_word_detected_trigger_->trigger(this->wake_word_); + } + } + break; + } +} + +void MicroWakeWord::start() { + if (this->is_failed()) { + ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs"); + return; + } + if (this->state_ != State::IDLE) { + ESP_LOGW(TAG, "Wake word is already running"); + return; + } + this->set_state_(State::START_MICROPHONE); +} + +void MicroWakeWord::stop() { + if (this->state_ == State::IDLE) { + ESP_LOGW(TAG, "Wake word is already stopped"); + return; + } + if (this->state_ == State::STOPPING_MICROPHONE) { + ESP_LOGW(TAG, "Wake word is already stopping"); + return; + } + this->set_state_(State::STOP_MICROPHONE); +} + +void MicroWakeWord::set_state_(State state) { + ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)), + LOG_STR_ARG(micro_wake_word_state_to_string(state))); + this->state_ = state; +} + +bool MicroWakeWord::initialize_models() { + ExternalRAMAllocator arena_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator features_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator audio_samples_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + + this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE); + if (this->streaming_tensor_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena."); + return false; + } + + this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE); + if (this->streaming_var_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena."); + return false; + } + + this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE); + if (this->preprocessor_tensor_arena_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena."); + return false; + } + + this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE); + if (this->new_features_data_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio features buffer."); + return false; + } + + this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT); + if (this->preprocessor_audio_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer."); + return false; + } + + this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE); + if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) { + ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported"); + return false; + } + + this->streaming_model_ = tflite::GetModel(this->model_start_); + if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) { + ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported"); + return false; + } + + static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver; + static tflite::MicroMutableOpResolver<17> streaming_op_resolver; + + if (!this->register_preprocessor_ops_(preprocessor_op_resolver)) + return false; + if (!this->register_streaming_ops_(streaming_op_resolver)) + return false; + + tflite::MicroAllocator *ma = + tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); + this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15); + + static tflite::MicroInterpreter static_preprocessor_interpreter( + this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE); + + static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver, + this->streaming_tensor_arena_, + STREAMING_MODEL_ARENA_SIZE, this->mrv_); + + this->preprocessor_interperter_ = &static_preprocessor_interpreter; + this->streaming_interpreter_ = &static_streaming_interpreter; + + // Allocate tensors for each models. + if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor"); + return false; + } + if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model"); + return false; + } + + // Verify input tensor matches expected values + TfLiteTensor *input = this->streaming_interpreter_->input(0); + if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) || + (input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) { + ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]); + return false; + } + + if (input->type != kTfLiteInt8) { + ESP_LOGE(TAG, "Wake word detection model tensor input is not int8."); + return false; + } + + // Verify output tensor matches expected values + TfLiteTensor *output = this->streaming_interpreter_->output(0); + if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) { + ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1."); + } + + if (output->type != kTfLiteUInt8) { + ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8."); + return false; + } + + this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); + + return true; +} + +bool MicroWakeWord::update_features_() { + // Retrieve strided audio samples + int16_t *audio_samples = nullptr; + if (!this->stride_audio_samples_(&audio_samples)) { + return false; + } + + // Compute the features for the newest audio samples + if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) { + return false; + } + + return true; +} + +float MicroWakeWord::perform_streaming_inference_() { + TfLiteTensor *input = this->streaming_interpreter_->input(0); + + size_t bytes_to_copy = input->bytes; + + memcpy((void *) (tflite::GetTensorData(input)), (const void *) (this->new_features_data_), bytes_to_copy); + + uint32_t prior_invoke = millis(); + + TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke(); + if (invoke_status != kTfLiteOk) { + ESP_LOGW(TAG, "Streaming Interpreter Invoke failed"); + return false; + } + + ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke)); + + TfLiteTensor *output = this->streaming_interpreter_->output(0); + + return static_cast(output->data.uint8[0]) / 255.0; +} + +bool MicroWakeWord::detect_wake_word_() { + // Preprocess the newest audio samples into features + if (!this->update_features_()) { + return false; + } + + // Perform inference + float streaming_prob = this->perform_streaming_inference_(); + + // Add the most recent probability to the sliding window + this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob; + ++this->last_n_index_; + if (this->last_n_index_ == this->sliding_window_average_size_) + this->last_n_index_ = 0; + + float sum = 0.0; + for (auto &prob : this->recent_streaming_probabilities_) { + sum += prob; + } + + float sliding_window_average = sum / static_cast(this->sliding_window_average_size_); + + // Ensure we have enough samples since the last positive detection + this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); + if (this->ignore_windows_ < 0) { + return false; + } + + // Detect the wake word if the sliding window average is above the cutoff + if (sliding_window_average > this->probability_cutoff_) { + this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION; + for (auto &prob : this->recent_streaming_probabilities_) { + prob = 0; + } + + ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f", + sliding_window_average, streaming_prob); + return true; + } + + return false; +} + +void MicroWakeWord::set_sliding_window_average_size(size_t size) { + this->sliding_window_average_size_ = size; + this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); +} + +bool MicroWakeWord::slice_available_() { + size_t available = this->ring_buffer_->available(); + + return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t)); +} + +bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) { + if (!this->slice_available_()) { + return false; + } + + // Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer + memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET), + HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t)); + + // Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples + // over 10 ms) + size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP), + NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200)); + + if (bytes_read == 0) { + ESP_LOGE(TAG, "Could not read data from Ring Buffer"); + } else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) { + ESP_LOGD(TAG, "Partial Read of Data by Model"); + ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read, + (int) (NEW_SAMPLES_TO_GET * sizeof(int16_t))); + return false; + } + + *audio_samples = this->preprocessor_audio_buffer_; + return true; +} + +bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size, + int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) { + TfLiteTensor *input = this->preprocessor_interperter_->input(0); + TfLiteTensor *output = this->preprocessor_interperter_->output(0); + std::copy_n(audio_data, audio_data_size, tflite::GetTensorData(input)); + + if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) { + ESP_LOGE(TAG, "Failed to preprocess audio for local wake word."); + return false; + } + std::memcpy(feature_output, tflite::GetTensorData(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t)); + + return true; +} + +bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) { + if (op_resolver.AddReshape() != kTfLiteOk) + return false; + if (op_resolver.AddCast() != kTfLiteOk) + return false; + if (op_resolver.AddStridedSlice() != kTfLiteOk) + return false; + if (op_resolver.AddConcatenation() != kTfLiteOk) + return false; + if (op_resolver.AddMul() != kTfLiteOk) + return false; + if (op_resolver.AddAdd() != kTfLiteOk) + return false; + if (op_resolver.AddDiv() != kTfLiteOk) + return false; + if (op_resolver.AddMinimum() != kTfLiteOk) + return false; + if (op_resolver.AddMaximum() != kTfLiteOk) + return false; + if (op_resolver.AddWindow() != kTfLiteOk) + return false; + if (op_resolver.AddFftAutoScale() != kTfLiteOk) + return false; + if (op_resolver.AddRfft() != kTfLiteOk) + return false; + if (op_resolver.AddEnergy() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBank() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk) + return false; + if (op_resolver.AddPCAN() != kTfLiteOk) + return false; + if (op_resolver.AddFilterBankLog() != kTfLiteOk) + return false; + + return true; +} + +bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) { + if (op_resolver.AddCallOnce() != kTfLiteOk) + return false; + if (op_resolver.AddVarHandle() != kTfLiteOk) + return false; + if (op_resolver.AddReshape() != kTfLiteOk) + return false; + if (op_resolver.AddReadVariable() != kTfLiteOk) + return false; + if (op_resolver.AddStridedSlice() != kTfLiteOk) + return false; + if (op_resolver.AddConcatenation() != kTfLiteOk) + return false; + if (op_resolver.AddAssignVariable() != kTfLiteOk) + return false; + if (op_resolver.AddConv2D() != kTfLiteOk) + return false; + if (op_resolver.AddMul() != kTfLiteOk) + return false; + if (op_resolver.AddAdd() != kTfLiteOk) + return false; + if (op_resolver.AddMean() != kTfLiteOk) + return false; + if (op_resolver.AddFullyConnected() != kTfLiteOk) + return false; + if (op_resolver.AddLogistic() != kTfLiteOk) + return false; + if (op_resolver.AddQuantize() != kTfLiteOk) + return false; + if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk) + return false; + if (op_resolver.AddAveragePool2D() != kTfLiteOk) + return false; + if (op_resolver.AddMaxPool2D() != kTfLiteOk) + return false; + + return true; +} + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF + +#endif // CLANG_TIDY diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h new file mode 100644 index 000000000000..1d7c18d68690 --- /dev/null +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -0,0 +1,206 @@ +#pragma once + +/** + * This is a workaround until we can figure out a way to get + * the tflite-micro idf component code available in CI + * + * */ +// +#ifndef CLANG_TIDY + +#ifdef USE_ESP_IDF + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/ring_buffer.h" + +#include "esphome/components/microphone/microphone.h" + +#include +#include +#include + +namespace esphome { +namespace micro_wake_word { + +// The following are dictated by the preprocessor model +// +// The number of features the audio preprocessor generates per slice +static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40; +// How frequently the preprocessor generates a new set of features +static const uint8_t FEATURE_STRIDE_MS = 20; +// Duration of each slice used as input into the preprocessor +static const uint8_t FEATURE_DURATION_MS = 30; +// Audio sample frequency in hertz +static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000; +// The number of old audio samples that are saved to be part of the next feature window +static const uint16_t HISTORY_SAMPLES_TO_KEEP = + ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000)); +// The number of new audio samples to receive to be included with the next feature window +static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000)); +// The total number of audio samples included in the feature window +static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000; +// Number of bytes in memory needed for the preprocessor arena +static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528; + +// The following configure the streaming wake word model +// +// The number of audio slices to process before accepting a positive detection +static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74; + +// Number of bytes in memory needed for the streaming wake word model +static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000; +static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024; + +enum State { + IDLE, + START_MICROPHONE, + STARTING_MICROPHONE, + DETECTING_WAKE_WORD, + STOP_MICROPHONE, + STOPPING_MICROPHONE, +}; + +class MicroWakeWord : public Component { + public: + void setup() override; + void loop() override; + float get_setup_priority() const override; + void dump_config() override; + + void start(); + void stop(); + + bool is_running() const { return this->state_ != State::IDLE; } + + bool initialize_models(); + + std::string get_wake_word() { return this->wake_word_; } + + // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate + void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; } + void set_sliding_window_average_size(size_t size); + + void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; } + + Trigger *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } + + void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; } + void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } + + protected: + void set_state_(State state); + int read_microphone_(); + + const uint8_t *model_start_; + std::string wake_word_; + + microphone::Microphone *microphone_{nullptr}; + Trigger *wake_word_detected_trigger_ = new Trigger(); + State state_{State::IDLE}; + HighFrequencyLoopRequester high_freq_; + + std::unique_ptr ring_buffer_; + + int16_t *input_buffer_; + + const tflite::Model *preprocessor_model_{nullptr}; + const tflite::Model *streaming_model_{nullptr}; + tflite::MicroInterpreter *streaming_interpreter_{nullptr}; + tflite::MicroInterpreter *preprocessor_interperter_{nullptr}; + + std::vector recent_streaming_probabilities_; + size_t last_n_index_{0}; + + float probability_cutoff_{0.5}; + size_t sliding_window_average_size_{10}; + + // When the wake word detection first starts or after the word has been detected once, we ignore this many audio + // feature slices before accepting a positive detection again + int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION}; + + uint8_t *streaming_var_arena_{nullptr}; + uint8_t *streaming_tensor_arena_{nullptr}; + uint8_t *preprocessor_tensor_arena_{nullptr}; + int8_t *new_features_data_{nullptr}; + + tflite::MicroResourceVariables *mrv_{nullptr}; + + // Stores audio fed into feature generator preprocessor + int16_t *preprocessor_audio_buffer_; + + bool detected_{false}; + + /** Detects if wake word has been said + * + * If enough audio samples are available, it will generate one slice of new features. + * If the streaming model predicts the wake word, then the nonstreaming model confirms it. + * @param ring_Buffer Ring buffer containing raw audio samples + * @return True if the wake word is detected, false otherwise + */ + bool detect_wake_word_(); + + /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features + bool slice_available_(); + + /** Shifts previous feature slices over by one and generates a new slice of features + * + * @param ring_buffer ring buffer containing raw audio samples + * @return True if a new slice of features was generated, false otherwise + */ + bool update_features_(); + + /** Generates features from audio samples + * + * Adapted from TFLite micro speech example + * @param audio_data Pointer to array with the audio samples + * @param audio_data_size The number of samples to use as input to the preprocessor model + * @param feature_output Array that will store the features + * @return True if successful, false otherwise. + */ + bool generate_single_feature_(const int16_t *audio_data, int audio_data_size, + int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]); + + /** Performs inference over the most recent feature slice with the streaming model + * + * @return Probability of the wake word between 0.0 and 1.0 + */ + float perform_streaming_inference_(); + + /** Strides the audio samples by keeping the last 10 ms of the previous slice + * + * Adapted from the TFLite micro speech example + * @param ring_buffer Ring buffer containing raw audio samples + * @param audio_samples Pointer to an array that will store the strided audio samples + * @return True if successful, false otherwise + */ + bool stride_audio_samples_(int16_t **audio_samples); + + /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations + bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver); + + /// @brief Returns true if successfully registered the streaming model's TensorFlow operations + bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver); +}; + +template class StartAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->start(); } +}; + +template class StopAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->stop(); } +}; + +template class IsRunningCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_running(); } +}; + +} // namespace micro_wake_word +} // namespace esphome + +#endif // USE_ESP_IDF + +#endif // CLANG_TIDY diff --git a/esphome/components/micronova/__init__.py b/esphome/components/micronova/__init__.py new file mode 100644 index 000000000000..bd253f8ebd25 --- /dev/null +++ b/esphome/components/micronova/__init__.py @@ -0,0 +1,69 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import uart +from esphome.const import ( + CONF_ID, +) + +CODEOWNERS = ["@jorre05"] + +DEPENDENCIES = ["uart"] + +CONF_MICRONOVA_ID = "micronova_id" +CONF_ENABLE_RX_PIN = "enable_rx_pin" +CONF_MEMORY_LOCATION = "memory_location" +CONF_MEMORY_ADDRESS = "memory_address" + +micronova_ns = cg.esphome_ns.namespace("micronova") + +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.PollingComponent, uart.UARTDevice) + +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")) +) + + +def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address): + return cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional( + CONF_MEMORY_LOCATION, default=default_memory_location + ): cv.hex_int_range(), + cv.Optional( + CONF_MEMORY_ADDRESS, default=default_memory_address + ): cv.hex_int_range(), + } + ) + + +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) + enable_rx_pin = await cg.gpio_pin_expression(config[CONF_ENABLE_RX_PIN]) + cg.add(var.set_enable_rx_pin(enable_rx_pin)) diff --git a/esphome/components/micronova/button/__init__.py b/esphome/components/micronova/button/__init__.py new file mode 100644 index 000000000000..442f69c08bd3 --- /dev/null +++ b/esphome/components/micronova/button/__init__.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button + +from .. import ( + MicroNova, + MicroNovaFunctions, + CONF_MICRONOVA_ID, + CONF_MEMORY_LOCATION, + CONF_MEMORY_ADDRESS, + MICRONOVA_LISTENER_SCHEMA, + micronova_ns, +) + +MicroNovaButton = micronova_ns.class_("MicroNovaButton", button.Button, cg.Component) + +CONF_CUSTOM_BUTTON = "custom_button" +CONF_MEMORY_DATA = "memory_data" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional(CONF_CUSTOM_BUTTON): button.button_schema( + MicroNovaButton, + ) + .extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0xA0, default_memory_address=0x7D + ) + ) + .extend({cv.Required(CONF_MEMORY_DATA): cv.hex_int_range()}), + } +) + + +async def to_code(config): + mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) + + 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_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 new file mode 100644 index 000000000000..c1903fd87819 --- /dev/null +++ b/esphome/components/micronova/button/micronova_button.cpp @@ -0,0 +1,18 @@ +#include "micronova_button.h" + +namespace esphome { +namespace 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_->update(); +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/button/micronova_button.h b/esphome/components/micronova/button/micronova_button.h new file mode 100644 index 000000000000..77649051d693 --- /dev/null +++ b/esphome/components/micronova/button/micronova_button.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/components/micronova/micronova.h" +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace 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 set_memory_data(uint8_t f) { this->memory_data_ = f; } + uint8_t get_memory_data() { return this->memory_data_; } + + protected: + void press_action() override; +}; + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/micronova.cpp b/esphome/components/micronova/micronova.cpp new file mode 100644 index 000000000000..b96798ed1278 --- /dev/null +++ b/esphome/components/micronova/micronova.cpp @@ -0,0 +1,148 @@ +#include "micronova.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace micronova { + +void MicroNova::setup() { + if (this->enable_rx_pin_ != nullptr) { + this->enable_rx_pin_->setup(); + this->enable_rx_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->enable_rx_pin_->digital_write(false); + } + this->current_transmission_.request_transmission_time = millis(); + this->current_transmission_.memory_location = 0; + this->current_transmission_.memory_address = 0; + this->current_transmission_.reply_pending = false; + this->current_transmission_.initiating_listener = nullptr; +} + +void MicroNova::dump_config() { + ESP_LOGCONFIG(TAG, "MicroNova:"); + 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"); + for (auto &mv_listener : this->micronova_listeners_) { + mv_listener->set_needs_update(true); + } +} + +void MicroNova::loop() { + // Only read one sensor that needs update per loop + // If STOVE_REPLY_DELAY time has passed since last loop() + // check for a reply from the stove + if ((this->current_transmission_.reply_pending) && + (millis() - this->current_transmission_.request_transmission_time > STOVE_REPLY_DELAY)) { + int stove_reply_value = this->read_stove_reply(); + if (this->current_transmission_.initiating_listener != nullptr) { + this->current_transmission_.initiating_listener->process_value_from_stove(stove_reply_value); + this->current_transmission_.initiating_listener = nullptr; + } + this->current_transmission_.reply_pending = false; + return; + } else if (!this->current_transmission_.reply_pending) { + for (auto &mv_listener : this->micronova_listeners_) { + if (mv_listener->get_needs_update()) { + mv_listener->set_needs_update(false); + this->current_transmission_.initiating_listener = mv_listener; + mv_listener->request_value_from_stove(); + return; + } + } + } +} + +void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener) { + uint8_t write_data[2] = {0, 0}; + uint8_t trash_rx; + + if (this->reply_pending_mutex_.try_lock()) { + // clear rx buffer. + // Stove hickups may cause late replies in the rx + while (this->available()) { + this->read_byte(&trash_rx); + ESP_LOGW(TAG, "Reading excess byte 0x%02X", trash_rx); + } + + write_data[0] = location; + write_data[1] = address; + ESP_LOGV(TAG, "Request from stove [%02X,%02X]", write_data[0], write_data[1]); + + this->enable_rx_pin_->digital_write(true); + this->write_array(write_data, 2); + this->flush(); + this->enable_rx_pin_->digital_write(false); + + this->current_transmission_.request_transmission_time = millis(); + this->current_transmission_.memory_location = location; + this->current_transmission_.memory_address = address; + this->current_transmission_.reply_pending = true; + this->current_transmission_.initiating_listener = listener; + } else { + ESP_LOGE(TAG, "Reply is pending, skipping read request"); + } +} + +int MicroNova::read_stove_reply() { + uint8_t reply_data[2] = {0, 0}; + uint8_t checksum = 0; + + // assert enable_rx_pin is false + this->read_array(reply_data, 2); + + this->reply_pending_mutex_.unlock(); + ESP_LOGV(TAG, "Reply from stove [%02X,%02X]", reply_data[0], reply_data[1]); + + checksum = ((uint16_t) this->current_transmission_.memory_location + + (uint16_t) this->current_transmission_.memory_address + (uint16_t) reply_data[1]) & + 0xFF; + if (reply_data[0] != checksum) { + ESP_LOGE(TAG, "Checksum missmatch! From [0x%02X:0x%02X] received [0x%02X,0x%02X]. Expected 0x%02X, got 0x%02X", + this->current_transmission_.memory_location, this->current_transmission_.memory_address, reply_data[0], + reply_data[1], checksum, reply_data[0]); + return -1; + } + return ((int) reply_data[1]); +} + +void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) { + uint8_t write_data[4] = {0, 0, 0, 0}; + uint16_t checksum = 0; + + if (this->reply_pending_mutex_.try_lock()) { + write_data[0] = location; + write_data[1] = address; + write_data[2] = data; + + checksum = ((uint16_t) write_data[0] + (uint16_t) write_data[1] + (uint16_t) write_data[2]) & 0xFF; + write_data[3] = checksum; + + ESP_LOGV(TAG, "Write 4 bytes [%02X,%02X,%02X,%02X]", write_data[0], write_data[1], write_data[2], write_data[3]); + + this->enable_rx_pin_->digital_write(true); + this->write_array(write_data, 4); + this->flush(); + this->enable_rx_pin_->digital_write(false); + + this->current_transmission_.request_transmission_time = millis(); + this->current_transmission_.memory_location = location; + this->current_transmission_.memory_address = address; + this->current_transmission_.reply_pending = true; + this->current_transmission_.initiating_listener = nullptr; + } else { + ESP_LOGE(TAG, "Reply is pending, skipping write"); + } +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/micronova.h b/esphome/components/micronova/micronova.h new file mode 100644 index 000000000000..aebef277e5ae --- /dev/null +++ b/esphome/components/micronova/micronova.h @@ -0,0 +1,164 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/log.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace 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, + 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; + +////////////////////////////////////////////////////////////////////// +// Interface classes. +class MicroNovaBaseListener { + public: + MicroNovaBaseListener() {} + MicroNovaBaseListener(MicroNova *m) { this->micronova_ = m; } + virtual void dump_config(); + + 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_; } + + void set_memory_address(uint8_t a) { this->memory_address_ = a; } + uint8_t get_memory_address() { return this->memory_address_; } + + protected: + MicroNova *micronova_{nullptr}; + MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID; + uint8_t memory_location_ = 0; + uint8_t memory_address_ = 0; +}; + +class MicroNovaSensorListener : public MicroNovaBaseListener { + public: + MicroNovaSensorListener() {} + MicroNovaSensorListener(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; +}; + +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_; } + + protected: + 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) {} + + protected: + uint8_t memory_data_ = 0; +}; + +///////////////////////////////////////////////////////////////////// +// Main component class +class MicroNova : public PollingComponent, 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 request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener); + void write_address(uint8_t location, uint8_t address, uint8_t data); + int read_stove_reply(); + + 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 { + uint32_t request_transmission_time; + uint8_t memory_location; + uint8_t memory_address; + bool reply_pending; + MicroNovaSensorListener *initiating_listener; + }; + + Mutex reply_pending_mutex_; + MicroNovaSerialTransmission current_transmission_; + + std::vector micronova_listeners_{}; + MicroNovaSwitchListener *stove_switch_{nullptr}; +}; + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/number/__init__.py b/esphome/components/micronova/number/__init__.py new file mode 100644 index 000000000000..7124bf50d022 --- /dev/null +++ b/esphome/components/micronova/number/__init__.py @@ -0,0 +1,110 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + CONF_STEP, +) + +from .. import ( + MicroNova, + MicroNovaFunctions, + CONF_MICRONOVA_ID, + CONF_MEMORY_LOCATION, + CONF_MEMORY_ADDRESS, + MICRONOVA_LISTENER_SCHEMA, + micronova_ns, +) + +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, cg.Component) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional(CONF_THERMOSTAT_TEMPERATURE): number.number_schema( + MicroNovaNumber, + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + ) + .extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x20, default_memory_address=0x7D + ) + ) + .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( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x20, default_memory_address=0x7F + ) + ) + .extend( + {cv.Optional(CONF_MEMORY_WRITE_LOCATION, default=0xA0): cv.hex_int_range()} + ), + } +) + + +async def to_code(config): + mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) + + if thermostat_temperature_config := config.get(CONF_THERMOSTAT_TEMPERATURE): + numb = await number.new_number( + thermostat_temperature_config, + min_value=0, + max_value=40, + step=thermostat_temperature_config.get(CONF_STEP), + ) + 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( + power_level_config, + min_value=1, + max_value=5, + step=1, + ) + 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 new file mode 100644 index 000000000000..244eb7ee9f6b --- /dev/null +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -0,0 +1,45 @@ +#include "micronova_number.h" + +namespace esphome { +namespace 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; + } + this->publish_state(new_sensor_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; + } + this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number); + this->micronova_->update(); +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/number/micronova_number.h b/esphome/components/micronova/number/micronova_number.h new file mode 100644 index 000000000000..49c63582553a --- /dev/null +++ b/esphome/components/micronova/number/micronova_number.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/micronova/micronova.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace micronova { + +class MicroNovaNumber : public number::Number, public MicroNovaSensorListener { + public: + MicroNovaNumber() {} + MicroNovaNumber(MicroNova *m) : MicroNovaSensorListener(m) {} + void dump_config() override { LOG_NUMBER("", "Micronova number", this); } + void control(float value) override; + 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_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 micronova +} // namespace esphome diff --git a/esphome/components/micronova/sensor/__init__.py b/esphome/components/micronova/sensor/__init__.py new file mode 100644 index 000000000000..32e42f388822 --- /dev/null +++ b/esphome/components/micronova/sensor/__init__.py @@ -0,0 +1,172 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_REVOLUTIONS_PER_MINUTE, +) + +from .. import ( + MicroNova, + MicroNovaFunctions, + CONF_MICRONOVA_ID, + CONF_MEMORY_LOCATION, + CONF_MEMORY_ADDRESS, + MICRONOVA_LISTENER_SCHEMA, + micronova_ns, +) + +UNIT_BAR = "bar" + +MicroNovaSensor = micronova_ns.class_("MicroNovaSensor", sensor.Sensor, cg.Component) + +CONF_ROOM_TEMPERATURE = "room_temperature" +CONF_FUMES_TEMPERATURE = "fumes_temperature" +CONF_STOVE_POWER = "stove_power" +CONF_FAN_SPEED = "fan_speed" +CONF_WATER_TEMPERATURE = "water_temperature" +CONF_WATER_PRESSURE = "water_pressure" +CONF_MEMORY_ADDRESS_SENSOR = "memory_address_sensor" +CONF_FAN_RPM_OFFSET = "fan_rpm_offset" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional(CONF_ROOM_TEMPERATURE): sensor.sensor_schema( + MicroNovaSensor, + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x01 + ) + ), + cv.Optional(CONF_FUMES_TEMPERATURE): sensor.sensor_schema( + MicroNovaSensor, + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x5A + ) + ), + cv.Optional(CONF_STOVE_POWER): sensor.sensor_schema( + MicroNovaSensor, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x34 + ) + ), + cv.Optional(CONF_FAN_SPEED): sensor.sensor_schema( + MicroNovaSensor, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, + ) + .extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x37 + ) + ) + .extend( + {cv.Optional(CONF_FAN_RPM_OFFSET, default=0): cv.int_range(min=0, max=255)} + ), + cv.Optional(CONF_WATER_TEMPERATURE): sensor.sensor_schema( + MicroNovaSensor, + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x3B + ) + ), + cv.Optional(CONF_WATER_PRESSURE): sensor.sensor_schema( + MicroNovaSensor, + unit_of_measurement=UNIT_BAR, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x3C + ) + ), + cv.Optional(CONF_MEMORY_ADDRESS_SENSOR): sensor.sensor_schema( + MicroNovaSensor, + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x00 + ) + ), + } +) + + +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)) + + 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)) + 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.cpp b/esphome/components/micronova/sensor/micronova_sensor.cpp new file mode 100644 index 000000000000..3f0c0feaf862 --- /dev/null +++ b/esphome/components/micronova/sensor/micronova_sensor.cpp @@ -0,0 +1,35 @@ +#include "micronova_sensor.h" + +namespace esphome { +namespace micronova { + +void MicroNovaSensor::process_value_from_stove(int value_from_stove) { + if (value_from_stove == -1) { + this->publish_state(NAN); + 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; + } + this->publish_state(new_sensor_value); +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/sensor/micronova_sensor.h b/esphome/components/micronova/sensor/micronova_sensor.h new file mode 100644 index 000000000000..9d5ae96b879f --- /dev/null +++ b/esphome/components/micronova/sensor/micronova_sensor.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/components/micronova/micronova.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace micronova { + +class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener { + public: + MicroNovaSensor(MicroNova *m) : MicroNovaSensorListener(m) {} + void dump_config() override { LOG_SENSOR("", "Micronova sensor", this); } + + 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_fan_speed_offset(uint8_t f) { this->fan_speed_offset_ = f; } + uint8_t get_set_fan_speed_offset() { return this->fan_speed_offset_; } + + protected: + int fan_speed_offset_ = 0; +}; + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/switch/__init__.py b/esphome/components/micronova/switch/__init__.py new file mode 100644 index 000000000000..9846d46cc647 --- /dev/null +++ b/esphome/components/micronova/switch/__init__.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + ICON_POWER, +) + +from .. import ( + MicroNova, + MicroNovaFunctions, + CONF_MICRONOVA_ID, + CONF_MEMORY_LOCATION, + CONF_MEMORY_ADDRESS, + MICRONOVA_LISTENER_SCHEMA, + micronova_ns, +) + +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) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional(CONF_STOVE): switch.switch_schema( + MicroNovaSwitch, + icon=ICON_POWER, + ) + .extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x80, default_memory_address=0x21 + ) + ) + .extend( + { + cv.Optional(CONF_MEMORY_DATA_OFF, default=0x06): cv.hex_int_range(), + cv.Optional(CONF_MEMORY_DATA_ON, default=0x01): cv.hex_int_range(), + } + ), + } +) + + +async def to_code(config): + mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) + + 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])) + 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 new file mode 100644 index 000000000000..dcc96102db2f --- /dev/null +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -0,0 +1,33 @@ +#include "micronova_switch.h" + +namespace esphome { +namespace 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->micronova_->get_current_stove_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()); + } 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) { + 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()); + } + this->micronova_->update(); + break; + + default: + break; + } +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/switch/micronova_switch.h b/esphome/components/micronova/switch/micronova_switch.h new file mode 100644 index 000000000000..b0ca33b497f0 --- /dev/null +++ b/esphome/components/micronova/switch/micronova_switch.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/components/micronova/micronova.h" +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace 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 set_stove_state(bool v) override { this->publish_state(v); } + bool get_stove_state() override { return this->state; } + + 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; +}; + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/text_sensor/__init__.py b/esphome/components/micronova/text_sensor/__init__.py new file mode 100644 index 000000000000..dc27c4f32cec --- /dev/null +++ b/esphome/components/micronova/text_sensor/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor + +from .. import ( + MicroNova, + MicroNovaFunctions, + CONF_MICRONOVA_ID, + CONF_MEMORY_LOCATION, + CONF_MEMORY_ADDRESS, + MICRONOVA_LISTENER_SCHEMA, + micronova_ns, +) + +CONF_STOVE_STATE = "stove_state" + +MicroNovaTextSensor = micronova_ns.class_( + "MicroNovaTextSensor", text_sensor.TextSensor, cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova), + cv.Optional(CONF_STOVE_STATE): text_sensor.text_sensor_schema( + MicroNovaTextSensor + ).extend( + MICRONOVA_LISTENER_SCHEMA( + default_memory_location=0x00, default_memory_address=0x21 + ) + ), + } +) + + +async def to_code(config): + mv = await cg.get_variable(config[CONF_MICRONOVA_ID]) + + 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)) diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp new file mode 100644 index 000000000000..03b192ffd175 --- /dev/null +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp @@ -0,0 +1,31 @@ +#include "micronova_text_sensor.h" + +namespace esphome { +namespace micronova { + +void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) { + if (value_from_stove == -1) { + this->publish_state("unknown"); + return; + } + + 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; + } +} + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.h b/esphome/components/micronova/text_sensor/micronova_text_sensor.h new file mode 100644 index 000000000000..b4e5de9bb3ad --- /dev/null +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/components/micronova/micronova.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace micronova { + +class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener { + public: + MicroNovaTextSensor(MicroNova *m) : MicroNovaSensorListener(m) {} + void dump_config() override { LOG_TEXT_SENSOR("", "Micronova text sensor", this); } + 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; +}; + +} // namespace micronova +} // namespace esphome diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5313f07f727c..29c0ec5df2be 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -23,7 +23,7 @@ class DataTrigger : public Trigger &> { } }; -template class IsCapturingActon : public Condition, public Parented { +template class IsCapturingCondition : public Condition, public Parented { public: bool check(Ts... x) override { return this->parent_->is_running(); } }; diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 80b14615767d..83540a061a0f 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -11,6 +11,7 @@ CONF_CUSTOM_PRESETS, CONF_ID, CONF_NUM_ATTEMPTS, + CONF_OUTDOOR_TEMPERATURE, CONF_PERIOD, CONF_SUPPORTED_MODES, CONF_SUPPORTED_PRESETS, @@ -35,9 +36,8 @@ ) CODEOWNERS = ["@dudanov"] -DEPENDENCIES = ["climate", "uart", "wifi"] +DEPENDENCIES = ["climate", "uart"] AUTO_LOAD = ["sensor"] -CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_POWER_USAGE = "power_usage" CONF_HUMIDITY_SETPOINT = "humidity_setpoint" midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac") diff --git a/esphome/components/midea_ir/climate.py b/esphome/components/midea_ir/climate.py index 140e4ee4e0d5..8fea6b192b23 100644 --- a/esphome/components/midea_ir/climate.py +++ b/esphome/components/midea_ir/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate_ir -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT AUTO_LOAD = ["climate_ir", "coolix"] CODEOWNERS = ["@dudanov"] @@ -9,7 +9,6 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir") MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR) -CONF_USE_FAHRENHEIT = "use_fahrenheit" CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { diff --git a/esphome/components/mitsubishi/climate.py b/esphome/components/mitsubishi/climate.py index 0df17a5d16b8..5e865c636f72 100644 --- a/esphome/components/mitsubishi/climate.py +++ b/esphome/components/mitsubishi/climate.py @@ -9,9 +9,53 @@ mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi") MitsubishiClimate = mitsubishi_ns.class_("MitsubishiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend( +CONF_SET_FAN_MODE = "set_fan_mode" +SetFanMode = mitsubishi_ns.enum("SetFanMode") +SETFANMODE = { + "quiet_4levels": SetFanMode.MITSUBISHI_FAN_Q4L, + # "5levels": SetFanMode.MITSUBISHI_FAN_5L, + "4levels": SetFanMode.MITSUBISHI_FAN_4L, + "3levels": SetFanMode.MITSUBISHI_FAN_3L, +} + +CONF_SUPPORTS_DRY = "supports_dry" +CONF_SUPPORTS_FAN_ONLY = "supports_fan_only" + +CONF_HORIZONTAL_DEFAULT = "horizontal_default" +HorizontalDirections = mitsubishi_ns.enum("HorizontalDirections") +HORIZONTAL_DIRECTIONS = { + "left": HorizontalDirections.HORIZONTAL_DIRECTION_LEFT, + "middle-left": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_LEFT, + "middle": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE, + "middle-right": HorizontalDirections.HORIZONTAL_DIRECTION_MIDDLE_RIGHT, + "right": HorizontalDirections.HORIZONTAL_DIRECTION_RIGHT, + "split": HorizontalDirections.HORIZONTAL_DIRECTION_SPLIT, +} + +CONF_VERTICAL_DEFAULT = "vertical_default" +VerticalDirections = mitsubishi_ns.enum("VerticalDirections") +VERTICAL_DIRECTIONS = { + "auto": VerticalDirections.VERTICAL_DIRECTION_AUTO, + "up": VerticalDirections.VERTICAL_DIRECTION_UP, + "middle-up": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_UP, + "middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE, + "middle-down": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_DOWN, + "down": VerticalDirections.VERTICAL_DIRECTION_DOWN, +} + + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MitsubishiClimate), + cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE), + cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean, + cv.Optional(CONF_SUPPORTS_FAN_ONLY, default=False): cv.boolean, + cv.Optional(CONF_HORIZONTAL_DEFAULT, default="middle"): cv.enum( + HORIZONTAL_DIRECTIONS + ), + cv.Optional(CONF_VERTICAL_DEFAULT, default="middle"): cv.enum( + VERTICAL_DIRECTIONS + ), } ) @@ -19,3 +63,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await climate_ir.register_climate_ir(var, config) + + cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE])) + cg.add(var.set_supports_dry(config[CONF_SUPPORTS_DRY])) + cg.add(var.set_supports_fan_only(config[CONF_SUPPORTS_FAN_ONLY])) + cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) + cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 99ca6d1cc5ef..081c24a050cb 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -8,12 +8,31 @@ static const char *const TAG = "mitsubishi.climate"; const uint32_t MITSUBISHI_OFF = 0x00; -const uint8_t MITSUBISHI_COOL = 0x18; -const uint8_t MITSUBISHI_DRY = 0x10; -const uint8_t MITSUBISHI_AUTO = 0x20; -const uint8_t MITSUBISHI_HEAT = 0x08; +const uint8_t MITSUBISHI_MODE_AUTO = 0x20; +const uint8_t MITSUBISHI_MODE_COOL = 0x18; +const uint8_t MITSUBISHI_MODE_DRY = 0x10; +const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38; +const uint8_t MITSUBISHI_MODE_HEAT = 0x08; + +const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00; +const uint8_t MITSUBISHI_MODE_A_DRY = 0x02; +const uint8_t MITSUBISHI_MODE_A_COOL = 0x06; +const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06; + +const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0; + const uint8_t MITSUBISHI_FAN_AUTO = 0x00; +const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38; + +// const uint8_t MITSUBISHI_AUTO = 0X80; +const uint8_t MITSUBISHI_OTHERWISE = 0X40; +const uint8_t MITSUBISHI_POWERFUL = 0x08; + +// Optional presets used to enable some model features +const uint8_t MITSUBISHI_ECONOCOOL = 0x20; +const uint8_t MITSUBISHI_NIGHTMODE = 0xC1; + // Pulse parameters in usec const uint16_t MITSUBISHI_BIT_MARK = 430; const uint16_t MITSUBISHI_ONE_SPACE = 1250; @@ -22,19 +41,97 @@ const uint16_t MITSUBISHI_HEADER_MARK = 3500; const uint16_t MITSUBISHI_HEADER_SPACE = 1700; const uint16_t MITSUBISHI_MIN_GAP = 17500; +// Marker bytes +const uint8_t MITSUBISHI_BYTE00 = 0X23; +const uint8_t MITSUBISHI_BYTE01 = 0XCB; +const uint8_t MITSUBISHI_BYTE02 = 0X26; +const uint8_t MITSUBISHI_BYTE03 = 0X01; +const uint8_t MITSUBISHI_BYTE04 = 0X00; +const uint8_t MITSUBISHI_BYTE13 = 0X00; +const uint8_t MITSUBISHI_BYTE16 = 0X00; + +climate::ClimateTraits MitsubishiClimate::traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_action(false); + traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN); + traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX); + traits.set_visual_temperature_step(1.0f); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF}); + + if (this->supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (this->supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (this->supports_cool_ && this->supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + + if (this->supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (this->supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + + // Default to only 3 levels in ESPHome even if most unit supports 4. The 3rd level is not used. + traits.set_supported_fan_modes( + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}); + if (this->fan_mode_ == MITSUBISHI_FAN_Q4L) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET); + if (/*this->fan_mode_ == MITSUBISHI_FAN_5L ||*/ this->fan_mode_ >= MITSUBISHI_FAN_4L) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); // Shouldn't be used for this but it helps + + traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL}); + + traits.set_supported_presets({climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, + climate::CLIMATE_PRESET_BOOST, climate::CLIMATE_PRESET_SLEEP}); + + return traits; +} + void MitsubishiClimate::transmit_state() { - uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x30, - 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00 + // Byte 5: On=0x20, Off: 0x00 + // Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly) + // Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN + // Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C + // Byte 8: MODE_A & Wide Vane (if present) + // MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above) + // Wide Vane bits 4,5,6,7 (Middle = 0x30) + // Byte 9: FAN/Vertical Vane/Switch To Auto + // FAN (Speed) bits 0,1,2 + // Vertical Vane bits 3,4,5 (Auto = 0x00) + // Switch To Auto bits 6,7 + // Byte 10: CLOCK Current time as configured on remote (0x00=Not used) + // Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting) + // Byte 12: START CLOCK Start time of HVAC (0x00 for no setting) + // Byte 13: Constant 0x00 + // Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00 + // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00 + // Byte 16: Constant 0x00 + // Byte 17: Checksum: SUM[Byte0...Byte16] + uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; switch (this->mode) { - case climate::CLIMATE_MODE_COOL: - remote_state[6] = MITSUBISHI_COOL; - break; case climate::CLIMATE_MODE_HEAT: - remote_state[6] = MITSUBISHI_HEAT; + remote_state[6] = MITSUBISHI_MODE_HEAT; + remote_state[8] = MITSUBISHI_MODE_A_HEAT; + break; + case climate::CLIMATE_MODE_DRY: + remote_state[6] = MITSUBISHI_MODE_DRY; + remote_state[8] = MITSUBISHI_MODE_A_DRY; + break; + case climate::CLIMATE_MODE_COOL: + remote_state[6] = MITSUBISHI_MODE_COOL; + remote_state[8] = MITSUBISHI_MODE_A_COOL; break; case climate::CLIMATE_MODE_HEAT_COOL: - remote_state[6] = MITSUBISHI_AUTO; + remote_state[6] = MITSUBISHI_MODE_AUTO; + remote_state[8] = MITSUBISHI_MODE_A_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + remote_state[6] = MITSUBISHI_MODE_FAN_ONLY; + remote_state[8] = MITSUBISHI_MODE_A_AUTO; break; case climate::CLIMATE_MODE_OFF: default: @@ -42,17 +139,111 @@ void MitsubishiClimate::transmit_state() { break; } - remote_state[7] = (uint8_t) roundf(clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - - MITSUBISHI_TEMP_MIN); + // Temperature + if (this->mode == climate::CLIMATE_MODE_DRY) { + remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected + } else { + remote_state[7] = (uint8_t) roundf( + clamp(this->target_temperature, MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) - MITSUBISHI_TEMP_MIN); + } + + // Wide Vane + switch (this->swing_mode) { + case climate::CLIMATE_SWING_HORIZONTAL: + case climate::CLIMATE_SWING_BOTH: + remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing + break; + case climate::CLIMATE_SWING_OFF: + default: + remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position + break; + } + + ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_); + + // Fan Speed & Vertical Vane + // Map of Climate fan mode to this device expected value + // For 3Level: Low = 1, Medium = 2, High = 3 + // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5 + + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + remote_state[9] = 1; + break; + case climate::CLIMATE_FAN_MEDIUM: + if (this->fan_mode_ == MITSUBISHI_FAN_3L) { + remote_state[9] = 2; + } else { + remote_state[9] = 3; + } + break; + case climate::CLIMATE_FAN_HIGH: + if (this->fan_mode_ == MITSUBISHI_FAN_3L) { + remote_state[9] = 3; + } else { + remote_state[9] = 4; + } + break; + case climate::CLIMATE_FAN_MIDDLE: + remote_state[9] = 2; + break; + case climate::CLIMATE_FAN_QUIET: + remote_state[9] = 5; + break; + default: + remote_state[9] = MITSUBISHI_FAN_AUTO; + break; + } + + ESP_LOGD(TAG, "fan: %02x state: %02x", this->fan_mode.value(), remote_state[9]); + + // Vertical Vane + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + case climate::CLIMATE_SWING_BOTH: + remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing + break; + case climate::CLIMATE_SWING_OFF: + default: + remote_state[9] = remote_state[9] | this->default_vertical_direction_ | + MITSUBISHI_OTHERWISE; // Off--> vertical default position + break; + } + + ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_); - ESP_LOGV(TAG, "Sending Mitsubishi target temp: %.1f state: %02X mode: %02X temp: %02X", this->target_temperature, - remote_state[5], remote_state[6], remote_state[7]); + // Special modes + switch (this->preset.value()) { + case climate::CLIMATE_PRESET_ECO: + remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE; + remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL; + remote_state[14] = MITSUBISHI_ECONOCOOL; + break; + case climate::CLIMATE_PRESET_SLEEP: + remote_state[9] = MITSUBISHI_FAN_AUTO; + remote_state[14] = MITSUBISHI_NIGHTMODE; + break; + case climate::CLIMATE_PRESET_BOOST: + remote_state[6] |= MITSUBISHI_OTHERWISE; + remote_state[15] = MITSUBISHI_POWERFUL; + break; + case climate::CLIMATE_PRESET_NONE: + default: + break; + } // Checksum for (int i = 0; i < 17; i++) { remote_state[17] += remote_state[i]; } + ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X", + remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], + remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], + remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]); + auto transmit = this->transmitter_->transmit(); auto *data = transmit.get_data(); @@ -81,5 +272,119 @@ void MitsubishiClimate::transmit_state() { transmit.perform(); } +bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; } + +bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t state_frame[18] = {}; + + if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) { + ESP_LOGV(TAG, "Header fail"); + return false; + } + + for (uint8_t pos = 0; pos < 18; pos++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit); + return false; + } + } + state_frame[pos] = byte; + + // Check Header && Footer + if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) || + (pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) || + (pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) || + (pos == 16 && byte != MITSUBISHI_BYTE16)) { + ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value"); + return false; + } + } + + // On/Off and Mode + if (state_frame[5] == MITSUBISHI_OFF) { + this->mode = climate::CLIMATE_MODE_OFF; + } else { + switch (state_frame[6]) { + case MITSUBISHI_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case MITSUBISHI_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case MITSUBISHI_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case MITSUBISHI_MODE_FAN_ONLY: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + case MITSUBISHI_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + } + } + + // Temp + this->target_temperature = state_frame[7] + MITSUBISHI_TEMP_MIN; + + // Fan + uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed) + // Map of Climate fan mode to this device expected value + // For 3Level: Low = 1, Medium = 2, High = 3 + // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4 + // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5 + climate::ClimateFanMode modes_mapping[8] = { + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_MEDIUM : climate::CLIMATE_FAN_MIDDLE, + this->fan_mode_ == MITSUBISHI_FAN_3L ? climate::CLIMATE_FAN_HIGH : climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + climate::CLIMATE_FAN_QUIET, + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_AUTO}; + this->fan_mode = modes_mapping[fan]; + + // Wide Vane + uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7 + switch (wide_vane) { + case MITSUBISHI_WIDE_VANE_SWING: + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + break; + default: + this->swing_mode = climate::CLIMATE_SWING_OFF; + break; + } + + // Vertical Vane + uint8_t vertical_vane = state_frame[9] & 0x38; // Bits 3,4,5 + switch (vertical_vane) { + case MITSUBISHI_VERTICAL_VANE_SWING: + if (this->swing_mode == climate::CLIMATE_SWING_HORIZONTAL) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } + break; + } + + switch (state_frame[14]) { + case MITSUBISHI_ECONOCOOL: + this->preset = climate::CLIMATE_PRESET_ECO; + break; + case MITSUBISHI_NIGHTMODE: + this->preset = climate::CLIMATE_PRESET_SLEEP; + break; + } + + ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str()); + + this->publish_state(); + return true; +} + } // namespace mitsubishi } // namespace esphome diff --git a/esphome/components/mitsubishi/mitsubishi.h b/esphome/components/mitsubishi/mitsubishi.h index e6bd7b8ebe13..cfe12428da40 100644 --- a/esphome/components/mitsubishi/mitsubishi.h +++ b/esphome/components/mitsubishi/mitsubishi.h @@ -2,6 +2,8 @@ #include "esphome/components/climate_ir/climate_ir.h" +#include + namespace esphome { namespace mitsubishi { @@ -9,13 +11,72 @@ namespace mitsubishi { const uint8_t MITSUBISHI_TEMP_MIN = 16; // Celsius const uint8_t MITSUBISHI_TEMP_MAX = 31; // Celsius +// Fan mode +enum SetFanMode { + MITSUBISHI_FAN_3L = 0, // 3 levels + auto + MITSUBISHI_FAN_4L, // 4 levels + auto + MITSUBISHI_FAN_Q4L, // Quiet + 4 levels + auto + // MITSUBISHI_FAN_5L, // 5 levels + auto +}; + +// Enum to represent horizontal directios +enum HorizontalDirection { + HORIZONTAL_DIRECTION_LEFT = 0x10, + HORIZONTAL_DIRECTION_MIDDLE_LEFT = 0x20, + HORIZONTAL_DIRECTION_MIDDLE = 0x30, + HORIZONTAL_DIRECTION_MIDDLE_RIGHT = 0x40, + HORIZONTAL_DIRECTION_RIGHT = 0x50, + HORIZONTAL_DIRECTION_SPLIT = 0x80, +}; + +// Enum to represent vertical directions +enum VerticalDirection { + VERTICAL_DIRECTION_AUTO = 0x00, + VERTICAL_DIRECTION_UP = 0x08, + VERTICAL_DIRECTION_MIDDLE_UP = 0x10, + VERTICAL_DIRECTION_MIDDLE = 0x18, + VERTICAL_DIRECTION_MIDDLE_DOWN = 0x20, + VERTICAL_DIRECTION_DOWN = 0x28, +}; + class MitsubishiClimate : public climate_ir::ClimateIR { public: - MitsubishiClimate() : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX) {} + MitsubishiClimate() + : climate_ir::ClimateIR(MITSUBISHI_TEMP_MIN, MITSUBISHI_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MIDDLE, + climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL}, + {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_SLEEP}) {} + + void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } + void set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } + void set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; } + void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } + + void set_fan_mode(SetFanMode fan_mode) { this->fan_mode_ = fan_mode; } + + void set_horizontal_default(HorizontalDirection horizontal_direction) { + this->default_horizontal_direction_ = horizontal_direction; + } + void set_vertical_default(VerticalDirection vertical_direction) { + this->default_vertical_direction_ = vertical_direction; + } protected: - /// Transmit via IR the state of this climate controller. + // Transmit via IR the state of this climate controller. void transmit_state() override; + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(const uint8_t frame[]); + + SetFanMode fan_mode_; + + HorizontalDirection default_horizontal_direction_; + VerticalDirection default_vertical_direction_; + + climate::ClimateTraits traits() override; }; } // namespace mitsubishi diff --git a/esphome/components/mmc5603/sensor.py b/esphome/components/mmc5603/sensor.py index 348a0e7dcc9b..cf161324702c 100644 --- a/esphome/components/mmc5603/sensor.py +++ b/esphome/components/mmc5603/sensor.py @@ -3,6 +3,10 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ADDRESS, + CONF_FIELD_STRENGTH_X, + CONF_FIELD_STRENGTH_Y, + CONF_FIELD_STRENGTH_Z, + CONF_HEADING, CONF_ID, ICON_MAGNET, STATE_CLASS_MEASUREMENT, @@ -16,11 +20,6 @@ mmc5603_ns = cg.esphome_ns.namespace("mmc5603") -CONF_FIELD_STRENGTH_X = "field_strength_x" -CONF_FIELD_STRENGTH_Y = "field_strength_y" -CONF_FIELD_STRENGTH_Z = "field_strength_z" -CONF_HEADING = "heading" - MMC5603Component = mmc5603_ns.class_( "MMC5603Component", cg.PollingComponent, i2c.I2CDevice ) diff --git a/esphome/components/mmc5983/__init__.py b/esphome/components/mmc5983/__init__.py new file mode 100644 index 000000000000..c8db8c4300d0 --- /dev/null +++ b/esphome/components/mmc5983/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@agoode"] diff --git a/esphome/components/mmc5983/mmc5983.cpp b/esphome/components/mmc5983/mmc5983.cpp new file mode 100644 index 000000000000..5b045ae38bdc --- /dev/null +++ b/esphome/components/mmc5983/mmc5983.cpp @@ -0,0 +1,141 @@ +// See https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/tree/main +// for datasheets and an Arduino implementation. + +#include "mmc5983.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mmc5983 { + +static const char *const TAG = "mmc5983"; + +namespace { +constexpr uint8_t IC0_ADDR = 0x09; +constexpr uint8_t IC1_ADDR = 0x0a; +constexpr uint8_t IC2_ADDR = 0x0b; +constexpr uint8_t IC3_ADDR = 0x0c; +constexpr uint8_t PRODUCT_ID_ADDR = 0x2f; + +float convert_data_to_millitesla(uint8_t data_17_10, uint8_t data_9_2, uint8_t data_1_0) { + int32_t counts = (data_17_10 << 10) | (data_9_2 << 2) | data_1_0; + counts -= 131072; // "Null Field Output" from datasheet. + + // Sensitivity is 16384 counts/gauss, which is 163840 counts/mT. + return counts / 163840.0f; +} +} // namespace + +void MMC5983Component::update() { + // Schedule a SET/RESET. This will recalibrate the sensor. + // We are supposed to be able to set this once, and have it automatically continue every reading, but + // this does not appear to work in continuous mode, even with En_prd_set turned on in Internal Control 2. + // Bit 5 = Auto_SR_en (automatic SET/RESET enable). + const uint8_t ic0_value = 0b10000; + i2c::ErrorCode err = this->write_register(IC0_ADDR, &ic0_value, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGW(TAG, "Writing Internal Control 0 failed with i2c error %d", err); + this->status_set_warning(); + } + + // Read out the data, 7 bytes starting from 0x00. + uint8_t data[7]; + err = this->read_register(0x00, data, sizeof(data)); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGW(TAG, "Reading data failed with i2c error %d", err); + this->status_set_warning(); + return; + } + + // Unpack the data and publish to sensors. + // Data is in this format: + // data[0]: Xout[17:10] + // data[1]: Xout[9:2] + // data[2]: Yout[17:10] + // data[3]: Yout[9:2] + // data[4]: Zout[17:10] + // data[5]: Zout[9:2] + // data[6]: { Xout[1], Xout[0], Yout[1], Yout[0], Zout[1], Zout[0], 0, 0 } + if (this->x_sensor_) { + this->x_sensor_->publish_state(convert_data_to_millitesla(data[0], data[1], (data[6] & 0b11000000) >> 6)); + } + if (this->y_sensor_) { + this->y_sensor_->publish_state(convert_data_to_millitesla(data[2], data[3], (data[6] & 0b00110000) >> 4)); + } + if (this->z_sensor_) { + this->z_sensor_->publish_state(convert_data_to_millitesla(data[4], data[5], (data[6] & 0b00001100) >> 2)); + } +} + +void MMC5983Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MMC5983..."); + + // Verify product id. + const uint8_t mmc5983_product_id = 0x30; + uint8_t id; + i2c::ErrorCode err = this->read_register(PRODUCT_ID_ADDR, &id, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Reading product id failed with i2c error %d", err); + this->mark_failed(); + return; + } + if (id != mmc5983_product_id) { + ESP_LOGE(TAG, "Product id 0x%02x does not match expected value 0x%02x", id, mmc5983_product_id); + this->mark_failed(); + return; + } + + // Initialize Internal Control registers to 0. + // Internal Control 0. + const uint8_t zero = 0; + err = this->write_register(IC0_ADDR, &zero, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Initializing Internal Control 0 failed with i2c error %d", err); + this->mark_failed(); + return; + } + // Internal Control 1. + err = this->write_register(IC1_ADDR, &zero, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Initializing Internal Control 1 failed with i2c error %d", err); + this->mark_failed(); + return; + } + // Internal Control 2. + err = this->write_register(IC2_ADDR, &zero, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Initializing Internal Control 2 failed with i2c error %d", err); + this->mark_failed(); + return; + } + // Internal Control 3. + err = this->write_register(IC3_ADDR, &zero, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Initializing Internal Control 3 failed with i2c error %d", err); + this->mark_failed(); + return; + } + + // Enable continuous mode at 100 Hz, using Internal Control 2. + // Bit 3 = Cmm_en (continuous mode enable). + // Bit [2:0] = Cm_freq. 0b101 = 100 Hz, the fastest reading speed at Bandwidth=100 Hz. + const uint8_t ic2_value = 0b00001101; + err = this->write_register(IC2_ADDR, &ic2_value, 1); + if (err != i2c::ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Writing Internal Control 2 failed with i2c error %d", err); + this->mark_failed(); + return; + } +} + +void MMC5983Component::dump_config() { + ESP_LOGD(TAG, "MMC5983:"); + LOG_I2C_DEVICE(this); + LOG_SENSOR(" ", "X", this->x_sensor_); + LOG_SENSOR(" ", "Y", this->y_sensor_); + LOG_SENSOR(" ", "Z", this->z_sensor_); +} + +float MMC5983Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace mmc5983 +} // namespace esphome diff --git a/esphome/components/mmc5983/mmc5983.h b/esphome/components/mmc5983/mmc5983.h new file mode 100644 index 000000000000..d4254189040f --- /dev/null +++ b/esphome/components/mmc5983/mmc5983.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mmc5983 { + +class MMC5983Component : public PollingComponent, public i2c::I2CDevice { + public: + void update() override; + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; } + void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } + void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } + + protected: + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; +}; + +} // namespace mmc5983 +} // namespace esphome diff --git a/esphome/components/mmc5983/sensor.py b/esphome/components/mmc5983/sensor.py new file mode 100644 index 000000000000..e3f4209cf975 --- /dev/null +++ b/esphome/components/mmc5983/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_FIELD_STRENGTH_X, + CONF_FIELD_STRENGTH_Y, + CONF_FIELD_STRENGTH_Z, + CONF_ID, + ICON_MAGNET, + STATE_CLASS_MEASUREMENT, + UNIT_MICROTESLA, +) + +DEPENDENCIES = ["i2c"] + +mmc5983_ns = cg.esphome_ns.namespace("mmc5983") +MMC5983Component = mmc5983_ns.class_( + "MMC5983Component", cg.PollingComponent, i2c.I2CDevice +) + +field_strength_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=4, + state_class=STATE_CLASS_MEASUREMENT, +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MMC5983Component), + cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x30)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if x_config := config.get(CONF_FIELD_STRENGTH_X): + sens = await sensor.new_sensor(x_config) + cg.add(var.set_x_sensor(sens)) + if y_config := config.get(CONF_FIELD_STRENGTH_Y): + sens = await sensor.new_sensor(y_config) + cg.add(var.set_y_sensor(sens)) + if z_config := config.get(CONF_FIELD_STRENGTH_Z): + sens = await sensor.new_sensor(z_config) + cg.add(var.set_z_sensor(sens)) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 46bb2c42331a..8703771c3ac5 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -8,6 +8,7 @@ CONF_BITMASK, CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, + CONF_OFFLINE_SKIP_UPDATES, CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, @@ -104,6 +105,7 @@ cv.Optional( CONF_COMMAND_THROTTLE, default="0ms" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int, } ) .extend(cv.polling_component_schema("60s")) @@ -206,8 +208,9 @@ async def add_modbus_base_properties( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) + var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) + cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) await register_modbus_device(var, config) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index baf72efb9431..1a23640e171a 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -1,6 +1,7 @@ CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" +CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates" CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 79c13e3f6843..7565dc5e1b7f 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -26,6 +26,17 @@ bool ModbusController::send_next_command_() { // remove from queue if command was sent too often if (command->send_countdown < 1) { + if (!this->module_offline_) { + ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_); + + if (this->offline_skip_updates_ > 0) { + // Update skip_updates_counter to stop flooding channel with timeouts + for (auto &r : this->register_ranges_) { + r.skip_updates_counter = this->offline_skip_updates_; + } + } + } + this->module_offline_ = true; ESP_LOGD( TAG, "Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue", @@ -49,6 +60,18 @@ bool ModbusController::send_next_command_() { void ModbusController::on_modbus_data(const std::vector &data) { auto ¤t_command = this->command_queue_.front(); if (current_command != nullptr) { + if (this->module_offline_) { + ESP_LOGW(TAG, "Modbus device=%d back online", this->address_); + + if (this->offline_skip_updates_ > 0) { + // Restore skip_updates_counter to restore commands updates + for (auto &r : this->register_ranges_) { + r.skip_updates_counter = 0; + } + } + } + this->module_offline_ = false; + // Move the commandItem to the response queue current_command->payload = data; this->incoming_queue_.push(std::move(current_command)); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index ccb0edf9c663..a38937552392 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -409,7 +409,6 @@ class ModbusCommandItem { class ModbusController : public PollingComponent, public modbus::ModbusDevice { public: - ModbusController(uint16_t throttle = 0) : command_throttle_(throttle){}; void dump_config() override; void loop() override; void setup() override; @@ -431,6 +430,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { const std::vector &data); /// called by esphome generated code to set the command_throttle period void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; } + /// called by esphome generated code to set the offline_skip_updates + void set_offline_skip_updates(uint16_t offline_skip_updates) { this->offline_skip_updates_ = offline_skip_updates; } + /// get the number of queued modbus commands (should be mostly empty) + size_t get_command_queue_length() { return command_queue_.size(); } + /// get if the module is offline, didn't respond the last command + bool get_module_offline() { return module_offline_; } protected: /// parse sensormap_ and create range of sequential addresses @@ -443,8 +448,6 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { void process_modbus_data_(const ModbusCommandItem *response); /// send the next modbus command from the send queue bool send_next_command_(); - /// get the number of queued modbus commands (should be mostly empty) - size_t get_command_queue_length_() { return command_queue_.size(); } /// dump the parsed sensormap for diagnostics void dump_sensors_(); /// Collection of all sensors for this component @@ -459,6 +462,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { uint32_t last_command_timestamp_; /// min time in ms between sending modbus commands uint16_t command_throttle_; + /// if module didn't respond the last command + bool module_offline_; + /// how many updates to skip if module is offline + uint16_t offline_skip_updates_; }; /** Convert vector response payload to float. diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index c90890c88f9f..359c6e2f50a5 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -13,10 +13,10 @@ void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Te void ModbusTextSensor::parse_and_publish(const std::vector &data) { std::ostringstream output; - uint8_t max_items = this->response_bytes; + uint8_t items_left = this->response_bytes; uint8_t index = this->offset; char buffer[4]; - while ((max_items != 0) && index < data.size()) { + while ((items_left > 0) && index < data.size()) { uint8_t b = data[index]; switch (this->encode_) { case RawEncoding::HEXBYTES: @@ -33,7 +33,7 @@ void ModbusTextSensor::parse_and_publish(const std::vector &data) { output << (char) b; break; } - + items_left--; index++; } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 02d77a6b33be..f79e40bb4e58 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -54,7 +54,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (static_cast(manu_data.data[0]) != STANDARD_BOTTOM_UP && static_cast(manu_data.data[0]) != LIPPERT_BOTTOM_UP && - static_cast(manu_data.data[0]) != PLUS_BOTTOM_UP) { + static_cast(manu_data.data[0]) != PLUS_BOTTOM_UP && + static_cast(manu_data.data[0]) != PRO_UNIVERSAL) { ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]); return false; } @@ -69,7 +70,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if ((this->distance_ != nullptr) || (this->level_ != nullptr)) { uint32_t distance_value = this->parse_distance_(manu_data.data); SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data); - ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%dmm)", quality_value, distance_value); + ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%" PRId32 "mm)", quality_value, distance_value); if (quality_value < QUALITY_HIGH) { ESP_LOGW(TAG, "Poor read quality."); } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h index 8b126a204cde..8b4d47e4c65e 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.h +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -1,11 +1,12 @@ #pragma once +#include +#include + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include - #ifdef USE_ESP32 namespace esphome { @@ -16,7 +17,9 @@ enum SensorType { TOP_DOWN_AIR_ABOVE = 0x04, BOTTOM_UP_WATER = 0x05, LIPPERT_BOTTOM_UP = 0x06, - PLUS_BOTTOM_UP = 0x08 + PLUS_BOTTOM_UP = 0x08, + PRO_UNIVERSAL = 0xC // Pro Check Universal + // all other values are reserved }; diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 67e749c68b3f..6685a23c4119 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -16,8 +16,8 @@ static const uint16_t MANUFACTURER_ID = 0x000D; 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: %imm", this->empty_mm_); - ESP_LOGCONFIG(TAG, " Tank distance full: %imm", this->full_mm_); + ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_); + ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_); LOG_SENSOR(" ", "Level", this->level_); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -71,7 +71,8 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto *mopeka_data = (const mopeka_std_package *) manu_data.data.data(); const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; - if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL) { + if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL && + static_cast(hardware_id) != ETRAILER) { 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 e4d81afbd767..2a1d9d2dfc68 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.h +++ b/esphome/components/mopeka_std_check/mopeka_std_check.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" @@ -14,6 +15,7 @@ namespace mopeka_std_check { enum SensorType { STANDARD = 0x02, XL = 0x03, + ETRAILER = 0x46, }; // 4 values in one struct so it aligns to 8 byte. One `mopeka_std_values` is 40 bit long. diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 102c070eb6b0..31cbb2cf97bf 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -10,6 +10,8 @@ CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CERTIFICATE_AUTHORITY, + CONF_CLIENT_CERTIFICATE, + CONF_CLIENT_CERTIFICATE_KEY, CONF_CLIENT_ID, CONF_COMMAND_TOPIC, CONF_COMMAND_RETAIN, @@ -43,13 +45,21 @@ CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_BK72XX, ) from esphome.core import coroutine_with_priority, CORE from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["network"] -AUTO_LOAD = ["json"] + +def AUTO_LOAD(): + if CORE.is_esp8266 or CORE.is_libretiny: + return ["async_tcp", "json"] + return ["json"] + CONF_IDF_SEND_ASYNC = "idf_send_async" CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" @@ -108,9 +118,15 @@ def validate_message_just_topic(value): MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) +MQTTDateComponent = mqtt_ns.class_("MQTTDateComponent", MQTTComponent) +MQTTTimeComponent = mqtt_ns.class_("MQTTTimeComponent", MQTTComponent) +MQTTDateTimeComponent = mqtt_ns.class_("MQTTDateTimeComponent", MQTTComponent) +MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent) +MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent) +MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { @@ -129,33 +145,47 @@ def validate_config(value): # Populate default fields out = value.copy() topic_prefix = value[CONF_TOPIC_PREFIX] + # If the topic prefix is not null and these messages are not configured, then set them to the default + # If the topic prefix is null and these messages are not configured, then set them to null if CONF_BIRTH_MESSAGE not in value: - out[CONF_BIRTH_MESSAGE] = { - CONF_TOPIC: f"{topic_prefix}/status", - CONF_PAYLOAD: "online", - CONF_QOS: 0, - CONF_RETAIN: True, - } + if topic_prefix != "": + out[CONF_BIRTH_MESSAGE] = { + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "online", + CONF_QOS: 0, + CONF_RETAIN: True, + } + else: + out[CONF_BIRTH_MESSAGE] = {} if CONF_WILL_MESSAGE not in value: - out[CONF_WILL_MESSAGE] = { - CONF_TOPIC: f"{topic_prefix}/status", - CONF_PAYLOAD: "offline", - CONF_QOS: 0, - CONF_RETAIN: True, - } + if topic_prefix != "": + out[CONF_WILL_MESSAGE] = { + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "offline", + CONF_QOS: 0, + CONF_RETAIN: True, + } + else: + out[CONF_WILL_MESSAGE] = {} if CONF_SHUTDOWN_MESSAGE not in value: - out[CONF_SHUTDOWN_MESSAGE] = { - CONF_TOPIC: f"{topic_prefix}/status", - CONF_PAYLOAD: "offline", - CONF_QOS: 0, - CONF_RETAIN: True, - } + if topic_prefix != "": + out[CONF_SHUTDOWN_MESSAGE] = { + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "offline", + CONF_QOS: 0, + CONF_RETAIN: True, + } + else: + out[CONF_SHUTDOWN_MESSAGE] = {} if CONF_LOG_TOPIC not in value: - out[CONF_LOG_TOPIC] = { - CONF_TOPIC: f"{topic_prefix}/debug", - CONF_QOS: 0, - CONF_RETAIN: True, - } + if topic_prefix != "": + out[CONF_LOG_TOPIC] = { + CONF_TOPIC: f"{topic_prefix}/debug", + CONF_QOS: 0, + CONF_RETAIN: True, + } + else: + out[CONF_LOG_TOPIC] = {} return out @@ -181,6 +211,12 @@ def validate_fingerprint(value): cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( cv.string, cv.only_with_esp_idf ), + cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All( + cv.string, cv.only_on_esp32 + ), + 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 ), @@ -250,7 +286,7 @@ def validate_fingerprint(value): } ), validate_config, - cv.only_on(["esp32", "esp8266"]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]), ) @@ -271,10 +307,10 @@ def exp_mqtt_message(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - # Add required libraries for ESP8266 - if CORE.is_esp8266: - # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") + # Add required libraries for ESP8266 and LibreTiny + if CORE.is_esp8266 or CORE.is_libretiny: + # https://github.com/heman/async-mqtt-client/blob/master/library.json + cg.add_library("heman/AsyncMqttClient-esphome", "2.0.0") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) @@ -360,6 +396,9 @@ async def to_code(config): if CONF_CERTIFICATE_AUTHORITY in config: cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY])) cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK])) + if CONF_CLIENT_CERTIFICATE in config: + cg.add(var.set_cl_certificate(config[CONF_CLIENT_CERTIFICATE])) + cg.add(var.set_cl_key(config[CONF_CLIENT_CERTIFICATE_KEY])) # prevent error -0x428e # See https://github.com/espressif/esp-idf/issues/139 @@ -460,6 +499,8 @@ def get_default_topic_for(data, component_type, name, suffix): async def register_mqtt_component(var, config): await cg.register_component(var, {}) + if CONF_QOS in config: + cg.add(var.set_qos(config[CONF_QOS])) if CONF_RETAIN in config: cg.add(var.set_retain(config[CONF_RETAIN])) if not config.get(CONF_DISCOVERY, True): diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 2d4e6802f2ba..9c2e487ae7c0 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -45,6 +45,11 @@ bool MQTTBackendESP32::initialize_() { mqtt_cfg_.cert_pem = ca_certificate_.value().c_str(); mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_; mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL; + + if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) { + mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str(); + mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str(); + } } else { mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; } @@ -79,6 +84,11 @@ bool MQTTBackendESP32::initialize_() { mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str(); mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_; mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; + + if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) { + mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_.value().c_str(); + mqtt_cfg_.credentials.authentication.key = this->cl_key_.value().c_str(); + } } else { mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; } diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index a4ee96ca596e..b1f672da1078 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend { void loop() final; void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; } + void set_cl_certificate(const std::string &cert) { cl_certificate_ = cert; } + void set_cl_key(const std::string &key) { cl_key_ = key; } void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; } protected: @@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend { uint16_t keep_alive_; bool clean_session_; optional ca_certificate_; + optional cl_certificate_; + optional cl_key_; bool skip_cert_cn_check_{false}; // callbacks diff --git a/esphome/components/mqtt/mqtt_backend_esp8266.h b/esphome/components/mqtt/mqtt_backend_esp8266.h index 2d91877e9d2c..06d4993bdf6f 100644 --- a/esphome/components/mqtt/mqtt_backend_esp8266.h +++ b/esphome/components/mqtt/mqtt_backend_esp8266.h @@ -19,9 +19,7 @@ class MQTTBackendESP8266 final : public MQTTBackend { void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { mqtt_client_.setWill(topic, qos, retain, payload); } - void set_server(network::IPAddress ip, uint16_t port) final { - mqtt_client_.setServer(IPAddress(static_cast(ip)), port); - } + void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(ip, port); } void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } #if ASYNC_TCP_SSL_ENABLED void set_secure(bool secure) { mqtt_client.setSecure(secure); } diff --git a/esphome/components/mqtt/mqtt_backend_libretiny.h b/esphome/components/mqtt/mqtt_backend_libretiny.h new file mode 100644 index 000000000000..ac4d4298fcc8 --- /dev/null +++ b/esphome/components/mqtt/mqtt_backend_libretiny.h @@ -0,0 +1,72 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#include "mqtt_backend.h" +#include + +namespace esphome { +namespace mqtt { + +class MQTTBackendLibreTiny final : public MQTTBackend { + public: + void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); } + void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); } + void set_clean_session(bool clean_session) final { mqtt_client_.setCleanSession(clean_session); } + void set_credentials(const char *username, const char *password) final { + mqtt_client_.setCredentials(username, password); + } + void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { + mqtt_client_.setWill(topic, qos, retain, payload); + } + void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(IPAddress(ip), port); } + void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } +#if ASYNC_TCP_SSL_ENABLED + void set_secure(bool secure) { mqtt_client.setSecure(secure); } + void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); } +#endif + + void set_on_connect(std::function &&callback) final { + this->mqtt_client_.onConnect(std::move(callback)); + } + void set_on_disconnect(std::function &&callback) final { + auto async_callback = [callback](AsyncMqttClientDisconnectReason reason) { + // int based enum so casting isn't a problem + callback(static_cast(reason)); + }; + this->mqtt_client_.onDisconnect(std::move(async_callback)); + } + void set_on_subscribe(std::function &&callback) final { + this->mqtt_client_.onSubscribe(std::move(callback)); + } + void set_on_unsubscribe(std::function &&callback) final { + this->mqtt_client_.onUnsubscribe(std::move(callback)); + } + void set_on_message(std::function &&callback) final { + auto async_callback = [callback](const char *topic, const char *payload, + AsyncMqttClientMessageProperties async_properties, size_t len, size_t index, + size_t total) { callback(topic, payload, len, index, total); }; + mqtt_client_.onMessage(std::move(async_callback)); + } + void set_on_publish(std::function &&callback) final { + this->mqtt_client_.onPublish(std::move(callback)); + } + + bool connected() const final { return mqtt_client_.connected(); } + void connect() final { mqtt_client_.connect(); } + void disconnect() final { mqtt_client_.disconnect(true); } + bool subscribe(const char *topic, uint8_t qos) final { return mqtt_client_.subscribe(topic, qos) != 0; } + bool unsubscribe(const char *topic) final { return mqtt_client_.unsubscribe(topic) != 0; } + bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { + return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0; + } + using MQTTBackend::publish; + + protected: + AsyncMqttClient mqtt_client_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // defined(USE_LIBRETINY) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 79e6989a8f68..6d12e883910b 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -25,7 +25,7 @@ void MQTTBinarySensorComponent::dump_config() { MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor *binary_sensor) : binary_sensor_(binary_sensor) { if (this->binary_sensor_->is_status_binary_sensor()) { - this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); + this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic.c_str()); } } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index d3f759c07253..abcbb414d9b6 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -66,30 +66,38 @@ void MQTTClientComponent::setup() { } #endif - this->subscribe( - "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, - 2); - - std::string topic = "esphome/ping/"; - topic.append(App.get_name()); - this->subscribe( - topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); + if (this->is_discovery_enabled()) { + this->subscribe( + "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, + 2); + + std::string topic = "esphome/ping/"; + topic.append(App.get_name()); + this->subscribe( + topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); + } this->last_connected_ = millis(); this->start_dnslookup_(); } void MQTTClientComponent::send_device_info_() { - if (!this->is_connected()) { + if (!this->is_connected() or !this->is_discovery_enabled()) { return; } std::string topic = "esphome/discover/"; topic.append(App.get_name()); + this->publish_json( topic, [](JsonObject root) { - auto ip = network::get_ip_address(); - root["ip"] = ip.str(); + uint8_t index = 0; + for (auto &ip : network::get_ip_addresses()) { + if (ip.is_set()) { + root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str(); + index++; + } + } root["name"] = App.get_name(); #ifdef USE_API root["port"] = api::global_api_server->get_port(); @@ -103,6 +111,9 @@ void MQTTClientComponent::send_device_info_() { #ifdef USE_ESP32 root["platform"] = "ESP32"; #endif +#ifdef USE_LIBRETINY + root["platform"] = lt_cpu_get_model_name(); +#endif root["board"] = ESPHOME_BOARD; #if defined(USE_WIFI) @@ -141,7 +152,7 @@ void MQTTClientComponent::dump_config() { ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str()); } } -bool MQTTClientComponent::can_proceed() { return this->is_connected(); } +bool MQTTClientComponent::can_proceed() { return network::is_disabled() || this->is_connected(); } void MQTTClientComponent::start_dnslookup_() { for (auto &subscription : this->subscriptions_) { @@ -153,28 +164,18 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolve_error_ = false; this->dns_resolved_ = false; ip_addr_t addr; -#ifdef USE_ESP32 +#if USE_NETWORK_IPV6 + err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, + MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV6_IPV4); +#else err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); -#endif -#ifdef USE_ESP8266 - err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, - esphome::mqtt::MQTTClientComponent::dns_found_callback, this); -#endif +#endif /* USE_NETWORK_IPV6 */ switch (err) { case ERR_OK: { // Got IP immediately this->dns_resolved_ = true; -#ifdef USE_ESP32 -#if LWIP_IPV6 - this->ip_ = addr.u_addr.ip4.addr; -#else - this->ip_ = addr.addr; -#endif -#endif -#ifdef USE_ESP8266 - this->ip_ = addr.addr; -#endif + this->ip_ = network::IPAddress(&addr); this->start_connect_(); return; } @@ -186,11 +187,7 @@ void MQTTClientComponent::start_dnslookup_() { default: case ERR_ARG: { // error -#if defined(USE_ESP8266) - ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err); -#else ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err); -#endif break; } } @@ -225,16 +222,7 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * if (ipaddr == nullptr) { a_this->dns_resolve_error_ = true; } else { -#ifdef USE_ESP32 -#if LWIP_IPV6 - a_this->ip_ = ipaddr->u_addr.ip4.addr; -#else - a_this->ip_ = ipaddr->addr; -#endif -#endif // USE_ESP32 -#ifdef USE_ESP8266 - a_this->ip_ = ipaddr->addr; -#endif + a_this->ip_ = network::IPAddress(ipaddr); a_this->dns_resolved_ = true; } } @@ -482,8 +470,8 @@ bool MQTTClientComponent::publish(const MQTTMessage &message) { if (!logging_topic) { if (ret) { - ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(), - message.retain); + ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d qos=%d)", message.topic.c_str(), message.payload.c_str(), + message.retain, message.qos); } else { ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(), message.payload.length()); diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 00eb3fdd40ff..454316aa87e5 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -13,6 +13,8 @@ #include "mqtt_backend_esp32.h" #elif defined(USE_ESP8266) #include "mqtt_backend_esp8266.h" +#elif defined(USE_LIBRETINY) +#include "mqtt_backend_libretiny.h" #endif #include "lwip/ip_addr.h" @@ -144,6 +146,8 @@ class MQTTClientComponent : public Component { #endif #ifdef USE_ESP32 void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); } + void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); } + void set_cl_key(const char *key) { this->mqtt_backend_.set_cl_key(key); } void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); } #endif const Availability &get_availability(); @@ -300,6 +304,8 @@ class MQTTClientComponent : public Component { MQTTBackendESP32 mqtt_backend_; #elif defined(USE_ESP8266) MQTTBackendESP8266 mqtt_backend_; +#elif defined(USE_LIBRETINY) + MQTTBackendLibreTiny mqtt_backend_; #endif MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 44c490c3089e..49a8f0673411 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -17,9 +17,12 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo auto traits = this->device_->get_traits(); // current_temperature_topic if (traits.get_supports_current_temperature()) { - // current_temperature_topic root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } + // current_humidity_topic + if (traits.get_supports_current_humidity()) { + root[MQTT_CURRENT_HUMIDITY_TOPIC] = this->get_current_humidity_state_topic(); + } // mode_command_topic root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic(); // mode_state_topic @@ -57,6 +60,13 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic(); } + if (traits.get_supports_target_humidity()) { + // target_humidity_command_topic + root[MQTT_TARGET_HUMIDITY_COMMAND_TOPIC] = this->get_target_humidity_command_topic(); + // target_humidity_state_topic + root[MQTT_TARGET_HUMIDITY_STATE_TOPIC] = this->get_target_humidity_state_topic(); + } + // min_temp root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature(); // max_temp @@ -66,6 +76,11 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // temperature units are always coerced to Celsius internally root[MQTT_TEMPERATURE_UNIT] = "C"; + // min_humidity + root[MQTT_MIN_HUMIDITY] = traits.get_visual_min_humidity(); + // max_humidity + root[MQTT_MAX_HUMIDITY] = traits.get_visual_max_humidity(); + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { // preset_mode_command_topic root[MQTT_PRESET_MODE_COMMAND_TOPIC] = this->get_preset_command_topic(); @@ -192,6 +207,20 @@ void MQTTClimateComponent::setup() { }); } + if (traits.get_supports_target_humidity()) { + this->subscribe(this->get_target_humidity_command_topic(), + [this](const std::string &topic, const std::string &payload) { + auto val = parse_number(payload); + if (!val.has_value()) { + ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); + return; + } + auto call = this->device_->make_call(); + call.set_target_humidity(*val); + call.perform(); + }); + } + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) { auto call = this->device_->make_call(); @@ -273,6 +302,17 @@ bool MQTTClimateComponent::publish_state_() { success = false; } + if (traits.get_supports_current_humidity() && !std::isnan(this->device_->current_humidity)) { + std::string payload = value_accuracy_to_string(this->device_->current_humidity, 0); + if (!this->publish(this->get_current_humidity_state_topic(), payload)) + success = false; + } + if (traits.get_supports_target_humidity() && !std::isnan(this->device_->target_humidity)) { + std::string payload = value_accuracy_to_string(this->device_->target_humidity, 0); + if (!this->publish(this->get_target_humidity_state_topic(), payload)) + success = false; + } + if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { std::string payload; if (this->device_->preset.has_value()) { diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index a93070fe66ea..4e54230e68f4 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -20,6 +20,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { void setup() override; MQTT_COMPONENT_CUSTOM_TOPIC(current_temperature, state) + MQTT_COMPONENT_CUSTOM_TOPIC(current_humidity, state) MQTT_COMPONENT_CUSTOM_TOPIC(mode, state) MQTT_COMPONENT_CUSTOM_TOPIC(mode, command) MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature, state) @@ -28,6 +29,8 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_low, command) MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_high, state) MQTT_COMPONENT_CUSTOM_TOPIC(target_temperature_high, command) + MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, state) + MQTT_COMPONENT_CUSTOM_TOPIC(target_humidity, command) MQTT_COMPONENT_CUSTOM_TOPIC(away, state) MQTT_COMPONENT_CUSTOM_TOPIC(away, command) MQTT_COMPONENT_CUSTOM_TOPIC(action, state) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 1c7d9f86ddd4..bb46ce732df9 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -2,9 +2,9 @@ #ifdef USE_MQTT -#include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/version.h" #include "mqtt_const.h" @@ -14,6 +14,8 @@ namespace mqtt { static const char *const TAG = "mqtt.component"; +void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } + void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { @@ -23,32 +25,37 @@ std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discove } std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) const { - return global_mqtt_client->get_topic_prefix() + "/" + this->component_type() + "/" + this->get_default_object_id_() + - "/" + suffix; + const std::string &topic_prefix = global_mqtt_client->get_topic_prefix(); + if (topic_prefix.empty()) { + // If the topic_prefix is null, the default topic should be null + return ""; + } + + return topic_prefix + "/" + this->component_type() + "/" + this->get_default_object_id_() + "/" + suffix; } std::string MQTTComponent::get_state_topic_() const { - if (this->custom_state_topic_.empty()) - return this->get_default_topic_for_("state"); - return this->custom_state_topic_; + if (this->has_custom_state_topic_) + return this->custom_state_topic_.str(); + return this->get_default_topic_for_("state"); } std::string MQTTComponent::get_command_topic_() const { - if (this->custom_command_topic_.empty()) - return this->get_default_topic_for_("command"); - return this->custom_command_topic_; + if (this->has_custom_command_topic_) + return this->custom_command_topic_.str(); + return this->get_default_topic_for_("command"); } bool MQTTComponent::publish(const std::string &topic, const std::string &payload) { if (topic.empty()) return false; - return global_mqtt_client->publish(topic, payload, 0, this->retain_); + return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_); } bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) { if (topic.empty()) return false; - return global_mqtt_client->publish_json(topic, f, 0, this->retain_); + return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_); } bool MQTTComponent::send_discovery_() { @@ -56,7 +63,7 @@ bool MQTTComponent::send_discovery_() { if (discovery_info.clean) { ESP_LOGV(TAG, "'%s': Cleaning discovery...", this->friendly_name().c_str()); - return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, 0, true); + 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()); @@ -71,7 +78,11 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Fields from EntityBase - root[MQTT_NAME] = this->friendly_name(); + if (this->get_entity()->has_own_name()) { + root[MQTT_NAME] = this->friendly_name(); + } else { + root[MQTT_NAME] = ""; + } if (this->is_disabled_by_default()) root[MQTT_ENABLED_BY_DEFAULT] = false; if (!this->get_icon().empty()) @@ -136,6 +147,7 @@ bool MQTTComponent::send_discovery_() { if (node_friendly_name.empty()) { node_friendly_name = node_name; } + const std::string &node_area = App.get_area(); JsonObject device_info = root.createNestedObject(MQTT_DEVICE); device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); @@ -143,10 +155,13 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; device_info[MQTT_DEVICE_MANUFACTURER] = "espressif"; + device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area; }, - 0, discovery_info.retain); + this->qos_, discovery_info.retain); } +uint8_t MQTTComponent::get_qos() const { return this->qos_; } + bool MQTTComponent::get_retain() const { return this->retain_; } bool MQTTComponent::is_discovery_enabled() const { @@ -169,11 +184,13 @@ MQTTComponent::MQTTComponent() = default; float MQTTComponent::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } void MQTTComponent::disable_discovery() { this->discovery_enabled_ = false; } -void MQTTComponent::set_custom_state_topic(const std::string &custom_state_topic) { - this->custom_state_topic_ = custom_state_topic; +void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) { + this->custom_state_topic_ = StringRef(custom_state_topic); + this->has_custom_state_topic_ = true; } -void MQTTComponent::set_custom_command_topic(const std::string &custom_command_topic) { - this->custom_command_topic_ = custom_command_topic; +void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) { + this->custom_command_topic_ = StringRef(custom_command_topic); + this->has_custom_command_topic_ = true; } void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; } @@ -240,7 +257,28 @@ bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connec 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(); } -bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } +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 + // else, return false, as it is explicitly set to a topic, so it is not internal and should publish + return this->get_state_topic_().empty(); + } + + if (this->has_custom_command_topic_) { + // If the custom command_topic is null, return true as it is internal and should not publish + // else, return false, as it is explicitly set to a topic, so it is not internal and should publish + return this->get_command_topic_().empty(); + } + + // No custom topics have been set + if (this->get_default_topic_for_("").empty()) { + // If the default topic prefix is null, then the component, by default, is internal and should not publish + return true; + } + + // Use ESPHome's component internal state if topic_prefix is not null with no custom state_topic or command_topic + return this->get_entity()->is_internal(); +} } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 16a00cfddeae..147840d11f6e 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -8,6 +8,7 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/string_ref.h" #include "mqtt_client.h" namespace esphome { @@ -76,6 +77,10 @@ class MQTTComponent : public Component { virtual bool is_internal(); + /// Set QOS for state messages. + void set_qos(uint8_t qos); + uint8_t get_qos() const; + /// Set whether state message should be retained. void set_retain(bool retain); bool get_retain() const; @@ -88,9 +93,9 @@ class MQTTComponent : public Component { virtual std::string component_type() const = 0; /// Set a custom state topic. Set to "" for default behavior. - void set_custom_state_topic(const std::string &custom_state_topic); + void set_custom_state_topic(const char *custom_state_topic); /// Set a custom command topic. Set to "" for default behavior. - void set_custom_command_topic(const std::string &custom_command_topic); + void set_custom_command_topic(const char *custom_command_topic); /// Set whether command message should be retained. void set_command_retain(bool command_retain); @@ -188,12 +193,18 @@ class MQTTComponent : public Component { /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. std::string get_default_object_id_() const; - std::string custom_state_topic_{}; - std::string custom_command_topic_{}; + StringRef custom_state_topic_{}; + StringRef custom_command_topic_{}; + + std::unique_ptr availability_; + + bool has_custom_state_topic_{false}; + bool has_custom_command_topic_{false}; + bool command_retain_{false}; bool retain_{true}; + uint8_t qos_{0}; bool discovery_enabled_{true}; - std::unique_ptr availability_; bool resend_state_{false}; }; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 7f74197ab4cf..66872680bb11 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -9,8 +9,8 @@ namespace mqtt { #ifdef USE_MQTT_ABBREVIATIONS -constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; 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"; @@ -21,58 +21,70 @@ 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_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; -constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; -constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; 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_CLEANING_TOPIC = "cln_t"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_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_TOPIC = "cmd_t"; constexpr const char *const MQTT_COMMAND_RETAIN = "ret"; constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; -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_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; +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_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_DOCKED_TOPIC = "dock_t"; +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_DOCKED_TEMPLATE = "dock_tpl"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; -constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; -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_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"; @@ -84,56 +96,49 @@ 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_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; 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_MIN = "min"; constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; -constexpr const char *const MQTT_MIN_MIREDS = "min_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_TOPIC = "mode_stat_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_OPTIONS = "ops"; constexpr const char *const MQTT_OPTIMISTIC = "opt"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; +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_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; -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_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_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; 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_LOCK = "pl_lock"; 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"; @@ -150,20 +155,26 @@ 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_STOP = "pl_stop"; +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_RETURN_TO_BASE = "pl_ret"; +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_TOPIC = "pow_stat_t"; constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; +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"; @@ -186,36 +197,38 @@ 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_POSITION_TOPIC = "pos_t"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; +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_STATE_TOPIC = "spd_stat_t"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; 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_SOURCE_TYPE = "src_type"; 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_LOCKED = "stat_locked"; -constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; -constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; 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_FEATURES = "sup_feat"; 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_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"; @@ -230,15 +243,15 @@ 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_TOPIC = "tilt_cmd_t"; 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_TOPIC = "tilt_status_t"; 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"; @@ -253,18 +266,10 @@ 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"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; -constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; - #else -constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; 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"; @@ -275,58 +280,70 @@ 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_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; -constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; -constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; 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_CLEANING_TOPIC = "cleaning_topic"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_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_TOPIC = "command_topic"; constexpr const char *const MQTT_COMMAND_RETAIN = "retain"; constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; -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_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; +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_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_DOCKED_TOPIC = "docked_topic"; +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_DOCKED_TEMPLATE = "docked_template"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; -constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; -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_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"; @@ -338,56 +355,49 @@ 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_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; 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_MIN = "min"; constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; -constexpr const char *const MQTT_MIN_MIREDS = "min_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_TOPIC = "mode_state_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_OPTIONS = "options"; constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; +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_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; -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_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_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; 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_LOCK = "payload_lock"; 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"; @@ -404,20 +414,26 @@ constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidit 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_STOP = "payload_stop"; +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_RETURN_TO_BASE = "payload_return_to_base"; +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_TOPIC = "power_state_topic"; constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; +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"; @@ -440,36 +456,38 @@ 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_POSITION_TOPIC = "position_topic"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; +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_STATE_TOPIC = "speed_state_topic"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; 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_SOURCE_TYPE = "source_type"; 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_LOCKED = "state_locked"; -constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; -constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; 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_FEATURES = "supported_features"; 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_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"; @@ -484,15 +502,15 @@ constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state 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_TOPIC = "tilt_command_topic"; 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_TOPIC = "tilt_status_topic"; 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"; @@ -507,19 +525,8 @@ 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"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; -constexpr const char *const MQTT_DEVICE_MODEL = "model"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; #endif -// Additional MQTT fields where no abbreviation is defined in HA source -constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; -constexpr const char *const MQTT_MODE = "mode"; - } // namespace mqtt } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp new file mode 100644 index 000000000000..088a4788ed52 --- /dev/null +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -0,0 +1,68 @@ +#include "mqtt_date.h" + +#include +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_DATE + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.datetime"; + +using namespace esphome::datetime; + +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.containsKey("year")) { + call.set_year(root["year"]); + } + if (root.containsKey("month")) { + call.set_month(root["month"]); + } + if (root.containsKey("day")) { + call.set_day(root["day"]); + } + call.perform(); + }); + this->date_->add_on_state_callback( + [this]() { this->publish_state(this->date_->year, this->date_->month, this->date_->day); }); +} + +void MQTTDateComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Date '%s':", this->date_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true) +} + +std::string MQTTDateComponent::component_type() const { return "date"; } +const EntityBase *MQTTDateComponent::get_entity() const { return this->date_; } + +void MQTTDateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // Nothing extra to add here +} +bool MQTTDateComponent::send_initial_state() { + if (this->date_->has_state()) { + return this->publish_state(this->date_->year, this->date_->month, this->date_->day); + } else { + return true; + } +} +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) { + root["year"] = year; + root["month"] = month; + root["day"] = day; + }); +} + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h new file mode 100644 index 000000000000..5147afe7e7be --- /dev/null +++ b/esphome/components/mqtt/mqtt_date.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_DATE + +#include "esphome/components/datetime/date_entity.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTDateComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTDateComponent instance with the provided friendly_name and date + * + * @param date The date component. + */ + explicit MQTTDateComponent(datetime::DateEntity *date); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(uint16_t year, uint8_t month, uint8_t day); + + protected: + std::string component_type() const override; + const EntityBase *get_entity() const override; + + datetime::DateEntity *date_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp new file mode 100644 index 000000000000..4fa44aafb81f --- /dev/null +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -0,0 +1,84 @@ +#include "mqtt_datetime.h" + +#include +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_TIME + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.datetime.time"; + +using namespace esphome::datetime; + +MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetime_(datetime) {} + +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.containsKey("year")) { + call.set_year(root["year"]); + } + if (root.containsKey("month")) { + call.set_month(root["month"]); + } + if (root.containsKey("day")) { + call.set_day(root["day"]); + } + if (root.containsKey("hour")) { + call.set_hour(root["hour"]); + } + if (root.containsKey("minute")) { + call.set_minute(root["minute"]); + } + if (root.containsKey("second")) { + call.set_second(root["second"]); + } + call.perform(); + }); + this->datetime_->add_on_state_callback([this]() { + this->publish_state(this->datetime_->year, this->datetime_->month, this->datetime_->day, this->datetime_->hour, + this->datetime_->minute, this->datetime_->second); + }); +} + +void MQTTDateTimeComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT DateTime '%s':", this->datetime_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true) +} + +std::string MQTTDateTimeComponent::component_type() const { return "datetime"; } +const EntityBase *MQTTDateTimeComponent::get_entity() const { return this->datetime_; } + +void MQTTDateTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // Nothing extra to add here +} +bool MQTTDateTimeComponent::send_initial_state() { + if (this->datetime_->has_state()) { + return this->publish_state(this->datetime_->year, this->datetime_->month, this->datetime_->day, + this->datetime_->hour, this->datetime_->minute, this->datetime_->second); + } else { + return true; + } +} +bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, + uint8_t second) { + return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { + root["year"] = year; + root["month"] = month; + root["day"] = day; + root["hour"] = hour; + root["minute"] = minute; + root["second"] = second; + }); +} + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_TIME +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.h b/esphome/components/mqtt/mqtt_datetime.h new file mode 100644 index 000000000000..f0d68ad2e1b9 --- /dev/null +++ b/esphome/components/mqtt/mqtt_datetime.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_TIME + +#include "esphome/components/datetime/datetime_entity.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTDateTimeComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTDateTimeComponent instance with the provided friendly_name and time + * + * @param time The time entity. + */ + explicit MQTTDateTimeComponent(datetime::DateTimeEntity *time); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); + + protected: + std::string component_type() const override; + const EntityBase *get_entity() const override; + + datetime::DateTimeEntity *datetime_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp new file mode 100644 index 000000000000..cf0b90e3d685 --- /dev/null +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -0,0 +1,54 @@ +#include "mqtt_event.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_EVENT + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.event"; + +using namespace esphome::event; + +MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {} + +void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES); + 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(); + + config.command_topic = false; +} + +void MQTTEventComponent::setup() { + this->event_->add_on_event_callback([this](const std::string &event_type) { this->publish_event_(event_type); }); +} + +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()); + } + LOG_MQTT_COMPONENT(true, true); +} + +bool MQTTEventComponent::publish_event_(const std::string &event_type) { + return this->publish_json(this->get_state_topic_(), + [event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; }); +} + +std::string MQTTEventComponent::component_type() const { return "event"; } +const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; } + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.h b/esphome/components/mqtt/mqtt_event.h new file mode 100644 index 000000000000..4335820e5379 --- /dev/null +++ b/esphome/components/mqtt/mqtt_event.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_EVENT + +#include "esphome/components/event/event.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTEventComponent : public mqtt::MQTTComponent { + public: + explicit MQTTEventComponent(event::Event *event); + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + void setup() override; + + void dump_config() override; + + /// Events do not send a state so just return true. + bool send_initial_state() override { return true; } + + protected: + bool publish_event_(const std::string &event_type); + std::string component_type() const override; + const EntityBase *get_entity() const override; + + event::Event *event_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 197d0c32d4ae..f4a5126d0cad 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -40,6 +40,8 @@ const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { if (this->lock_->traits.get_assumed_state()) root[MQTT_OPTIMISTIC] = true; + if (this->lock_->traits.get_supports_open()) + root[MQTT_PAYLOAD_OPEN] = "OPEN"; } bool MQTTLockComponent::send_initial_state() { return this->publish_state(); } diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp new file mode 100644 index 000000000000..cb852b64cd05 --- /dev/null +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -0,0 +1,63 @@ +#include "mqtt_text.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_TEXT + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.text"; + +using namespace esphome::text; + +MQTTTextComponent::MQTTTextComponent(Text *text) : text_(text) {} + +void MQTTTextComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { + auto call = this->text_->make_call(); + call.set_value(state); + call.perform(); + }); + + this->text_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); }); +} + +void MQTTTextComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT text '%s':", this->text_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true) +} + +std::string MQTTTextComponent::component_type() const { return "text"; } +const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } + +void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + switch (this->text_->traits.get_mode()) { + case TEXT_MODE_TEXT: + root[MQTT_MODE] = "text"; + break; + case TEXT_MODE_PASSWORD: + root[MQTT_MODE] = "password"; + break; + } + + config.command_topic = true; +} +bool MQTTTextComponent::send_initial_state() { + if (this->text_->has_state()) { + return this->publish_state(this->text_->state); + } else { + return true; + } +} +bool MQTTTextComponent::publish_state(const std::string &value) { + return this->publish(this->get_state_topic_(), value); +} + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.h b/esphome/components/mqtt/mqtt_text.h new file mode 100644 index 000000000000..d9486fcbf8e5 --- /dev/null +++ b/esphome/components/mqtt/mqtt_text.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_TEXT + +#include "esphome/components/text/text.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTTextComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTTextComponent instance with the provided friendly_name and text + * + * @param text The text input. + */ + explicit MQTTTextComponent(text::Text *text); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(const std::string &value); + + protected: + /// Override for MQTTComponent, returns "text". + std::string component_type() const override; + const EntityBase *get_entity() const override; + + text::Text *text_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index d0d3174bfe0c..b0754bc8b316 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_text_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_TEXT_SENSOR @@ -13,6 +15,8 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + if (!this->sensor_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp new file mode 100644 index 000000000000..332ef53cbcc9 --- /dev/null +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -0,0 +1,68 @@ +#include "mqtt_time.h" + +#include +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_TIME + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.datetime.time"; + +using namespace esphome::datetime; + +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.containsKey("hour")) { + call.set_hour(root["hour"]); + } + if (root.containsKey("minute")) { + call.set_minute(root["minute"]); + } + if (root.containsKey("second")) { + call.set_second(root["second"]); + } + call.perform(); + }); + this->time_->add_on_state_callback( + [this]() { this->publish_state(this->time_->hour, this->time_->minute, this->time_->second); }); +} + +void MQTTTimeComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Time '%s':", this->time_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true) +} + +std::string MQTTTimeComponent::component_type() const { return "time"; } +const EntityBase *MQTTTimeComponent::get_entity() const { return this->time_; } + +void MQTTTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + // Nothing extra to add here +} +bool MQTTTimeComponent::send_initial_state() { + if (this->time_->has_state()) { + return this->publish_state(this->time_->hour, this->time_->minute, this->time_->second); + } else { + return true; + } +} +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) { + root["hour"] = hour; + root["minute"] = minute; + root["second"] = second; + }); +} + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_TIME +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.h b/esphome/components/mqtt/mqtt_time.h new file mode 100644 index 000000000000..b9dd822a73b0 --- /dev/null +++ b/esphome/components/mqtt/mqtt_time.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_DATETIME_TIME + +#include "esphome/components/datetime/time_entity.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTTimeComponent : public mqtt::MQTTComponent { + public: + /** Construct this MQTTTimeComponent instance with the provided friendly_name and time + * + * @param time The time entity. + */ + explicit MQTTTimeComponent(datetime::TimeEntity *time); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + /// Override setup. + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(uint8_t hour, uint8_t minute, uint8_t second); + + protected: + std::string component_type() const override; + const EntityBase *get_entity() const override; + + datetime::TimeEntity *time_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif // USE_DATETIME_DATE +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp new file mode 100644 index 000000000000..07eeca08d63d --- /dev/null +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -0,0 +1,90 @@ +#include "mqtt_valve.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_VALVE + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.valve"; + +using namespace esphome::valve; + +MQTTValveComponent::MQTTValveComponent(Valve *valve) : valve_(valve) {} +void MQTTValveComponent::setup() { + auto traits = this->valve_->get_traits(); + this->valve_->add_on_state_callback([this]() { this->publish_state(); }); + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + auto call = this->valve_->make_call(); + call.set_command(payload.c_str()); + call.perform(); + }); + if (traits.get_supports_position()) { + this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) { + auto value = parse_number(payload); + if (!value.has_value()) { + ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str()); + return; + } + auto call = this->valve_->make_call(); + call.set_position(*value / 100.0f); + call.perform(); + }); + } +} + +void MQTTValveComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT valve '%s':", this->valve_->get_name().c_str()); + auto traits = this->valve_->get_traits(); + bool has_command_topic = traits.get_supports_position(); + LOG_MQTT_COMPONENT(true, has_command_topic) + if (traits.get_supports_position()) { + ESP_LOGCONFIG(TAG, " Position State Topic: '%s'", this->get_position_state_topic().c_str()); + ESP_LOGCONFIG(TAG, " Position Command Topic: '%s'", this->get_position_command_topic().c_str()); + } +} +void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + if (!this->valve_->get_device_class().empty()) + root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); + + auto traits = this->valve_->get_traits(); + if (traits.get_is_assumed_state()) { + root[MQTT_OPTIMISTIC] = true; + } + if (traits.get_supports_position()) { + root[MQTT_POSITION_TOPIC] = this->get_position_state_topic(); + root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic(); + } +} + +std::string MQTTValveComponent::component_type() const { return "valve"; } +const EntityBase *MQTTValveComponent::get_entity() const { return this->valve_; } + +bool MQTTValveComponent::send_initial_state() { return this->publish_state(); } +bool MQTTValveComponent::publish_state() { + auto traits = this->valve_->get_traits(); + bool success = true; + if (traits.get_supports_position()) { + std::string pos = value_accuracy_to_string(roundf(this->valve_->position * 100), 0); + if (!this->publish(this->get_position_state_topic(), pos)) + success = false; + } + const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening" + : this->valve_->current_operation == VALVE_OPERATION_CLOSING ? "closing" + : this->valve_->position == VALVE_CLOSED ? "closed" + : this->valve_->position == VALVE_OPEN ? "open" + : traits.get_supports_position() ? "open" + : "unknown"; + if (!this->publish(this->get_state_topic_(), state_s)) + success = false; + return success; +} + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.h b/esphome/components/mqtt/mqtt_valve.h new file mode 100644 index 000000000000..63a046219309 --- /dev/null +++ b/esphome/components/mqtt/mqtt_valve.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "mqtt_component.h" + +#ifdef USE_MQTT +#ifdef USE_VALVE + +#include "esphome/components/valve/valve.h" + +namespace esphome { +namespace mqtt { + +class MQTTValveComponent : public mqtt::MQTTComponent { + public: + explicit MQTTValveComponent(valve::Valve *valve); + + void setup() override; + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + MQTT_COMPONENT_CUSTOM_TOPIC(position, command) + MQTT_COMPONENT_CUSTOM_TOPIC(position, state) + + bool send_initial_state() override; + + bool publish_state(); + + void dump_config() override; + + protected: + std::string component_type() const override; + const EntityBase *get_entity() const override; + + valve::Valve *valve_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/ms8607/__init__.py b/esphome/components/ms8607/__init__.py new file mode 100644 index 000000000000..e1cd49ec7b69 --- /dev/null +++ b/esphome/components/ms8607/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@e28eta"] diff --git a/esphome/components/ms8607/ms8607.cpp b/esphome/components/ms8607/ms8607.cpp new file mode 100644 index 000000000000..4ad6ac336d08 --- /dev/null +++ b/esphome/components/ms8607/ms8607.cpp @@ -0,0 +1,444 @@ +#include "ms8607.h" + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ms8607 { + +/// TAG used for logging calls +static const char *const TAG = "ms8607"; + +/// Reset the Pressure/Temperature sensor +static const uint8_t MS8607_PT_CMD_RESET = 0x1E; + +/// Beginning of PROM register addresses. Same for both i2c addresses. Each address has 16 bits of data, and +/// PROM addresses step by two, so the LSB is always 0 +static const uint8_t MS8607_PROM_START = 0xA0; +/// Last PROM register address. +static const uint8_t MS8607_PROM_END = 0xAE; +/// Number of PROM registers. +static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1; + +/// Reset the Humidity sensor +static const uint8_t MS8607_CMD_H_RESET = 0xFE; +/// Read relative humidity, without holding i2c master +static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5; +/// Temperature correction coefficient for Relative Humidity from datasheet +static const float MS8607_H_TEMP_COEFFICIENT = -0.18; + +/// Read the converted analog value, either D1 (pressure) or D2 (temperature) +static const uint8_t MS8607_CMD_ADC_READ = 0x00; + +// TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration. +// ms8607 supports 6 different settings + +/// Request conversion of analog D1 (pressure) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A; +/// Request conversion of analog D2 (temperature) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms +static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A; + +enum class MS8607Component::ErrorCode { + /// Component hasn't failed (yet?) + NONE = 0, + /// Both the Pressure/Temperature address and the Humidity address failed to reset + PTH_RESET_FAILED = 1, + /// Asking the Pressure/Temperature sensor to reset failed + PT_RESET_FAILED = 2, + /// Asking the Humidity sensor to reset failed + H_RESET_FAILED = 3, + /// Reading the PROM calibration values failed + PROM_READ_FAILED = 4, + /// The PROM calibration values failed the CRC check + PROM_CRC_FAILED = 5, +}; + +enum class MS8607Component::SetupStatus { + /// This component has not successfully reset the PT & H devices + NEEDS_RESET, + /// Reset commands succeeded, need to wait >= 15ms to read PROM + NEEDS_PROM_READ, + /// Successfully read PROM and ready to update sensors + SUCCESSFUL, +}; + +static uint8_t crc4(uint16_t *buffer, size_t length); +static uint8_t hsensor_crc_check(uint16_t value); + +void MS8607Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MS8607..."); + this->error_code_ = ErrorCode::NONE; + this->setup_status_ = SetupStatus::NEEDS_RESET; + + // I do not know why the device sometimes NACKs the reset command, but + // try 3 times in case it's a transitory issue on this boot + this->set_retry( + "reset", 5, 3, + [this](const uint8_t remaining_setup_attempts) { + ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_, + this->humidity_device_->get_address()); + // I believe sending the reset command to both addresses is preferable to + // skipping humidity if PT fails for some reason. + // However, only consider the reset successful if they both ACK + bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0); + bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0); + + if (!(pt_successful && h_successful)) { + ESP_LOGE(TAG, "Resetting I2C devices failed"); + if (!pt_successful && !h_successful) { + this->error_code_ = ErrorCode::PTH_RESET_FAILED; + } else if (!pt_successful) { + this->error_code_ = ErrorCode::PT_RESET_FAILED; + } else { + this->error_code_ = ErrorCode::H_RESET_FAILED; + } + + if (remaining_setup_attempts > 0) { + this->status_set_error(); + } else { + this->mark_failed(); + } + return RetryResult::RETRY; + } + + this->setup_status_ = SetupStatus::NEEDS_PROM_READ; + this->error_code_ = ErrorCode::NONE; + this->status_clear_error(); + + // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library + this->set_timeout("prom-read", 15, [this]() { + if (this->read_calibration_values_from_prom_()) { + this->setup_status_ = SetupStatus::SUCCESSFUL; + this->status_clear_error(); + } else { + this->mark_failed(); + return; + } + }); + + return RetryResult::DONE; + }, + 5.0f); // executes at now, +5ms, +25ms +} + +void MS8607Component::update() { + if (this->setup_status_ != SetupStatus::SUCCESSFUL) { + // setup is still occurring, either because reset had to retry or due to the 15ms + // delay needed between reset & reading the PROM values + return; + } + + // Updating happens async and sequentially. + // Temperature, then pressure, then humidity + this->request_read_temperature_(); +} + +void MS8607Component::dump_config() { + ESP_LOGCONFIG(TAG, "MS8607:"); + LOG_I2C_DEVICE(this); + // LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address() + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->humidity_device_->get_address()); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MS8607 failed."); + switch (this->error_code_) { + case ErrorCode::PT_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure RESET failed"); + break; + case ErrorCode::H_RESET_FAILED: + ESP_LOGE(TAG, "Humidity RESET failed"); + break; + case ErrorCode::PTH_RESET_FAILED: + ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed"); + break; + case ErrorCode::PROM_READ_FAILED: + ESP_LOGE(TAG, "Reading PROM failed"); + break; + case ErrorCode::PROM_CRC_FAILED: + ESP_LOGE(TAG, "PROM values failed CRC"); + break; + case ErrorCode::NONE: + default: + ESP_LOGE(TAG, "Error reason unknown %u", static_cast(this->error_code_)); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +bool MS8607Component::read_calibration_values_from_prom_() { + ESP_LOGD(TAG, "Reading calibration values from PROM"); + + uint16_t buffer[MS8607_PROM_COUNT]; + bool successful = true; + + for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) { + uint8_t const address_to_read = MS8607_PROM_START + (idx * 2); + successful &= this->read_byte_16(address_to_read, &buffer[idx]); + } + + if (!successful) { + ESP_LOGE(TAG, "Reading calibration values from PROM failed"); + this->error_code_ = ErrorCode::PROM_READ_FAILED; + return false; + } + + ESP_LOGD(TAG, "Checking CRC of calibration values from PROM"); + uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12; // first 4 bits + buffer[0] &= 0x0FFF; // strip CRC from buffer, in order to run CRC + uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT); + + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc); + this->error_code_ = ErrorCode::PROM_CRC_FAILED; + return false; + } + + this->calibration_values_.pressure_sensitivity = buffer[1]; + this->calibration_values_.pressure_offset = buffer[2]; + this->calibration_values_.pressure_sensitivity_temperature_coefficient = buffer[3]; + this->calibration_values_.pressure_offset_temperature_coefficient = buffer[4]; + this->calibration_values_.reference_temperature = buffer[5]; + this->calibration_values_.temperature_coefficient_of_temperature = buffer[6]; + ESP_LOGD(TAG, "Finished reading calibration values"); + + // Skipping reading Humidity PROM, since it doesn't have anything interesting for us + + return true; +} + +/** + CRC-4 algorithm from datasheet. It operates on a buffer of 16-bit values, one byte at a time, using a 16-bit + value to collect the CRC result into. + + The provided/expected CRC value must already be zeroed out from the buffer. + */ +static uint8_t crc4(uint16_t *buffer, size_t length) { + uint16_t crc_remainder = 0; + + // algorithm to add a byte into the crc + auto apply_crc = [&crc_remainder](uint8_t next) { + crc_remainder ^= next; + for (uint8_t bit = 8; bit > 0; --bit) { + if (crc_remainder & 0x8000) { + crc_remainder = (crc_remainder << 1) ^ 0x3000; + } else { + crc_remainder = (crc_remainder << 1); + } + } + }; + + // add all the bytes + for (size_t idx = 0; idx < length; ++idx) { + for (auto byte : decode_value(buffer[idx])) { + apply_crc(byte); + } + } + // For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through + apply_crc(0); + apply_crc(0); + + return (crc_remainder >> 12) & 0xF; // only the most significant 4 bits +} + +/** + * @brief Calculates CRC value for the provided humidity (+ status bits) value + * + * CRC-8 check comes from other MS8607 libraries on github. I did not find it in the datasheet, + * and it differs from the crc8 implementation that's already part of esphome. + * + * @param value two byte humidity sensor value read from i2c + * @return uint8_t computed crc value + */ +static uint8_t hsensor_crc_check(uint16_t value) { + uint32_t polynom = 0x988000; // x^8 + x^5 + x^4 + 1 + uint32_t msb = 0x800000; + uint32_t mask = 0xFF8000; + uint32_t result = (uint32_t) value << 8; // Pad with zeros as specified in spec + + while (msb != 0x80) { + // Check if msb of current value is 1 and apply XOR mask + if (result & msb) { + result = ((result ^ polynom) & mask) | (result & ~mask); + } + + // Shift by one + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + return result & 0xFF; +} + +void MS8607Component::request_read_temperature_() { + // Tell MS8607 to start ADC conversion of temperature sensor + if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_temperature_, this); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("temperature", 20, f); +} + +void MS8607Component::read_temperature_() { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + + const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->request_read_pressure_(d2_raw_temperature); +} + +void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) { + if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) { + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature); + // datasheet says 17.2ms max conversion time at OSR 8192 + this->set_timeout("pressure", 20, f); +} + +void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) { + uint8_t bytes[3]; // 24 bits + if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { + this->status_set_warning(); + return; + } + const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]); + this->calculate_values_(d2_raw_temperature, d1_raw_pressure); +} + +void MS8607Component::request_read_humidity_(float temperature_float) { + if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) { + ESP_LOGW(TAG, "Request to measure humidity failed"); + this->status_set_warning(); + return; + } + + auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float); + // datasheet says 15.89ms max conversion time at OSR 8192 + this->set_timeout("humidity", 20, f); +} + +void MS8607Component::read_humidity_(float temperature_float) { + uint8_t bytes[3]; + if (!this->humidity_device_->read_bytes_raw(bytes, 3)) { + ESP_LOGW(TAG, "Failed to read the measured humidity value"); + this->status_set_warning(); + return; + } + + // "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information. + // Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned" + uint16_t humidity = encode_uint16(bytes[0], bytes[1]); + uint8_t const expected_crc = bytes[2]; + uint8_t const actual_crc = hsensor_crc_check(humidity); + if (expected_crc != actual_crc) { + ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, + actual_crc); + this->status_set_warning(); + return; + } + if (!(humidity & 0x2)) { + // data sheet says Bit1 should always set, but nothing about what happens if it isn't + ESP_LOGE(TAG, "Humidity status bit was not set to 1?"); + } + humidity &= ~(0b11); // strip status & unassigned bits from data + + // map 16 bit humidity value into range [-6%, 118%] + float const humidity_partial = double(humidity) / (1 << 16); + float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0); + float const compensated_humidity_percentage = + humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT; + ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage); + + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(compensated_humidity_percentage); + } + this->status_clear_warning(); +} + +void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) { + // Perform the first order pressure/temperature calculation + + // d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8 + const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8); + // actual temperature as hundredths of degree celsius in range [-4000, 8500] + // 2000 + d_t * [C6] / (2**23) + int32_t temperature = + 2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23); + + // offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6)) + int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) + + ((int64_t(d_t) * this->calibration_values_.pressure_offset_temperature_coefficient) >> 6); + // sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7) + int64_t pressure_sensitivity = + (int64_t(this->calibration_values_.pressure_sensitivity) << 16) + + ((int64_t(d_t) * this->calibration_values_.pressure_sensitivity_temperature_coefficient) >> 7); + + // Perform the second order compensation, for non-linearity over temperature range + const int64_t d_t_squared = int64_t(d_t) * d_t; + int64_t temperature_2 = 0; + int32_t pressure_offset_2 = 0; + int32_t pressure_sensitivity_2 = 0; + if (temperature < 2000) { + // (TEMP - 2000)**2 / 2**4 + const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4; + + // T2 = 3 * (d_t**2) / 2**33 + temperature_2 = (3 * d_t_squared) >> 33; + // OFF2 = 61 * (TEMP-2000)**2 / 2**4 + pressure_offset_2 = 61 * low_temperature_adjustment; + // SENS2 = 29 * (TEMP-2000)**2 / 2**4 + pressure_sensitivity_2 = 29 * low_temperature_adjustment; + + if (temperature < -1500) { + // (TEMP+1500)**2 + const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500); + + // OFF2 = OFF2 + 17 * (TEMP+1500)**2 + pressure_offset_2 += 17 * very_low_temperature_adjustment; + // SENS2 = SENS2 + 9 * (TEMP+1500)**2 + pressure_sensitivity_2 += 9 * very_low_temperature_adjustment; + } + } else { + // T2 = 5 * (d_t**2) / 2**38 + temperature_2 = (5 * d_t_squared) >> 38; + } + + temperature -= temperature_2; + pressure_offset -= pressure_offset_2; + pressure_sensitivity -= pressure_sensitivity_2; + + // Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar] + const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15; + + const float temperature_float = temperature / 100.0f; + const float pressure_float = pressure / 100.0f; + ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float); + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(temperature_float); + } + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(pressure_float); // hPa aka mbar + } + this->status_clear_warning(); + + if (this->humidity_sensor_ != nullptr) { + // now that we have temperature (to compensate the humidity with), kick off that read + this->request_read_humidity_(temperature_float); + } +} + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/ms8607.h b/esphome/components/ms8607/ms8607.h new file mode 100644 index 000000000000..0bee7e97b77f --- /dev/null +++ b/esphome/components/ms8607/ms8607.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ms8607 { + +/** + Class for I2CDevice used to communicate with the Humidity sensor + on the chip. See MS8607Component instead + */ +class MS8607HumidityDevice : public i2c::I2CDevice { + public: + uint8_t get_address() { return address_; } +}; + +/** + Temperature, pressure, and humidity sensor. + + By default, the MS8607 measures sensors at the highest resolution. + A potential enhancement would be to expose the resolution as a configurable + setting. A lower resolution speeds up ADC conversion time & uses less power. + + Datasheet: + https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS8607-02BA01%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS8607-02BA01_B3.pdf%7FCAT-BLPS0018 + + Other implementations: + - https://github.com/TEConnectivity/MS8607_Generic_C_Driver + - https://github.com/adafruit/Adafruit_MS8607 + - https://github.com/sparkfun/SparkFun_PHT_MS8607_Arduino_Library + */ +class MS8607Component : public PollingComponent, public i2c::I2CDevice { + public: + virtual ~MS8607Component() = default; + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + void set_humidity_device(MS8607HumidityDevice *humidity_device) { humidity_device_ = humidity_device; } + + protected: + /** + Read and store the Pressure & Temperature calibration settings from the PROM. + Intended to be called during setup(), this will set the `failure_reason_` + */ + bool read_calibration_values_from_prom_(); + + /// Start async temperature read + void request_read_temperature_(); + /// Process async temperature read + void read_temperature_(); + /// start async pressure read + void request_read_pressure_(uint32_t raw_temperature); + /// process async pressure read + void read_pressure_(uint32_t raw_temperature); + /// start async humidity read + void request_read_humidity_(float temperature_float); + /// process async humidity read + void read_humidity_(float temperature_float); + /// use raw temperature & pressure to calculate & publish values + void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); + + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + sensor::Sensor *humidity_sensor_; + + /** I2CDevice object to communicate with secondary I2C address for the humidity sensor + * + * The MS8607 only has one set of I2C pins, despite using two different addresses. + * + * Default address for humidity is 0x40 + */ + MS8607HumidityDevice *humidity_device_; + + /// This device's pressure & temperature calibration values, read from PROM + struct CalibrationValues { + /// Pressure sensitivity | SENS-T1. [C1] + uint16_t pressure_sensitivity; + /// Temperature coefficient of pressure sensitivity | TCS. [C3] + uint16_t pressure_sensitivity_temperature_coefficient; + /// Pressure offset | OFF-T1. [C2] + uint16_t pressure_offset; + /// Temperature coefficient of pressure offset | TCO. [C4] + uint16_t pressure_offset_temperature_coefficient; + /// Reference temperature | T-REF. [C5] + uint16_t reference_temperature; + /// Temperature coefficient of the temperature | TEMPSENS. [C6] + uint16_t temperature_coefficient_of_temperature; + } calibration_values_; + + /// Possible failure reasons of this component + enum class ErrorCode; + /// Keep track of the reason why this component failed, to augment the dumped config + ErrorCode error_code_; + + /// Current progress through required component setup + enum class SetupStatus; + /// Current step in the multi-step & possibly delayed setup() process + SetupStatus setup_status_; +}; + +} // namespace ms8607 +} // namespace esphome diff --git a/esphome/components/ms8607/sensor.py b/esphome/components/ms8607/sensor.py new file mode 100644 index 000000000000..1113e14af234 --- /dev/null +++ b/esphome/components/ms8607/sensor.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +ms8607_ns = cg.esphome_ns.namespace("ms8607") +MS8607Component = ms8607_ns.class_( + "MS8607Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_HUMIDITY_I2C_ID = "humidity_i2c_id" +MS8607HumidityDevice = ms8607_ns.class_("MS8607HumidityDevice", i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MS8607Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, # Resolution: 0.01 + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=2, # Resolution: 0.016 + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, # Resolution: 0.04 + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(CONF_HUMIDITY_I2C_ID): cv.declare_id( + MS8607HumidityDevice + ), + } + ) + .extend(i2c.i2c_device_schema(0x40)), # default address for humidity + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x76)) # default address for temp/pressure +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) + humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID]) + await i2c.register_i2c_device(humidity_device, humidity_config) + cg.add(var.set_humidity_device(humidity_device)) diff --git a/esphome/components/my9231/my9231.cpp b/esphome/components/my9231/my9231.cpp index a97587b7bedb..c5115918564b 100644 --- a/esphome/components/my9231/my9231.cpp +++ b/esphome/components/my9231/my9231.cpp @@ -1,5 +1,6 @@ #include "my9231.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace my9231 { @@ -51,7 +52,11 @@ void MY9231OutputComponent::setup() { MY9231_CMD_SCATTER_APDM | MY9231_CMD_FREQUENCY_DIVIDE_1 | MY9231_CMD_REACTION_FAST | MY9231_CMD_ONE_SHOT_DISABLE; ESP_LOGV(TAG, " Command: 0x%02X", command); - this->init_chips_(command); + { + InterruptLock lock; + this->send_dcki_pulses_(32 * this->num_chips_); + this->init_chips_(command); + } ESP_LOGV(TAG, " Chips initialized."); } void MY9231OutputComponent::dump_config() { @@ -66,11 +71,14 @@ void MY9231OutputComponent::loop() { if (!this->update_) return; - for (auto pwm_amount : this->pwm_amounts_) { - this->write_word_(pwm_amount, this->bit_depth_); + { + InterruptLock lock; + for (auto pwm_amount : this->pwm_amounts_) { + this->write_word_(pwm_amount, this->bit_depth_); + } + // Send 8 DI pulses. After 8 falling edges, the duty data are store. + this->send_di_pulses_(8); } - // Send 8 DI pulses. After 8 falling edges, the duty data are store. - this->send_di_pulses_(8); this->update_ = false; } void MY9231OutputComponent::set_channel_value_(uint8_t channel, uint16_t value) { @@ -92,6 +100,7 @@ void MY9231OutputComponent::init_chips_(uint8_t command) { // Send 16 DI pulse. After 14 falling edges, the command data are // stored and after 16 falling edges the duty mode is activated. this->send_di_pulses_(16); + delayMicroseconds(12); } void MY9231OutputComponent::write_word_(uint16_t value, uint8_t bits) { for (uint8_t i = bits; i > 0; i--) { @@ -106,6 +115,13 @@ void MY9231OutputComponent::send_di_pulses_(uint8_t count) { this->pin_di_->digital_write(false); } } +void MY9231OutputComponent::send_dcki_pulses_(uint8_t count) { + delayMicroseconds(12); + for (uint8_t i = 0; i < count; i++) { + this->pin_dcki_->digital_write(true); + this->pin_dcki_->digital_write(false); + } +} } // namespace my9231 } // namespace esphome diff --git a/esphome/components/my9231/my9231.h b/esphome/components/my9231/my9231.h index a777dcc9609e..77c125985379 100644 --- a/esphome/components/my9231/my9231.h +++ b/esphome/components/my9231/my9231.h @@ -49,6 +49,7 @@ class MY9231OutputComponent : public Component { void init_chips_(uint8_t command); void write_word_(uint16_t value, uint8_t bits); void send_di_pulses_(uint8_t count); + void send_dcki_pulses_(uint8_t count); GPIOPin *pin_di_; GPIOPin *pin_dcki_; diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index cd29734f4277..36144ff0a485 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -5,6 +5,10 @@ from esphome.const import ( CONF_ENABLE_IPV6, + CONF_MIN_IPV6_ADDR_COUNT, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, ) CODEOWNERS = ["@esphome/core"] @@ -15,15 +19,20 @@ CONFIG_SCHEMA = cv.Schema( { - cv.SplitDefault(CONF_ENABLE_IPV6, esp32=False): cv.All( - cv.only_on_esp32, cv.boolean + cv.SplitDefault(CONF_ENABLE_IPV6): cv.All( + cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]) ), + cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int, } ) async def to_code(config): if CONF_ENABLE_IPV6 in config: + cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6]) + cg.add_define( + "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] + ) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) add_idf_sdkconfig_option( @@ -33,3 +42,7 @@ async def to_code(config): if config[CONF_ENABLE_IPV6]: cg.add_build_flag("-DCONFIG_LWIP_IPV6") cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") + if CORE.is_rp2040: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6") + if CORE.is_esp8266: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY") diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index af198179ba5f..30a426e4583c 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -3,43 +3,140 @@ #include #include #include +#include "esphome/core/macros.h" +#include "esphome/core/helpers.h" + +#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) +#include +#endif + +#if USE_ARDUINO +#include +#include +#endif /* USE_ADRDUINO */ + +#ifdef USE_HOST +#include +using ip_addr_t = in_addr; +using ip4_addr_t = in_addr; +#define ipaddr_aton(x, y) inet_aton((x), (y)) +#endif + +#if USE_ESP32_FRAMEWORK_ARDUINO +#define arduino_ns Arduino_h +#elif USE_LIBRETINY +#define arduino_ns arduino +#elif USE_ARDUINO +#define arduino_ns +#endif + +#ifdef USE_ESP32 +#include +#include +#endif namespace esphome { namespace network { struct IPAddress { public: - IPAddress() : addr_({0, 0, 0, 0}) {} - IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) : addr_({first, second, third, fourth}) {} - IPAddress(uint32_t raw) { - addr_[0] = (uint8_t) (raw >> 0); - addr_[1] = (uint8_t) (raw >> 8); - addr_[2] = (uint8_t) (raw >> 16); - addr_[3] = (uint8_t) (raw >> 24); - } - operator uint32_t() const { - uint32_t res = 0; - res |= ((uint32_t) addr_[0]) << 0; - res |= ((uint32_t) addr_[1]) << 8; - res |= ((uint32_t) addr_[2]) << 16; - res |= ((uint32_t) addr_[3]) << 24; - return res; - } - std::string str() const { - char buffer[24]; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", addr_[0], addr_[1], addr_[2], addr_[3]); - return buffer; - } - bool operator==(const IPAddress &other) const { - return addr_[0] == other.addr_[0] && addr_[1] == other.addr_[1] && addr_[2] == other.addr_[2] && - addr_[3] == other.addr_[3]; - } - uint8_t operator[](int index) const { return addr_[index]; } - uint8_t &operator[](int index) { return addr_[index]; } +#ifdef USE_HOST + IPAddress() { ip_addr_.s_addr = 0; } + IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { + this->ip_addr_.s_addr = htonl((first << 24) | (second << 16) | (third << 8) | fourth); + } + 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; } +#else + IPAddress() { ip_addr_set_zero(&ip_addr_); } + IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { + IP_ADDR4(&ip_addr_, first, second, third, fourth); + } + IPAddress(const ip_addr_t *other_ip) { ip_addr_copy(ip_addr_, *other_ip); } + IPAddress(const std::string &in_address) { ipaddr_aton(in_address.c_str(), &ip_addr_); } + IPAddress(ip4_addr_t *other_ip) { + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip4_addr_t)); +#if USE_ESP32 && LWIP_IPV6 + ip_addr_.type = IPADDR_TYPE_V4; +#endif + } +#if USE_ARDUINO + IPAddress(const arduino_ns::IPAddress &other_ip) { ip_addr_set_ip4_u32(&ip_addr_, other_ip); } +#endif +#if LWIP_IPV6 + IPAddress(ip6_addr_t *other_ip) { + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip6_addr_t)); + ip_addr_.type = IPADDR_TYPE_V6; + } +#endif /* LWIP_IPV6 */ + +#ifdef USE_ESP32 +#if LWIP_IPV6 + IPAddress(esp_ip6_addr_t *other_ip) { + memcpy((void *) &ip_addr_.u_addr.ip6, (void *) other_ip, sizeof(esp_ip6_addr_t)); + 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_ip_addr_t *other_ip) { +#if LWIP_IPV6 + memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_)); +#else + memcpy((void *) &ip_addr_, (void *) &other_ip->u_addr.ip4, sizeof(ip_addr_)); +#endif + } + operator esp_ip_addr_t() const { + esp_ip_addr_t tmp; +#if LWIP_IPV6 + memcpy((void *) &tmp, (void *) &ip_addr_, sizeof(ip_addr_)); +#else + memcpy((void *) &tmp.u_addr.ip4, (void *) &ip_addr_, sizeof(ip_addr_)); +#endif /* LWIP_IPV6 */ + return tmp; + } + operator esp_ip4_addr_t() const { + esp_ip4_addr_t tmp; +#if LWIP_IPV6 + memcpy((void *) &tmp, (void *) &ip_addr_.u_addr.ip4, sizeof(esp_ip4_addr_t)); +#else + memcpy((void *) &tmp, (void *) &ip_addr_, sizeof(ip_addr_)); +#endif /* LWIP_IPV6 */ + return tmp; + } +#endif /* USE_ESP32 */ + + operator ip_addr_t() const { return ip_addr_; } +#if LWIP_IPV6 + operator ip4_addr_t() const { return *ip_2_ip4(&ip_addr_); } +#endif /* LWIP_IPV6 */ + +#if USE_ARDUINO + operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); } +#endif + + bool is_set() { return !ip_addr_isany(&ip_addr_); } + bool is_ip4() { return IP_IS_V4(&ip_addr_); } + bool is_ip6() { return IP_IS_V6(&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_); } + IPAddress &operator+=(uint8_t increase) { + if (IP_IS_V4(&ip_addr_)) { +#if LWIP_IPV6 + (((u8_t *) (&ip_addr_.u_addr.ip4))[3]) += increase; +#else + (((u8_t *) (&ip_addr_.addr))[3]) += increase; +#endif /* LWIP_IPV6 */ + } + return *this; + } +#endif protected: - std::array addr_; + ip_addr_t ip_addr_; }; +using IPAddresses = std::array; + } // namespace network } // namespace esphome diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp index 941102d6c1e0..445485b64481 100644 --- a/esphome/components/network/util.cpp +++ b/esphome/components/network/util.cpp @@ -29,14 +29,22 @@ bool is_connected() { return false; } -network::IPAddress get_ip_address() { +bool is_disabled() { +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->is_disabled(); +#endif + return false; +} + +network::IPAddresses get_ip_addresses() { #ifdef USE_ETHERNET if (ethernet::global_eth_component != nullptr) - return ethernet::global_eth_component->get_ip_address(); + return ethernet::global_eth_component->get_ip_addresses(); #endif #ifdef USE_WIFI if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->get_ip_address(); + return wifi::global_wifi_component->get_ip_addresses(); #endif return {}; } diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h index f248d5cbf40b..5377d44f2fdb 100644 --- a/esphome/components/network/util.h +++ b/esphome/components/network/util.h @@ -8,9 +8,11 @@ namespace network { /// Return whether the node is connected to the network (through wifi, eth, ...) bool is_connected(); +/// Return whether the network is disabled (only wifi for now) +bool is_disabled(); /// Get the active network hostname std::string get_use_address(); -IPAddress get_ip_address(); +IPAddresses get_ip_addresses(); } // namespace network } // namespace esphome diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index 210d7b2e2bfa..f51fe6b4f8c5 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -33,5 +33,14 @@ class PageTrigger : public Trigger { } }; +class TouchTrigger : public Trigger { + public: + explicit TouchTrigger(Nextion *nextion) { + nextion->add_touch_event_callback([this](uint8_t page_id, uint8_t component_id, bool touch_event) { + this->trigger(page_id, component_id, touch_event); + }); + } +}; + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index b2a857c88821..784da35371be 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -21,6 +21,7 @@ CONF_ON_PAGE = "on_page" CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_START_UP_PAGE = "start_up_page" CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" CONF_WAVE_MAX_LENGTH = "wave_max_length" CONF_BACKGROUND_COLOR = "background_color" @@ -28,17 +29,18 @@ CONF_FOREGROUND_COLOR = "foreground_color" CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" CONF_FONT_ID = "font_id" +CONF_EXIT_REPARSE_ON_START = "exit_reparse_on_start" def NextionName(value): - valid_chars = f"{ascii_letters + digits}." + valid_chars = f"{ascii_letters + digits + '_'}." if not isinstance(value, str) or len(value) > 29: raise cv.Invalid("Must be a string less than 29 characters") for char in value: if char not in valid_chars: raise cv.Invalid( - f"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{char}' cannot be used." + f"Must only consist of upper/lowercase characters, numbers, the underscore '_', and the period '.'. The character '{char}' cannot be used." ) return value diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index bf6e74cb38fe..499cd901c08d 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -27,7 +27,7 @@ void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, b } void NextionBinarySensor::update() { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (this->variable_name_.empty()) // This is a touch component @@ -37,7 +37,7 @@ void NextionBinarySensor::update() { } void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (this->component_id_ == 0) // This is a legacy touch component diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 72f56bd6f3ec..ce45d25e7b85 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -2,11 +2,13 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import display, uart +from esphome.components import esp32 from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS, CONF_TRIGGER_ID, + CONF_ON_TOUCH, ) from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref @@ -18,10 +20,12 @@ CONF_TFT_URL, CONF_TOUCH_SLEEP_TIMEOUT, CONF_WAKE_UP_PAGE, + CONF_START_UP_PAGE, CONF_AUTO_WAKE_ON_TOUCH, + CONF_EXIT_REPARSE_ON_START, ) -CODEOWNERS = ["@senexcrenshaw"] +CODEOWNERS = ["@senexcrenshaw", "@edwardtfn"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"] @@ -30,12 +34,13 @@ SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) PageTrigger = nextion_ns.class_("PageTrigger", automation.Trigger.template()) +TouchTrigger = nextion_ns.class_("TouchTrigger", automation.Trigger.template()) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), - cv.Optional(CONF_TFT_URL): cv.All(cv.string, cv.only_with_arduino), + cv.Optional(CONF_TFT_URL): cv.url, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_ON_SETUP): automation.validate_automation( { @@ -57,9 +62,16 @@ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PageTrigger), } ), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TouchTrigger), + } + ), cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), - cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int, + cv.Optional(CONF_WAKE_UP_PAGE): cv.uint8_t, + cv.Optional(CONF_START_UP_PAGE): cv.uint8_t, cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, + cv.Optional(CONF_EXIT_REPARSE_ON_START, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -69,7 +81,6 @@ 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_BRIGHTNESS in config: @@ -83,10 +94,15 @@ async def to_code(config): if CONF_TFT_URL in config: cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) - if CORE.is_esp32: + if CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFiClientSecure", None) cg.add_library("HTTPClient", None) - if CORE.is_esp8266: + elif CORE.is_esp32 and CORE.using_esp_idf: + 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: cg.add_library("ESP8266HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: @@ -95,8 +111,12 @@ async def to_code(config): if CONF_WAKE_UP_PAGE in config: cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) - if CONF_AUTO_WAKE_ON_TOUCH in config: - cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + if CONF_START_UP_PAGE in config: + cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE])) + + cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + + cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START])) await display.register_display(var, config) @@ -115,3 +135,15 @@ async def to_code(config): for conf in config.get(CONF_ON_PAGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.uint8, "x")], conf) + + for conf in config.get(CONF_ON_TOUCH, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, + [ + (cg.uint8, "page_id"), + (cg.uint8, "component_id"), + (cg.bool_, "touch_event"), + ], + conf, + ) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index f8beaeab788c..ddbd3328efd5 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -2,6 +2,7 @@ #include "esphome/core/util.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include namespace esphome { namespace nextion { @@ -47,6 +48,9 @@ bool Nextion::check_connect_() { this->ignore_is_setup_ = true; this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating + if (this->exit_reparse_on_start_) { + this->send_command_("DRAKJHSUYDGBNCJHGJKSHBDN"); + } this->send_command_("connect"); this->comok_sent_ = millis(); @@ -61,6 +65,11 @@ bool Nextion::check_connect_() { std::string response; this->recv_ret_string_(response, 0, false); + if (!response.empty() && response[0] == 0x1A) { + // Swallow invalid variable name responses that may be caused by the above commands + ESP_LOGD(TAG, "0x1A error ignored during setup"); + return false; + } if (response.empty() || response.find("comok") == std::string::npos) { #ifdef NEXTION_PROTOCOL_LOG ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); @@ -88,7 +97,8 @@ bool Nextion::check_connect_() { connect_info.push_back(response.substr(start, end - start)); } - if (connect_info.size() == 7) { + this->is_detected_ = (connect_info.size() == 7); + if (this->is_detected_) { ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size()); this->device_model_ = connect_info[2]; @@ -111,6 +121,7 @@ void Nextion::reset_(bool reset_nextion) { this->read_byte(&d); }; this->nextion_queue_.clear(); + this->waveform_queue_.clear(); } void Nextion::dump_config() { @@ -119,14 +130,19 @@ void Nextion::dump_config() { ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str()); ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str()); ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str()); - ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False"); + ESP_LOGCONFIG(TAG, " Wake On Touch: %s", YESNO(this->auto_wake_on_touch_)); + ESP_LOGCONFIG(TAG, " Exit reparse: %s", YESNO(this->exit_reparse_on_start_)); if (this->touch_sleep_timeout_ != 0) { - ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_); + ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu32, this->touch_sleep_timeout_); } if (this->wake_up_page_ != -1) { - ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_); + ESP_LOGCONFIG(TAG, " Wake Up Page: %" PRId16, this->wake_up_page_); + } + + if (this->start_up_page_ != -1) { + ESP_LOGCONFIG(TAG, " Start Up Page: %" PRId16, this->start_up_page_); } } @@ -156,6 +172,10 @@ void Nextion::add_new_page_callback(std::function &&callback) { this->page_callback_.add(std::move(callback)); } +void Nextion::add_touch_event_callback(std::function &&callback) { + this->touch_callback_.add(std::move(callback)); +} + void Nextion::update_all_components() { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; @@ -174,6 +194,17 @@ void Nextion::update_all_components() { } } +bool Nextion::send_command(const char *command) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + + if (this->send_command_(command)) { + this->add_no_result_to_queue_("send_command"); + return true; + } + return false; +} + bool Nextion::send_command_printf(const char *format, ...) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return false; @@ -225,9 +256,14 @@ void Nextion::loop() { this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command. this->set_backlight_brightness(this->brightness_); - this->goto_page("0"); + + // Check if a startup page has been set and send the command + if (this->start_up_page_ != -1) { + this->goto_page(this->start_up_page_); + } this->set_auto_wake_on_touch(this->auto_wake_on_touch_); + this->set_exit_reparse_on_start(this->exit_reparse_on_start_); if (this->touch_sleep_timeout_ != 0) { this->set_touch_sleep_timeout(this->touch_sleep_timeout_); @@ -359,37 +395,21 @@ void Nextion::process_nextion_commands_() { ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; case 0x12: // invalid Waveform ID or Channel # was used + if (this->waveform_queue_.empty()) { + ESP_LOGW(TAG, + "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); + } else { + auto &nb = this->waveform_queue_.front(); + NextionComponentBase *component = nb->component; - if (!this->nextion_queue_.empty()) { - int index = 0; - int found = -1; - for (auto &nb : this->nextion_queue_) { - NextionComponentBase *component = nb->component; - - if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { - ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", - component->get_component_id(), component->get_wave_channel_id()); - - ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", - component->get_component_id(), component->get_wave_channel_id()); - - found = index; - - delete component; // NOLINT(cppcoreguidelines-owning-memory) - delete nb; // NOLINT(cppcoreguidelines-owning-memory) + ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", + component->get_component_id(), component->get_wave_channel_id()); - break; - } - ++index; - } + ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", + component->get_component_id(), component->get_wave_channel_id()); - if (found != -1) { - this->nextion_queue_.erase(this->nextion_queue_.begin() + found); - } else { - ESP_LOGW( - TAG, - "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); - } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->waveform_queue_.pop_front(); } break; case 0x1A: // variable name invalid @@ -434,11 +454,14 @@ void Nextion::process_nextion_commands_() { uint8_t page_id = to_process[0]; uint8_t component_id = to_process[1]; uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press - ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, - touch_event ? "PRESS" : "RELEASE"); + ESP_LOGD(TAG, "Got touch event:"); + ESP_LOGD(TAG, " page_id: %u", page_id); + ESP_LOGD(TAG, " component_id: %u", component_id); + ESP_LOGD(TAG, " event type: %s", touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { touch->process_touch(page_id, component_id, touch_event != 0); } + this->touch_callback_.call(page_id, component_id, touch_event != 0); break; } case 0x66: { // Nextion initiated new page event return data. @@ -449,7 +472,7 @@ void Nextion::process_nextion_commands_() { } uint8_t page_id = to_process[0]; - ESP_LOGD(TAG, "Got new page=%u", page_id); + ESP_LOGD(TAG, "Got new page: %u", page_id); this->page_callback_.call(page_id); break; } @@ -467,7 +490,10 @@ void Nextion::process_nextion_commands_() { uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press - ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); + ESP_LOGD(TAG, "Got touch event:"); + ESP_LOGD(TAG, " x: %u", x); + ESP_LOGD(TAG, " y: %u", y); + ESP_LOGD(TAG, " type: %s", touch_event ? "PRESS" : "RELEASE"); break; } @@ -589,7 +615,9 @@ void Nextion::process_nextion_commands_() { variable_name = to_process.substr(0, index); ++index; - ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0); + ESP_LOGN(TAG, "Got Switch:"); + ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str()); + ESP_LOGN(TAG, " value: %d", to_process[0] != 0); for (auto *switchtype : this->switchtype_) { switchtype->process_bool(variable_name, to_process[index] != 0); @@ -620,7 +648,9 @@ void Nextion::process_nextion_commands_() { value += to_process[i + index + 1] << (8 * i); } - ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value); + ESP_LOGN(TAG, "Got sensor:"); + ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str()); + ESP_LOGN(TAG, " value: %d", value); for (auto *sensor : this->sensortype_) { sensor->process_sensor(variable_name, value); @@ -652,7 +682,9 @@ void Nextion::process_nextion_commands_() { text_value = to_process.substr(index); - ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str()); + ESP_LOGN(TAG, "Got Text Sensor:"); + ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str()); + ESP_LOGN(TAG, " value: %s", text_value.c_str()); // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue; // nq->variable_name = variable_name; @@ -683,7 +715,9 @@ void Nextion::process_nextion_commands_() { variable_name = to_process.substr(0, index); ++index; - ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0); + ESP_LOGN(TAG, "Got Binary Sensor:"); + ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str()); + ESP_LOGN(TAG, " value: %d", to_process[index] != 0); for (auto *binarysensortype : this->binarysensortype_) { binarysensortype->process_bool(&variable_name[0], to_process[index] != 0); @@ -692,44 +726,29 @@ void Nextion::process_nextion_commands_() { } case 0xFD: { // data transparent transmit finished ESP_LOGVV(TAG, "Nextion reported data transmit finished!"); + this->check_pending_waveform_(); break; } case 0xFE: { // data transparent transmit ready ESP_LOGVV(TAG, "Nextion reported ready for transmit!"); - - int index = 0; - int found = -1; - for (auto &nb : this->nextion_queue_) { - auto *component = nb->component; - if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { - size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() - : 255; // ADDT command can only send 255 - - this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); - - ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", - component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); - - if (component->get_wave_buffer().size() <= 255) { - component->get_wave_buffer().clear(); - } else { - component->get_wave_buffer().erase(component->get_wave_buffer().begin(), - component->get_wave_buffer().begin() + buffer_to_send); - } - found = index; - delete component; // NOLINT(cppcoreguidelines-owning-memory) - delete nb; // NOLINT(cppcoreguidelines-owning-memory) - break; - } - ++index; - } - - if (found == -1) { + if (this->waveform_queue_.empty()) { ESP_LOGE(TAG, "No waveforms in queue to send data!"); break; - } else { - this->nextion_queue_.erase(this->nextion_queue_.begin() + found); } + + auto &nb = this->waveform_queue_.front(); + auto *component = nb->component; + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() + : 255; // ADDT command can only send 255 + + this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); + + ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", + component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); + + component->clear_wave_buffer(buffer_to_send); + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->waveform_queue_.pop_front(); break; } default: @@ -788,7 +807,10 @@ void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, } void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) { - ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type); + ESP_LOGN(TAG, "Received state:"); + ESP_LOGN(TAG, " variable: %s", name.c_str()); + ESP_LOGN(TAG, " state: %lf", state); + ESP_LOGN(TAG, " queue type: %d", queue_type); switch (queue_type) { case NextionQueueType::SENSOR: { @@ -825,7 +847,9 @@ void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::s } void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { - ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str()); + ESP_LOGD(TAG, "Received state:"); + ESP_LOGD(TAG, " variable: %s", name.c_str()); + ESP_LOGD(TAG, " state: %s", state.c_str()); for (auto *sensor : this->textsensortype_) { if (name == sensor->get_variable_name()) { @@ -885,6 +909,12 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool start = millis(); while ((timeout == 0 && this->available()) || millis() - start <= timeout) { + if (!this->available()) { + App.feed_wdt(); + delay(1); + continue; + } + this->read_byte(&c); if (c == 0xFF) { nr_of_ff_bytes++; @@ -903,7 +933,7 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool } } App.feed_wdt(); - delay(1); + delay(2); if (exit_flag || ff_flag) { break; @@ -1005,23 +1035,23 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, - const std::string &variable_name_to_send, int state_value) { + const std::string &variable_name_to_send, int32_t state_value) { this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); } void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, - const std::string &variable_name_to_send, int state_value, + const std::string &variable_name_to_send, int32_t state_value, bool is_sleep_safe) { if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) return; - this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(), + this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%" PRId32, variable_name_to_send.c_str(), state_value); } @@ -1088,17 +1118,28 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component = component; nextion_queue->queue_time = millis(); + this->waveform_queue_.push_back(nextion_queue); + if (this->waveform_queue_.size() == 1) + this->check_pending_waveform_(); +} + +void Nextion::check_pending_waveform_() { + if (this->waveform_queue_.empty()) + return; + + auto *nb = this->waveform_queue_.front(); + auto *component = nb->component; size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255; // ADDT command can only send 255 std::string command = "addt " + to_string(component->get_component_id()) + "," + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); - if (this->send_command_(command)) { - this->nextion_queue_.push_back(nextion_queue); + if (!this->send_command_(command)) { + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->waveform_queue_.pop_front(); } } @@ -1107,5 +1148,7 @@ void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = write ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect", "v1.20") void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is deprecated"); } +bool Nextion::is_updating() { return this->is_updating_; } + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 28663138d780..dfa74f644dfa 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -12,14 +12,18 @@ #include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ARDUINO #ifdef USE_ESP32 #include -#endif +#endif // USE_ESP32 #ifdef USE_ESP8266 #include #include -#endif -#endif +#endif // USE_ESP8266 +#elif defined(USE_ESP_IDF) +#include +#endif // ARDUINO vs USE_ESP_IDF +#endif // USE_NEXTION_TFT_UPLOAD namespace esphome { namespace nextion { @@ -46,6 +50,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * This will set the `txt` property of the component `textview` to `Hello World`. */ void set_component_text(const char *component, const char *text); + /** * Set the text of a component to a formatted string * @param component The component name. @@ -62,6 +67,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * For example when `uptime_sensor` = 506, then, `The uptime is: 506` will be displayed. */ void set_component_text_printf(const char *component, const char *format, ...) __attribute__((format(printf, 3, 4))); + /** * Set the integer value of a component * @param component The component name. @@ -74,33 +80,38 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * This will change the property `value` of the component `gauge` to 50. */ - void set_component_value(const char *component, int value); + void set_component_value(const char *component, int32_t value); + /** * Set the picture of an image component. * @param component The component name. - * @param value The picture name. + * @param value The picture id. * * Example: * ```cpp - * it.set_component_picture("pic", "4"); + * it.set_component_picture("pic", 4); * ``` * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture); + void set_component_picture(const char *component, uint8_t picture_id); + /** * Set the background color of a component. * @param component The component name. - * @param color The color (as a uint32_t). + * @param color The color (as a uint16_t). * * Example: * ```cpp - * it.set_component_background_color("button", 0xFF0000); + * it.set_component_background_color("button", 63488); * ``` * * This will change the background color of the component `button` to red. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ - void set_component_background_color(const char *component, uint32_t color); + void set_component_background_color(const char *component, uint16_t color); + /** * Set the background color of a component. * @param component The component name. @@ -111,11 +122,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.set_component_background_color("button", "RED"); * ``` * - * This will change the background color of the component `button` to blue. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * This will change the background color of the component `button` to red. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ void set_component_background_color(const char *component, const char *color); + /** * Set the background color of a component. * @param component The component name. @@ -123,26 +134,31 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.set_component_background_color("button", color); + * auto blue = Color(0, 0, 255); + * it.set_component_background_color("button", blue); * ``` * - * This will change the background color of the component `button` to what color contains. + * This will change the background color of the component `button` to blue. */ void set_component_background_color(const char *component, Color color) override; + /** * Set the pressed background color of a component. * @param component The component name. - * @param color The color (as a int). + * @param color The color (as a uint16_t). * * Example: * ```cpp - * it.set_component_pressed_background_color("button", 0xFF0000 ); + * it.set_component_pressed_background_color("button", 63488); * ``` * * This will change the pressed background color of the component `button` to red. This is the background color that * is shown when the component is pressed. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ - void set_component_pressed_background_color(const char *component, uint32_t color); + void set_component_pressed_background_color(const char *component, uint16_t color); + /** * Set the pressed background color of a component. * @param component The component name. @@ -153,12 +169,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.set_component_pressed_background_color("button", "RED"); * ``` * - * This will change the pressed background color of the component `button` to blue. This is the background color that - * is shown when the component is pressed. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ void set_component_pressed_background_color(const char *component, const char *color); + /** * Set the pressed background color of a component. * @param component The component name. @@ -166,16 +182,109 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.set_component_pressed_background_color("button", color); + * auto red = Color(255, 0, 0); + * it.set_component_pressed_background_color("button", red); * ``` * - * This will change the pressed background color of the component `button` to blue. This is the background color that - * is shown when the component is pressed. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. */ void set_component_pressed_background_color(const char *component, Color color) override; + /** + * Set the foreground color of a component. + * @param component The component name. + * @param color The color (as a uint16_t). + * + * Example: + * ```cpp + * it.set_component_foreground_color("button", 63488); + * ``` + * + * This will change the foreground color of the component `button` to red. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_foreground_color(const char *component, uint16_t color); + + /** + * Set the foreground color of a component. + * @param component The component name. + * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_foreground_color("button", "RED"); + * ``` + * + * This will change the foreground color of the component `button` to red. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. + */ + void set_component_foreground_color(const char *component, const char *color); + + /** + * Set the foreground color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_foreground_color("button", Color::BLACK); + * ``` + * + * This will change the foreground color of the component `button` to black. + */ + void set_component_foreground_color(const char *component, Color color) override; + + /** + * Set the pressed foreground color of a component. + * @param component The component name. + * @param color The color (as a uint16_t). + * + * Example: + * ```cpp + * it.set_component_pressed_foreground_color("button", 63488 ); + * ``` + * + * This will change the pressed foreground color of the component `button` to red. This is the foreground color that + * is shown when the component is pressed. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_pressed_foreground_color(const char *component, uint16_t color); + + /** + * Set the pressed foreground color of a component. + * @param component The component name. + * @param color The color (as a string). + * + * Example: + * ```cpp + * it.set_component_pressed_foreground_color("button", "RED"); + * ``` + * + * This will change the pressed foreground color of the component `button` to red. This is the foreground color that + * is shown when the component is pressed. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. + */ + void set_component_pressed_foreground_color(const char *component, const char *color); + + /** + * Set the pressed foreground color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * auto blue = Color(0, 0, 255); + * it.set_component_pressed_foreground_color("button", blue); + * ``` + * + * This will change the pressed foreground color of the component `button` to blue. This is the foreground color that + * is shown when the component is pressed. + */ + void set_component_pressed_foreground_color(const char *component, Color color) override; + /** * Set the picture id of a component. * @param component The component name. @@ -189,6 +298,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); + /** * Set the background picture id of component. * @param component The component name. @@ -206,16 +316,19 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe /** * Set the font color of a component. * @param component The component name. - * @param color The color (as a uint32_t ). + * @param color The color (as a uint16_t). * * Example: * ```cpp - * it.set_component_font_color("textview", 0xFF0000); + * it.set_component_font_color("textview", 63488); * ``` * * This will change the font color of the component `textview` to a red color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ - void set_component_font_color(const char *component, uint32_t color); + void set_component_font_color(const char *component, uint16_t color); + /** * Set the font color of a component. * @param component The component name. @@ -226,11 +339,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.set_component_font_color("textview", "RED"); * ``` * - * This will change the font color of the component `textview` to a blue color. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * This will change the font color of the component `textview` to a red color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ void set_component_font_color(const char *component, const char *color); + /** * Set the font color of a component. * @param component The component name. @@ -238,27 +351,29 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.set_component_font_color("textview", color); + * it.set_component_font_color("textview", Color::BLACK); * ``` * - * This will change the font color of the component `textview` to a blue color. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * This will change the font color of the component `textview` to black. */ void set_component_font_color(const char *component, Color color) override; + /** * Set the pressed font color of a component. * @param component The component name. - * @param color The color (as a uint32_t). + * @param color The color (as a uint16_t). * * Example: * ```cpp - * it.set_component_pressed_font_color("button", 0xFF0000); + * it.set_component_pressed_font_color("button", 63488); * ``` * * This will change the pressed font color of the component `button` to a red. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. */ - void set_component_pressed_font_color(const char *component, uint32_t color); + void set_component_pressed_font_color(const char *component, uint16_t color); + /** * Set the pressed font color of a component. * @param component The component name. @@ -269,11 +384,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * it.set_component_pressed_font_color("button", "RED"); * ``` * - * This will change the pressed font color of the component `button` to a blue color. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * This will change the pressed font color of the component `button` to a red color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ void set_component_pressed_font_color(const char *component, const char *color); + /** * Set the pressed font color of a component. * @param component The component name. @@ -281,14 +396,13 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.set_component_pressed_font_color("button", color); + * it.set_component_pressed_font_color("button", Color::BLACK); * ``` * - * This will change the pressed font color of the component `button` to a blue color. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * This will change the pressed font color of the component `button` to black. */ void set_component_pressed_font_color(const char *component, Color color) override; + /** * Set the coordinates of a component on screen. * @param component The component name. @@ -302,7 +416,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * This will move the position of the component `pic` to the x coordinate `55` and y coordinate `100`. */ - void set_component_coordinates(const char *component, int x, int y); + void set_component_coordinates(const char *component, uint16_t x, uint16_t y); + /** * Set the font id for a component. * @param component The component name. @@ -316,6 +431,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ void set_component_font(const char *component, uint8_t font_id) override; + /** * Send the current time to the nextion display. * @param time The time instance to send (get this with id(my_time).now() ). @@ -334,6 +450,20 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Switches to the page named `main`. Pages are named in the Nextion Editor. */ void goto_page(const char *page); + + /** + * Show the page with a given id. + * @param page The id of the page. + * + * Example: + * ```cpp + * it.goto_page(2); + * ``` + * + * Switches to the page named `main`. Pages are named in the Nextion Editor. + */ + void goto_page(uint8_t page); + /** * Hide a component. * @param component The component name. @@ -346,6 +476,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Hides the component named `button`. */ void hide_component(const char *component) override; + /** * Show a component. * @param component The component name. @@ -358,6 +489,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Shows the component named `button`. */ void show_component(const char *component) override; + /** * Enable touch for a component. * @param component The component name. @@ -370,6 +502,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Enables touch for component named `button`. */ void enable_component_touch(const char *component); + /** * Disable touch for a component. * @param component The component name. @@ -382,14 +515,17 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Disables touch for component named `button`. */ void disable_component_touch(const char *component); + /** * Add waveform data to a waveform component * @param component_id The integer component id. * @param channel_number The channel number to write to. * @param value The value to write. */ - void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value); - void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value); + void add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value); + + void open_waveform_channel(uint8_t component_id, uint8_t channel_number, uint8_t value); + /** * Display a picture at coordinates. * @param picture_id The picture id. @@ -403,7 +539,28 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Displays the picture who has the id `2` at the x coordinates `15` and y coordinates `25`. */ - void display_picture(int picture_id, int x_start, int y_start); + void display_picture(uint16_t picture_id, uint16_t x_start, uint16_t y_start); + + /** + * Fill a rectangle with a color. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width to draw. + * @param height The height to draw. + * @param color The color to draw with (number). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, 63488); + * ``` + * + * Fills an area that starts at x coordinate `50` and y coordinate `50` with a height of `100` and width of `100` with + * the red color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, uint16_t color); + /** * Fill a rectangle with a color. * @param x1 The starting x coordinate. @@ -418,10 +575,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * ``` * * Fills an area that starts at x coordinate `50` and y coordinate `50` with a height of `100` and width of `100` with - * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to - * convert color codes to Nextion HMI colors + * the red color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ - void fill_area(int x1, int y1, int width, int height, const char *color); + void fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, const char *color); + /** * Fill a rectangle with a color. * @param x1 The starting x coordinate. @@ -432,14 +590,35 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * fill_area(50, 50, 100, 100, color); + * auto blue = Color(0, 0, 255); + * fill_area(50, 50, 100, 100, blue); * ``` * * Fills an area that starts at x coordinate `50` and y coordinate `50` with a height of `100` and width of `100` with - * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to - * convert color codes to Nextion HMI colors + * blue color. */ - void fill_area(int x1, int y1, int width, int height, Color color); + void fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, Color color); + + /** + * Draw a line on the screen. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param x2 The ending x coordinate. + * @param y2 The ending y coordinate. + * @param color The color to draw with (number). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, 63488); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the red color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color); + /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -450,15 +629,15 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.line(50, 50, 75, 75, "17013"); + * it.line(50, 50, 75, 75, "BLUE"); * ``` * * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate - * `75` with the color of blue. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * `75` with the blue color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ - void line(int x1, int y1, int x2, int y2, const char *color); + void line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const char *color); + /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -469,15 +648,35 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.line(50, 50, 75, 75, "17013"); + * auto blue = Color(0, 0, 255); + * it.line(50, 50, 75, 75, blue); * ``` * * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate - * `75` with the color of blue. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * `75` with blue color. */ - void line(int x1, int y1, int x2, int y2, Color color); + void line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, Color color); + + /** + * Draw a rectangle outline. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param color The color to draw with (number). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, 63488); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with the red color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, uint16_t color); + /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -488,15 +687,15 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.rectangle(25, 35, 40, 50, "17013"); + * it.rectangle(25, 35, 40, 50, "BLUE"); * ``` * * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a - * length of `50` with color of blue. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * length of `50` with the blue color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ - void rectangle(int x1, int y1, int width, int height, const char *color); + void rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, const char *color); + /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -507,23 +706,36 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.rectangle(25, 35, 40, 50, "17013"); + * auto blue = Color(0, 0, 255); + * it.rectangle(25, 35, 40, 50, blue); * ``` * * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a - * length of `50` with color of blue. Use this [color - * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI - * colors. + * length of `50` with blue color. */ - void rectangle(int x1, int y1, int width, int height, Color color); + void rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, Color color); + + /** + * Draw a circle outline + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (number). + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t color); + /** * Draw a circle outline * @param center_x The center x coordinate. * @param center_y The center y coordinate. * @param radius The circle radius. * @param color The color to draw with (as a string). + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. */ - void circle(int center_x, int center_y, int radius, const char *color); + void circle(uint16_t center_x, uint16_t center_y, uint16_t radius, const char *color); + /** * Draw a circle outline * @param center_x The center x coordinate. @@ -531,24 +743,43 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @param radius The circle radius. * @param color The color to draw with (as Color). */ - void circle(int center_x, int center_y, int radius, Color color); + void circle(uint16_t center_x, uint16_t center_y, uint16_t radius, Color color); + /** * Draw a filled circled. * @param center_x The center x coordinate. * @param center_y The center y coordinate. * @param radius The circle radius. - * @param color The color to draw with (as a string). + * @param color The color to draw with (number). * * Example: * ```cpp - * it.filled_cricle(25, 25, 10, "17013"); + * it.filled_cricle(25, 25, 10, 63488); * ``` * - * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with the red color. * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to * Nextion HMI colors. */ - void filled_circle(int center_x, int center_y, int radius, const char *color); + void filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t color); + + /** + * Draw a filled circled. + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as a string). + * + * Example: + * ```cpp + * it.filled_cricle(25, 25, 10, "BLUE"); + * ``` + * + * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with the blue color. + * Use [Nextion Instruction Set](https://nextion.tech/instruction-set/#s5) for a list of Nextion HMI colors constants. + */ + void filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, const char *color); + /** * Draw a filled circled. * @param center_x The center x coordinate. @@ -558,14 +789,59 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * Example: * ```cpp - * it.filled_cricle(25, 25, 10, color); + * auto blue = Color(0, 0, 255); + * it.filled_cricle(25, 25, 10, blue); * ``` * - * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with a color of blue. - * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to - * Nextion HMI colors. + * Makes a filled circle at the x coordinate `25` and y coordinate `25` with a radius of `10` with blue color. */ - void filled_circle(int center_x, int center_y, int radius, Color color); + void filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, Color color); + + /** + * Draws a QR code in the screen + * @param x1 The top left x coordinate to start the QR code. + * @param y1 The top left y coordinate to start the QR code. + * @param content The content of the QR code (as a plain text - Nextion will generate the QR code). + * @param size The size (in pixels) for the QR code. Defaults to 200px. + * @param background_color The background color to draw with (as rgb565 integer). Defaults to 65535 (white). + * @param foreground_color The foreground color to draw with (as rgb565 integer). Defaults to 0 (black). + * @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo). + * @param border_width The border width (in pixels) for the QR code. Defaults to 8px. + * + * Example: + * ```cpp + * it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;"); + * ``` + * + * 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); + + /** + * Draws a QR code in the screen + * @param x1 The top left x coordinate to start the QR code. + * @param y1 The top left y coordinate to start the QR code. + * @param content The content of the QR code (as a plain text - Nextion will generate the QR code). + * @param size The size (in pixels) for the QR code. Defaults to 200px. + * @param background_color The background color to draw with (as Color). Defaults to 65535 (white). + * @param foreground_color The foreground color to draw with (as Color). Defaults to 0 (black). + * @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo). + * @param border_width The border width (in pixels) for the QR code. Defaults to 8px. + * + * Example: + * ```cpp + * auto blue = Color(0, 0, 255); + * auto red = Color(255, 0, 0); + * it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;", 150, blue, red); + * ``` + * + * Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25) with size of 150px in + * red on a blue background. + */ + 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); /** Set the brightness of the backlight. * @@ -579,6 +855,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Changes the brightness of the display to 30%. */ void set_backlight_brightness(float brightness); + /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. @@ -592,6 +869,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); + /** * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to @@ -605,6 +883,21 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * The display will wake up to page 2. */ void set_wake_up_page(uint8_t page_id = 255); + + /** + * Sets which page Nextion loads when connecting to ESPHome. + * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to + * wakes up to current page. + * + * Example: + * ```cpp + * it.set_start_up_page(2); + * ``` + * + * The display will go to page 2 when it establishes a connection to ESPHome. + */ + void set_start_up_page(uint8_t page_id = 255); + /** * Sets if Nextion should auto-wake from sleep when touch press occurs. * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, @@ -618,12 +911,47 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * The display will wake up by touch. */ void set_auto_wake_on_touch(bool auto_wake); + + /** + * Sets if Nextion should exit the active reparse mode before the "connect" command is sent + * @param exit_reparse True or false. When exit_reparse is true, the exit reparse command + * will be sent before requesting the connection from Nextion. + * + * Example: + * ```cpp + * it.set_exit_reparse_on_start(true); + * ``` + * + * The display will be requested to leave active reparse mode before setup. + */ + void set_exit_reparse_on_start(bool exit_reparse); + /** * Sets Nextion mode between sleep and awake * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode. */ void sleep(bool sleep); + /** + * @brief Sets the Nextion display's protocol reparse mode. + * + * This function toggles the Nextion display's protocol reparse mode between active and passive. + * In active mode, the display actively parses incoming data. + * In passive mode, it does not parse data unless specifically instructed to do so. + * This is useful for managing how the Nextion display interprets incoming commands, + * especially during initialization or in scenarios where precise control over command processing is needed. + * + * @param active_mode A boolean value indicating the desired reparse mode. + * - true to set the display to active protocol reparse mode, where it actively parses incoming commands. + * - false to set the display to passive protocol reparse mode, where command parsing is done only on explicit + * instruction. + * + * @return bool Returns true if all commands were sent successfully to the Nextion display, indicating that the mode + * was set as expected. Returns false if any of the commands failed to send, indicating that the desired reparse mode + * may not be correctly set. + */ + bool set_protocol_reparse_mode(bool active_mode); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); } @@ -642,6 +970,13 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe // This function has been deprecated void set_wait_for_ack(bool wait_for_ack); + /** + * Manually send a raw command to the display. + * @param command The pcommand, like "page 0" + * @return Whether the send was successful. + */ + bool send_command(const char *command); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -652,16 +987,34 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe #ifdef USE_NEXTION_TFT_UPLOAD /** - * Set the tft file URL. https seems problamtic with arduino.. + * Set the tft file URL. https seems problematic with Arduino.. */ void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } -#endif - /** - * Upload the tft file and softreset the Nextion + * @brief Uploads the TFT file to the Nextion display. + * + * This function initiates the upload of a TFT file to the Nextion display. Users can specify a target baud rate for + * the transfer. If the provided baud rate is not supported by Nextion, the function defaults to using the current + * baud rate set for the display. If no baud rate is specified (or if 0 is passed), the current baud rate is used. + * + * Supported baud rates are: 2400, 4800, 9600, 19200, 31250, 38400, 57600, 115200, 230400, 250000, 256000, 512000 + * and 921600. Selecting a baud rate supported by both the Nextion display and the host hardware is essential for + * ensuring a successful upload process. + * + * @param baud_rate The desired baud rate for the TFT file transfer, specified as an unsigned 32-bit integer. + * If the specified baud rate is not supported, or if 0 is passed, the function will use the current baud rate. + * The default value is 0, which implies using the current baud rate. + * @param exit_reparse If true, the function exits reparse mode before uploading the TFT file. This parameter + * defaults to true, ensuring that the display is ready to receive and apply the new TFT file without needing + * to manually reset or reconfigure. Exiting reparse mode is recommended for most upload scenarios to ensure + * the display properly processes the uploaded file command. + * @return bool True: Transfer completed successfuly, False: Transfer failed. */ - void upload_tft(); + bool upload_tft(uint32_t baud_rate = 0, bool exit_reparse = true); + +#endif // USE_NEXTION_TFT_UPLOAD + void dump_config() override; /** @@ -693,6 +1046,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void add_new_page_callback(std::function &&callback); + /** Add a callback to be notified when Nextion has a touch event. + * + * @param callback The void() callback. + */ + void add_touch_event_callback(std::function &&callback); + void update_all_components(); /** @@ -711,9 +1070,9 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); void set_nextion_text_state(const std::string &name, const std::string &state); - void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, - int state_value) override; + int32_t state_value) override; void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, @@ -729,19 +1088,61 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe this->touch_sleep_timeout_ = touch_sleep_timeout; } void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } + void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; } void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } + void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) { + this->exit_reparse_on_start_ = exit_reparse_on_start; + } + + /** + * @brief Retrieves the number of commands pending in the Nextion command queue. + * + * This function returns the current count of commands that have been queued but not yet processed + * for the Nextion display. The Nextion command queue is used to store commands that are sent to + * the Nextion display for various operations like updating the display, changing interface elements, + * or other interactive features. A larger queue size might indicate a higher processing time or potential + * delays in command execution. This function is useful for monitoring the command flow and managing + * the execution efficiency of the Nextion display interface. + * + * @return size_t The number of commands currently in the Nextion queue. This count includes all commands + * that have been added to the queue and are awaiting processing. + */ + size_t queue_size() { return this->nextion_queue_.size(); } + + /** + * @brief Check if the TFT update process is currently running. + * + * This method provides a way to determine if the Nextion display is in the + * process of updating its TFT firmware. When a TFT update is in progress, + * certain operations or commands may be restricted or could interfere with the + * update process. By checking the state of the update process, the system can + * make informed decisions about performing actions that involve communication + * with the Nextion display. + * + * @return true if the TFT update process is active, indicating that the Nextion + * display is currently updating its firmware. This implies that caution + * should be taken with commands sent to the display to avoid interrupting + * the update process. + * @return false if the TFT update process is not active, indicating that the Nextion + * display is not currently updating its firmware and is in a normal operational + * state, ready to receive and process commands as usual. + */ + bool is_updating() override; protected: std::deque nextion_queue_; + std::deque waveform_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; bool remove_from_q_(bool report_empty = true); + /** * @brief * Sends commands ignoring of the Nextion has been setup. */ bool ignore_is_setup_ = false; + bool nextion_reports_is_setup_ = false; uint8_t nextion_event_; @@ -749,8 +1150,10 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void process_serial_(); bool is_updating_ = false; uint32_t touch_sleep_timeout_ = 0; - int wake_up_page_ = -1; + int16_t wake_up_page_ = -1; + int16_t start_up_page_ = -1; bool auto_wake_on_touch_ = true; + bool exit_reparse_on_start_ = false; /** * Manually send a raw command to the display and don't wait for an acknowledgement packet. @@ -766,42 +1169,60 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe __attribute__((format(printf, 3, 4))); void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, - const std::string &variable_name_to_send, int state_value, + const std::string &variable_name_to_send, int32_t state_value, bool is_sleep_safe = false); void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value, bool is_sleep_safe = false); + void check_pending_waveform_(); + #ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_ESP8266 WiFiClient *wifi_client_{nullptr}; BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; WiFiClient *get_wifi_client_(); -#endif +#endif // USE_ESP8266 + std::string tft_url_; + uint32_t content_length_ = 0; + int tft_size_ = 0; + 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 int contentLength Total size of the file - * @param uint32_t chunk_size - * @return true if success, false for failure. + * @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 content_length_ = 0; - int tft_size_ = 0; - int upload_by_chunks_(HTTPClient *http, int range_start); + int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); +#elif defined(USE_ESP_IDF) + /** + * will request 4096 bytes chunks from the web server + * and send each to Nextion + * @param esp_http_client_handle_t 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_(esp_http_client_handle_t http_client, uint32_t &range_start); +#endif // USE_ARDUINO vs USE_ESP_IDF - bool upload_with_range_(uint32_t range_start, uint32_t range_end); + /** + * Ends the upload process, restart Nextion and, if successful, + * restarts ESP + * @param bool url successful True: Transfer completed successfuly, False: Transfer failed. + * @return bool True: Transfer completed successfuly, False: Transfer failed. + */ + bool upload_end_(bool successful); /** - * start update tft file to nextion. - * - * @param const uint8_t *file_buf - * @param size_t buf_size - * @return true if success, false for failure. + * Returns the ESP Free Heap memory. This is framework independent. + * @return Free Heap in bytes. */ - bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); - void upload_end_(); + uint32_t get_free_heap_(); #endif // USE_NEXTION_TFT_UPLOAD @@ -818,6 +1239,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe CallbackManager sleep_callback_{}; CallbackManager wake_callback_{}; CallbackManager page_callback_{}; + CallbackManager touch_callback_{}; optional writer_; float brightness_{1.0}; @@ -829,22 +1251,15 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void remove_front_no_sensors_(); -#ifdef USE_NEXTION_TFT_UPLOAD - std::string tft_url_; - uint8_t *transfer_buffer_{nullptr}; - size_t transfer_buffer_size_; - bool upload_first_chunk_sent_ = false; -#endif - #ifdef NEXTION_PROTOCOL_LOG void print_queue_members_(); -#endif +#endif // NEXTION_PROTOCOL_LOG void reset_(bool reset_nextion = true); std::string command_data_; bool is_connected_ = false; - uint32_t startup_override_ms_ = 8000; - uint32_t max_q_age_ms_ = 8000; + const uint16_t startup_override_ms_ = 8000; + const uint16_t max_q_age_ms_ = 8000; uint32_t started_ms_ = 0; bool sent_setup_commands_ = false; }; diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index a24fd7406081..b88dd399f89b 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,9 +24,9 @@ class NextionBase; class NextionBase { public: - virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, - const std::string &variable_name_to_send, int state_value) = 0; + const std::string &variable_name_to_send, int32_t state_value) = 0; virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, @@ -39,6 +39,8 @@ class NextionBase { virtual void set_component_background_color(const char *component, Color color) = 0; virtual void set_component_pressed_background_color(const char *component, Color color) = 0; + virtual void set_component_foreground_color(const char *component, Color color) = 0; + virtual void set_component_pressed_foreground_color(const char *component, Color color) = 0; virtual void set_component_font_color(const char *component, Color color) = 0; virtual void set_component_pressed_font_color(const char *component, Color color) = 0; virtual void set_component_font(const char *component, uint8_t font_id) = 0; @@ -46,12 +48,16 @@ class NextionBase { virtual void show_component(const char *component) = 0; virtual void hide_component(const char *component) = 0; + virtual bool is_updating() { return false; } + bool is_sleeping() { return this->is_sleeping_; } bool is_setup() { return this->is_setup_; } + bool is_detected() { return this->is_detected_; } protected: bool is_setup_ = false; bool is_sleeping_ = false; + bool is_detected_ = false; }; } // namespace nextion diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index 0409e5ea6c60..fdd6c74d9929 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -1,6 +1,7 @@ #include "nextion.h" #include "esphome/core/util.h" #include "esphome/core/log.h" +#include namespace esphome { namespace nextion { @@ -10,15 +11,13 @@ static const char *const TAG = "nextion"; void Nextion::soft_reset() { this->send_command_("rest"); } void Nextion::set_wake_up_page(uint8_t page_id) { - if (page_id > 255) { - ESP_LOGD(TAG, "Wake up page of bounds, range 0-255"); - return; - } this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); } +void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; } + void Nextion::set_touch_sleep_timeout(uint16_t timeout) { - if (timeout < 3 || timeout > 65535) { + if (timeout < 3) { ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); return; } @@ -36,9 +35,30 @@ void Nextion::sleep(bool sleep) { } // End sleep safe commands -// Set Colors -void Nextion::set_component_background_color(const char *component, uint32_t color) { - this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color); +// Protocol reparse mode +bool Nextion::set_protocol_reparse_mode(bool active_mode) { + ESP_LOGV(TAG, "Set Nextion protocol reparse mode: %s", YESNO(active_mode)); + this->ignore_is_setup_ = true; // if not in reparse mode setup will fail, so it should be ignored + bool all_commands_sent = true; + if (active_mode) { // Sets active protocol reparse mode + all_commands_sent &= this->send_command_("recmod=1"); + } else { // Sets passive protocol reparse mode + all_commands_sent &= + this->send_command_("DRAKJHSUYDGBNCJHGJKSHBDN"); // To exit active reparse mode this sequence must be sent + all_commands_sent &= this->send_command_("recmod=0"); // Sending recmode=0 twice is recommended + all_commands_sent &= this->send_command_("recmod=0"); + } + if (!this->nextion_reports_is_setup_) { // No need to connect if is already setup + all_commands_sent &= this->send_command_("connect"); + } + this->ignore_is_setup_ = false; + return all_commands_sent; +} +void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; } + +// Set Colors - Background +void Nextion::set_component_background_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%" PRIu16, component, color); } void Nextion::set_component_background_color(const char *component, const char *color) { @@ -50,8 +70,10 @@ void Nextion::set_component_background_color(const char *component, Color color) display::ColorUtil::color_to_565(color)); } -void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) { - this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color); +// Set Colors - Background (pressed) +void Nextion::set_component_pressed_background_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%" PRIu16, component, + color); } void Nextion::set_component_pressed_background_color(const char *component, const char *color) { @@ -63,16 +85,38 @@ void Nextion::set_component_pressed_background_color(const char *component, Colo display::ColorUtil::color_to_565(color)); } -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=%d", component, pic_id); +// Set Colors - Foreground +void Nextion::set_component_foreground_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_foreground_color", "%s.pco=%" PRIu16, component, color); } -void Nextion::set_component_picc(const char *component, uint8_t pic_id) { - this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id); +void Nextion::set_component_foreground_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_foreground_color", "%s.pco=%s", component, color); +} + +void Nextion::set_component_foreground_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_foreground_color", "%s.pco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +// Set Colors - Foreground (pressed) +void Nextion::set_component_pressed_foreground_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_foreground_color", "%s.pco2=%" PRIu16, component, + color); } -void Nextion::set_component_font_color(const char *component, uint32_t color) { - this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color); +void Nextion::set_component_pressed_foreground_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_foreground_color", " %s.pco2=%s", component, color); +} + +void Nextion::set_component_pressed_foreground_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_foreground_color", "%s.pco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +// Set Colors - Font +void Nextion::set_component_font_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%" PRIu16, component, color); } void Nextion::set_component_font_color(const char *component, const char *color) { @@ -84,8 +128,9 @@ void Nextion::set_component_font_color(const char *component, Color color) { display::ColorUtil::color_to_565(color)); } -void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) { - this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color); +// Set Colors - Font (pressed) +void Nextion::set_component_pressed_font_color(const char *component, uint16_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%" PRIu16, component, color); } void Nextion::set_component_pressed_font_color(const char *component, const char *color) { @@ -97,6 +142,15 @@ void Nextion::set_component_pressed_font_color(const char *component, Color colo display::ColorUtil::color_to_565(color)); } +// 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_picc(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%" PRIu8, component, pic_id); +} + void Nextion::set_component_text_printf(const char *component, const char *format, ...) { va_list arg; va_start(arg, format); @@ -109,6 +163,7 @@ void Nextion::set_component_text_printf(const char *component, const char *forma // General Nextion void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); } +void Nextion::goto_page(uint8_t page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %i", page); } void Nextion::set_backlight_brightness(float brightness) { if (brightness < 0 || brightness > 1.0) { @@ -124,7 +179,7 @@ void Nextion::set_auto_wake_on_touch(bool auto_wake) { // General Component void Nextion::set_component_font(const char *component, uint8_t font_id) { - this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id); + this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%" PRIu8, component, font_id); } void Nextion::hide_component(const char *component) { @@ -143,80 +198,131 @@ 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, const char *picture) { - this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +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); } -void Nextion::set_component_value(const char *component, int value) { - this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value); +void Nextion::set_component_value(const char *component, int32_t value) { + this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%" PRId32, component, value); } -void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { - this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value); +void Nextion::add_waveform_data(uint8_t component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, + channel_number, value); } -void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) { - this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number, - value); +void Nextion::open_waveform_channel(uint8_t component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %" PRIu8 ",%" PRIu8 ",%" PRIu8, component_id, + channel_number, value); } -void Nextion::set_component_coordinates(const char *component, int x, int y) { - this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x); - this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y); +void Nextion::set_component_coordinates(const char *component, uint16_t x, uint16_t y) { + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%" PRIu16, component, x); + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%" PRIu16, component, y); } // Drawing -void Nextion::display_picture(int picture_id, int x_start, int y_start) { - this->add_no_result_to_queue_with_printf_("display_picture", "pic %d, %d, %d", x_start, y_start, picture_id); +void Nextion::display_picture(uint16_t picture_id, uint16_t x_start, uint16_t y_start) { + this->add_no_result_to_queue_with_printf_("display_picture", "pic %" PRIu16 ", %" PRIu16 ", %" PRIu16, x_start, + y_start, picture_id); } -void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { - this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color); +void Nextion::fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, uint16_t color) { + this->add_no_result_to_queue_with_printf_( + "fill_area", "fill %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, y1, width, height, color); } -void Nextion::fill_area(int x1, int y1, int width, int height, Color color) { - this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height, - display::ColorUtil::color_to_565(color)); +void Nextion::fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, const char *color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%s", x1, + y1, width, height, color); } -void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { - this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); +void Nextion::fill_area(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, Color color) { + this->add_no_result_to_queue_with_printf_("fill_area", + "fill %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, y1, + width, height, display::ColorUtil::color_to_565(color)); } -void Nextion::line(int x1, int y1, int x2, int y2, Color color) { - this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2, - display::ColorUtil::color_to_565(color)); +void Nextion::line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { + this->add_no_result_to_queue_with_printf_("line", "line %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, + y1, x2, y2, color); } -void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { - this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); +void Nextion::line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const char *color) { + this->add_no_result_to_queue_with_printf_("line", "line %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%s", x1, y1, + x2, y2, color); } -void Nextion::rectangle(int x1, int y1, int width, int height, Color color) { - this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height, - display::ColorUtil::color_to_565(color)); +void Nextion::line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, Color color) { + this->add_no_result_to_queue_with_printf_("line", "line %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, + y1, x2, y2, display::ColorUtil::color_to_565(color)); } -void Nextion::circle(int center_x, int center_y, int radius, const char *color) { - this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color); +void Nextion::rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, uint16_t color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, + y1, static_cast(x1 + width), static_cast(y1 + height), + color); } -void Nextion::circle(int center_x, int center_y, int radius, Color color) { - this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius, +void Nextion::rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, const char *color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%s", x1, y1, + static_cast(x1 + width), static_cast(y1 + height), + color); +} + +void Nextion::rectangle(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, Color color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, x1, + y1, static_cast(x1 + width), static_cast(y1 + height), display::ColorUtil::color_to_565(color)); } -void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { - this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color); +void Nextion::circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, center_x, + center_y, radius, color); } -void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) { - this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius, - display::ColorUtil::color_to_565(color)); +void Nextion::circle(uint16_t center_x, uint16_t center_y, uint16_t radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%s", center_x, center_y, + radius, color); +} + +void Nextion::circle(uint16_t center_x, uint16_t center_y, uint16_t radius, Color color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, center_x, + center_y, radius, display::ColorUtil::color_to_565(color)); +} + +void Nextion::filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, center_x, + center_y, radius, color); +} + +void Nextion::filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%s", center_x, center_y, + radius, color); +} + +void Nextion::filled_circle(uint16_t center_x, uint16_t center_y, uint16_t radius, Color color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %" PRIu16 ",%" PRIu16 ",%" PRIu16 ",%" PRIu16, center_x, + center_y, radius, display::ColorUtil::color_to_565(color)); +} + +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) { + 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) { + 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), + logo_pic, border_width, content); } void Nextion::set_nextion_rtc_time(ESPTime time) { diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp index bbb2cf6cb226..cfb4e3600c8a 100644 --- a/esphome/components/nextion/nextion_component.cpp +++ b/esphome/components/nextion/nextion_component.cpp @@ -99,11 +99,11 @@ void NextionComponent::update_component_settings(bool force_update) { this->bco2_needs_update_ = false; } if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { - this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_); + this->nextion_->set_component_foreground_color(this->variable_name_.c_str(), this->pco_); this->pco_needs_update_ = false; } if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { - this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_); + this->nextion_->set_component_pressed_foreground_color(this->variable_name_.c_str(), this->pco2_); this->pco2_needs_update_ = false; } diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index e0ef8f93bcb2..42e1b0099847 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -69,6 +69,13 @@ class NextionComponentBase { std::vector get_wave_buffer() { return this->wave_buffer_; } size_t get_wave_buffer_size() { return this->wave_buffer_.size(); } + void clear_wave_buffer(size_t buffer_sent) { + if (this->wave_buffer_.size() <= buffer_sent) { + this->wave_buffer_.clear(); + } else { + this->wave_buffer_.erase(this->wave_buffer_.begin(), this->wave_buffer_.begin() + buffer_sent); + } + } std::string get_variable_name() { return this->variable_name_; } std::string get_variable_name_to_send() { return this->variable_name_to_send_; } diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp deleted file mode 100644 index 9e6884398cb6..000000000000 --- a/esphome/components/nextion/nextion_upload.cpp +++ /dev/null @@ -1,339 +0,0 @@ -#include "nextion.h" - -#ifdef USE_NEXTION_TFT_UPLOAD - -#include "esphome/core/application.h" -#include "esphome/core/defines.h" -#include "esphome/core/util.h" -#include "esphome/core/log.h" -#include "esphome/components/network/util.h" - -#ifdef USE_ESP32 -#include -#endif - -namespace esphome { -namespace nextion { -static const char *const TAG = "nextion_upload"; - -// Followed guide -// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 - -int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { - int range_end = 0; - - if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip - range_end = 16384 - 1; - } else { - range_end = range_start + this->transfer_buffer_size_ - 1; - } - - if (range_end > this->tft_size_) - range_end = this->tft_size_; - -#ifdef USE_ESP8266 -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http->setFollowRedirects(true); -#endif -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http->setRedirectLimit(3); -#endif -#endif - - char range_header[64]; - sprintf(range_header, "bytes=%d-%d", range_start, range_end); - - ESP_LOGD(TAG, "Requesting range: %s", range_header); - - int tries = 1; - int code = 0; - bool begin_status = false; - while (tries <= 5) { -#ifdef USE_ESP32 - begin_status = http->begin(this->tft_url_.c_str()); -#endif -#ifdef USE_ESP8266 - begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif - - ++tries; - if (!begin_status) { - ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); - continue; - } - - http->addHeader("Range", range_header); - - code = http->GET(); - if (code == 200 || code == 206) { - break; - } - ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), - HTTPClient::errorToString(code).c_str(), tries); - http->end(); - App.feed_wdt(); - delay(500); // NOLINT - } - - if (tries > 5) { - return -1; - } - - std::string recv_string; - size_t size = 0; - int sent = 0; - int range = range_end - range_start; - - while (sent < range) { - size = http->getStreamPtr()->available(); - if (!size) { - App.feed_wdt(); - delay(0); - continue; - } - int c = http->getStreamPtr()->readBytes( - &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); - sent += c; - } - http->end(); - ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); - for (int i = 0; i < range; i += 4096) { - this->write_array(&this->transfer_buffer_[i], 4096); - this->content_length_ -= 4096; - ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, - range_end, range_start); - - if (!this->upload_first_chunk_sent_) { - this->upload_first_chunk_sent_ = true; - delay(500); // NOLINT - App.feed_wdt(); - } - - this->recv_ret_string_(recv_string, 2048, true); - if (recv_string[0] == 0x08) { - uint32_t result = 0; - for (int i = 0; i < 4; ++i) { - result += static_cast(recv_string[i + 1]) << (8 * i); - } - if (result > 0) { - ESP_LOGD(TAG, "Nextion reported new range %d", result); - this->content_length_ = this->tft_size_ - result; - return result; - } - } - recv_string.clear(); - } - return range_end + 1; -} - -void Nextion::upload_tft() { - if (this->is_updating_) { - ESP_LOGD(TAG, "Currently updating"); - return; - } - - if (!network::is_connected()) { - ESP_LOGD(TAG, "network is not connected"); - return; - } - - this->is_updating_ = true; - - HTTPClient http; - http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along - bool begin_status = false; -#ifdef USE_ESP32 - begin_status = http.begin(this->tft_url_.c_str()); -#endif -#ifdef USE_ESP8266 -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http.setFollowRedirects(true); -#endif -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http.setRedirectLimit(3); -#endif - begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif - - if (!begin_status) { - this->is_updating_ = false; - ESP_LOGD(TAG, "connection failed"); - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); - allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_); - return; - } else { - ESP_LOGD(TAG, "Connected"); - } - - http.addHeader("Range", "bytes=0-255"); - const char *header_names[] = {"Content-Range"}; - http.collectHeaders(header_names, 1); - ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); - - http.setReuse(true); - // try up to 5 times. DNS sometimes needs a second try or so - int tries = 1; - int code = http.GET(); - delay(100); // NOLINT - - App.feed_wdt(); - while (code != 200 && code != 206 && tries <= 5) { - ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), - HTTPClient::errorToString(code).c_str(), tries); - - delay(250); // NOLINT - App.feed_wdt(); - code = http.GET(); - ++tries; - } - - if ((code != 200 && code != 206) || tries > 5) { - this->upload_end_(); - } - - String content_range_string = http.header("Content-Range"); - content_range_string.remove(0, 12); - this->content_length_ = content_range_string.toInt(); - this->tft_size_ = content_length_; - http.end(); - - if (this->content_length_ < 4096) { - ESP_LOGE(TAG, "Failed to get file size"); - this->upload_end_(); - } - - ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); - // The Nextion will ignore the update command if it is sleeping - - this->send_command_("sleep=0"); - this->set_backlight_brightness(1.0); - delay(250); // NOLINT - - 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_, this->parent_->get_baud_rate()); - - // Clear serial receive buffer - uint8_t d; - while (this->available()) { - this->read_byte(&d); - }; - - this->send_command_(command); - - App.feed_wdt(); - - std::string response; - ESP_LOGD(TAG, "Waiting for upgrade response"); - this->recv_ret_string_(response, 2000, true); // This can take some time to return - - // The Nextion display will, if it's ready to accept data, send a 0x05 byte. - ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); - - for (size_t i = 0; i < response.length(); i++) { - ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); - } - - if (response.find(0x05) != std::string::npos) { - ESP_LOGD(TAG, "preparation for tft update done"); - } else { - ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str()); - this->upload_end_(); - } - - // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 -#ifdef USE_ESP32 - uint32_t chunk_size = 8192; - if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) { - chunk_size = this->content_length_; - } else { - if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand - int chunk = int((ESP.getFreeHeap() - 32768) / 4096); - chunk_size = chunk * 4096; - chunk_size = chunk_size > 65536 ? 65536 : chunk_size; - } else if (ESP.getFreeHeap() < 10240) { - chunk_size = 4096; - } - } -#else - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; -#endif - - if (this->transfer_buffer_ == nullptr) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = allocator.allocate(chunk_size); - if (this->transfer_buffer_ == nullptr) { // Try a smaller size - ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); - chunk_size = 4096; - ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = allocator.allocate(chunk_size); - - if (!this->transfer_buffer_) - this->upload_end_(); - } - - this->transfer_buffer_size_ = chunk_size; - } - - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", - this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); - - int result = 0; - while (this->content_length_ > 0) { - result = this->upload_by_chunks_(&http, result); - if (result < 0) { - ESP_LOGD(TAG, "Error updating Nextion!"); - this->upload_end_(); - } - App.feed_wdt(); - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); - } - ESP_LOGD(TAG, "Successfully updated Nextion!"); - - this->upload_end_(); -} - -void Nextion::upload_end_() { - ESP_LOGD(TAG, "Restarting Nextion"); - this->soft_reset(); - delay(1500); // NOLINT - ESP_LOGD(TAG, "Restarting esphome"); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) -} - -#ifdef USE_ESP8266 -WiFiClient *Nextion::get_wifi_client_() { - if (this->tft_url_.compare(0, 6, "https:") == 0) { - if (this->wifi_client_secure_ == nullptr) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); - this->wifi_client_secure_->setInsecure(); - this->wifi_client_secure_->setBufferSizes(512, 512); - } - return this->wifi_client_secure_; - } - - if (this->wifi_client_ == nullptr) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - this->wifi_client_ = new WiFiClient(); - } - return this->wifi_client_; -} -#endif -} // namespace nextion -} // namespace esphome - -#endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp new file mode 100644 index 000000000000..1187c77c8ed7 --- /dev/null +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -0,0 +1,387 @@ +#include "nextion.h" + +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ARDUINO + +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" +#include "esphome/components/network/util.h" +#include + +#ifdef USE_ESP32 +#include +#endif + +namespace esphome { +namespace nextion { +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, "Free heap: %" PRIu32, this->get_free_heap_()); + 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"); + return -1; + } + + char range_header[32]; + sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end); + ESP_LOGV(TAG, "Requesting range: %s", range_header); + http_client.addHeader("Range", range_header); + int code = http_client.GET(); + if (code != HTTP_CODE_OK and code != HTTP_CODE_PARTIAL_CONTENT) { + ESP_LOGW(TAG, "HTTP Request failed; Error: %s", HTTPClient::errorToString(code).c_str()); + return -1; + } + + // Allocate the buffer dynamically + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + uint8_t *buffer = allocator.allocate(4096); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate upload buffer"); + return -1; + } + + std::string recv_string; + while (true) { + App.feed_wdt(); + const uint16_t buffer_size = + this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data + ESP_LOGV(TAG, "Fetching %" PRIu16 " bytes from HTTP", buffer_size); + uint16_t read_len = 0; + int partial_read_len = 0; + const uint32_t start_time = millis(); + while (read_len < buffer_size && millis() - start_time < 5000) { + if (http_client.getStreamPtr()->available() > 0) { + partial_read_len = + http_client.getStreamPtr()->readBytes(reinterpret_cast(buffer) + read_len, buffer_size - read_len); + read_len += partial_read_len; + if (partial_read_len > 0) { + App.feed_wdt(); + delay(2); + } + } + } + if (read_len != buffer_size) { + // Did not receive the full package within the timeout period + ESP_LOGE(TAG, "Failed to read full package, received only %" PRIu16 " of %" PRIu16 " bytes", read_len, + buffer_size); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + ESP_LOGV(TAG, "%d bytes fetched, writing it to UART", read_len); + if (read_len > 0) { + recv_string.clear(); + this->write_array(buffer, buffer_size); + App.feed_wdt(); + 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, + "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " (DRAM) + %" PRIu32 " (PSRAM) bytes", + 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, "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " bytes", upload_percentage, + this->content_length_, this->get_free_heap_()); +#endif + upload_first_chunk_sent_ = true; + if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request + ESP_LOGD(TAG, "recv_string [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + uint32_t result = 0; + for (int j = 0; j < 4; ++j) { + result += static_cast(recv_string[j + 1]) << (8 * j); + } + if (result > 0) { + ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); + this->content_length_ = this->tft_size_ - result; + range_start = result; + } else { + range_start = range_end + 1; + } + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return range_end + 1; + } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" + ESP_LOGE(TAG, "Invalid response from Nextion: [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + + recv_string.clear(); + } else if (read_len == 0) { + ESP_LOGV(TAG, "End of HTTP response reached"); + break; // Exit the loop if there is no more data to read + } else { + ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %d", read_len); + break; // Exit the loop on error + } + } + range_start = range_end + 1; + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return range_end + 1; +} + +bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { + ESP_LOGD(TAG, "Nextion TFT upload requested"); + ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); + ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + + if (this->is_updating_) { + ESP_LOGW(TAG, "Currently uploading"); + return false; + } + + if (!network::is_connected()) { + ESP_LOGE(TAG, "Network is not connected"); + return false; + } + + this->is_updating_ = true; + + if (exit_reparse) { + ESP_LOGD(TAG, "Exiting Nextion reparse mode"); + if (!this->set_protocol_reparse_mode(false)) { + ESP_LOGW(TAG, "Failed to request Nextion to exit reparse mode"); + return false; + } + } + + // Check if baud rate is supported + this->original_baud_rate_ = this->parent_->get_baud_rate(); + static const std::vector SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600, + 115200, 230400, 250000, 256000, 512000, 921600}; + if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) { + baud_rate = this->original_baud_rate_; + } + ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); + + // Define the configuration for the HTTP client + ESP_LOGV(TAG, "Initializing HTTP client"); + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + 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); +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http_client.setFollowRedirects(true); +#endif +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http_client.setRedirectLimit(3); +#endif + begin_status = http_client.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif // USE_ESP8266 + if (!begin_status) { + this->is_updating_ = false; + ESP_LOGD(TAG, "Connection failed"); + return false; + } else { + ESP_LOGD(TAG, "Connected"); + } + http_client.addHeader("Range", "bytes=0-255"); + const char *header_names[] = {"Content-Range"}; + http_client.collectHeaders(header_names, 1); + ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); + http_client.setReuse(true); + // try up to 5 times. DNS sometimes needs a second try or so + int tries = 1; + int code = http_client.GET(); + delay(100); // NOLINT + + App.feed_wdt(); + while (code != 200 && code != 206 && tries <= 5) { + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + + delay(250); // NOLINT + App.feed_wdt(); + code = http_client.GET(); + ++tries; + } + + if (code != 200 and code != 206) { + return this->upload_end_(false); + } + + String content_range_string = http_client.header("Content-Range"); + content_range_string.remove(0, 12); + this->tft_size_ = content_range_string.toInt(); + + ESP_LOGD(TAG, "TFT file size: %zu bytes", this->tft_size_); + if (this->tft_size_ < 4096) { + ESP_LOGE(TAG, "File size check failed."); + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } else { + ESP_LOGV(TAG, "File size check passed. Proceeding..."); + } + this->content_length_ = this->tft_size_; + + ESP_LOGD(TAG, "Uploading Nextion"); + + // The Nextion will ignore the upload command if it is sleeping + ESP_LOGV(TAG, "Wake-up Nextion"); + this->ignore_is_setup_ = true; + this->send_command_("sleep=0"); + this->send_command_("dim=100"); + delay(250); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + + 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); + + // Clear serial receive buffer + ESP_LOGV(TAG, "Clear serial receive buffer"); + this->reset_(false); + delay(250); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + + ESP_LOGV(TAG, "Send upload instruction: %s", command); + this->send_command_(command); + + if (baud_rate != this->original_baud_rate_) { + ESP_LOGD(TAG, "Changing baud rate from %" PRIu32 " to %" PRIu32 " bps", this->original_baud_rate_, baud_rate); + this->parent_->set_baud_rate(baud_rate); + this->parent_->load_settings(); + } + + App.feed_wdt(); + + std::string response; + ESP_LOGV(TAG, "Waiting for upgrade response"); + 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. + ESP_LOGD(TAG, "Upgrade response is [%s] - %zu byte(s)", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + response.length()); + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + + if (response.find(0x05) != std::string::npos) { + ESP_LOGV(TAG, "Preparation for TFT upload done"); + } else { + ESP_LOGE(TAG, "Preparation for TFT upload failed %d \"%s\"", response[0], response.c_str()); + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } + + ESP_LOGD(TAG, "Uploading TFT to Nextion:"); + ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, " File size: %d bytes", this->content_length_); + ESP_LOGD(TAG, " Free heap: %" PRIu32, this->get_free_heap_()); + + // Proceed with the content download as before + + ESP_LOGV(TAG, "Starting transfer by chunks loop"); + + uint32_t position = 0; + while (this->content_length_ > 0) { + int upload_result = upload_by_chunks_(http_client, position); + if (upload_result < 0) { + ESP_LOGE(TAG, "Error uploading TFT to Nextion!"); + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } + App.feed_wdt(); + ESP_LOGV(TAG, "Free heap: %" PRIu32 ", Bytes left: %" PRIu32, this->get_free_heap_(), this->content_length_); + } + + ESP_LOGD(TAG, "Successfully uploaded TFT to Nextion!"); + + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); + return upload_end_(true); +} + +bool Nextion::upload_end_(bool successful) { + ESP_LOGD(TAG, "Nextion TFT upload finished: %s", YESNO(successful)); + this->is_updating_ = false; + this->ignore_is_setup_ = false; + + uint32_t baud_rate = this->parent_->get_baud_rate(); + if (baud_rate != this->original_baud_rate_) { + ESP_LOGD(TAG, "Changing baud rate back from %" PRIu32 " to %" PRIu32 " bps", baud_rate, this->original_baud_rate_); + this->parent_->set_baud_rate(this->original_baud_rate_); + this->parent_->load_settings(); + } + + if (successful) { + ESP_LOGD(TAG, "Restarting ESPHome"); + delay(1500); // NOLINT + arch_restart(); + } else { + ESP_LOGE(TAG, "Nextion TFT upload failed"); + } + return successful; +} + +#ifdef USE_ESP8266 +WiFiClient *Nextion::get_wifi_client_() { + if (this->tft_url_.compare(0, 6, "https:") == 0) { + if (this->wifi_client_secure_ == nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_->setInsecure(); + this->wifi_client_secure_->setBufferSizes(512, 512); + } + return this->wifi_client_secure_; + } + + if (this->wifi_client_ == nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_ = new WiFiClient(); + } + return this->wifi_client_; +} +#endif // USE_ESP8266 + +} // namespace nextion +} // namespace esphome + +#endif // USE_ARDUINO +#endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp new file mode 100644 index 000000000000..448b6fc0fff6 --- /dev/null +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -0,0 +1,367 @@ +#include "nextion.h" + +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ESP_IDF + +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" +#include "esphome/components/network/util.h" +#include +#include +#include + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion.upload.idf"; + +// Followed guide +// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 + +int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start) { + uint32_t range_size = this->tft_size_ - range_start; + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + 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"); + return -1; + } + + char range_header[32]; + sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end); + ESP_LOGV(TAG, "Requesting range: %s", range_header); + esp_http_client_set_header(http_client, "Range", range_header); + ESP_LOGV(TAG, "Opening HTTP connetion"); + esp_err_t err; + if ((err = esp_http_client_open(http_client, 0)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + return -1; + } + + ESP_LOGV(TAG, "Fetch content length"); + const int chunk_size = esp_http_client_fetch_headers(http_client); + ESP_LOGV(TAG, "content_length = %d", chunk_size); + if (chunk_size <= 0) { + ESP_LOGE(TAG, "Failed to get chunk's content length: %d", chunk_size); + return -1; + } + + // Allocate the buffer dynamically + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + uint8_t *buffer = allocator.allocate(4096); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate upload buffer"); + return -1; + } + + std::string recv_string; + while (true) { + App.feed_wdt(); + const uint16_t buffer_size = + this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data + ESP_LOGV(TAG, "Fetching %" PRIu16 " bytes from HTTP", buffer_size); + uint16_t read_len = 0; + int partial_read_len = 0; + uint8_t retries = 0; + // Attempt to read the chunk with retries. + while (retries < 5 && read_len < buffer_size) { + partial_read_len = + esp_http_client_read(http_client, reinterpret_cast(buffer) + read_len, buffer_size - read_len); + if (partial_read_len > 0) { + read_len += partial_read_len; // Accumulate the total read length. + // Reset retries on successful read. + retries = 0; + } else { + // If no data was read, increment retries. + retries++; + vTaskDelay(pdMS_TO_TICKS(2)); // NOLINT + } + App.feed_wdt(); // Feed the watchdog timer. + } + if (read_len != buffer_size) { + // Did not receive the full package within the timeout period + ESP_LOGE(TAG, "Failed to read full package, received only %" PRIu16 " of %" PRIu16 " bytes", read_len, + buffer_size); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + ESP_LOGV(TAG, "%d bytes fetched, writing it to UART", read_len); + if (read_len > 0) { + recv_string.clear(); + this->write_array(buffer, buffer_size); + App.feed_wdt(); + 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_; +#ifdef USE_PSRAM + ESP_LOGD( + TAG, + "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " (DRAM) + %" PRIu32 " (PSRAM) bytes", + 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, "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " bytes", upload_percentage, + this->content_length_, static_cast(esp_get_free_heap_size())); +#endif + upload_first_chunk_sent_ = true; + if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request + ESP_LOGD(TAG, "recv_string [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + uint32_t result = 0; + for (int j = 0; j < 4; ++j) { + result += static_cast(recv_string[j + 1]) << (8 * j); + } + if (result > 0) { + ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); + this->content_length_ = this->tft_size_ - result; + range_start = result; + } else { + range_start = range_end + 1; + } + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return range_end + 1; + } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" + ESP_LOGE(TAG, "Invalid response from Nextion: [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + + recv_string.clear(); + } else if (read_len == 0) { + ESP_LOGV(TAG, "End of HTTP response reached"); + break; // Exit the loop if there is no more data to read + } else { + ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %" PRIu16, read_len); + break; // Exit the loop on error + } + } + range_start = range_end + 1; + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return range_end + 1; +} + +bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { + ESP_LOGD(TAG, "Nextion TFT upload requested"); + ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); + ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + + if (this->is_updating_) { + ESP_LOGW(TAG, "Currently uploading"); + return false; + } + + if (!network::is_connected()) { + ESP_LOGE(TAG, "Network is not connected"); + return false; + } + + this->is_updating_ = true; + + if (exit_reparse) { + ESP_LOGD(TAG, "Exiting Nextion reparse mode"); + if (!this->set_protocol_reparse_mode(false)) { + ESP_LOGW(TAG, "Failed to request Nextion to exit reparse mode"); + return false; + } + } + + // Check if baud rate is supported + this->original_baud_rate_ = this->parent_->get_baud_rate(); + static const std::vector SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600, + 115200, 230400, 250000, 256000, 512000, 921600}; + if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) { + baud_rate = this->original_baud_rate_; + } + ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); + + // Define the configuration for the HTTP client + ESP_LOGV(TAG, "Initializing HTTP client"); + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + esp_http_client_config_t config = { + .url = this->tft_url_.c_str(), + .cert_pem = nullptr, + .method = HTTP_METHOD_HEAD, + .timeout_ms = 15000, + .disable_auto_redirect = false, + .max_redirection_count = 10, + }; + // Initialize the HTTP client with the configuration + esp_http_client_handle_t http_client = esp_http_client_init(&config); + if (!http_client) { + ESP_LOGE(TAG, "Failed to initialize HTTP client."); + return this->upload_end_(false); + } + + esp_err_t err = esp_http_client_set_header(http_client, "Connection", "keep-alive"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP set header failed: %s", esp_err_to_name(err)); + esp_http_client_cleanup(http_client); + return this->upload_end_(false); + } + + // Perform the HTTP request + ESP_LOGV(TAG, "Check if the client could connect"); + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + err = esp_http_client_perform(http_client); + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); + esp_http_client_cleanup(http_client); + return this->upload_end_(false); + } + + // Check the HTTP Status Code + ESP_LOGV(TAG, "Check the HTTP Status Code"); + ESP_LOGV(TAG, "Free 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); + } + + this->tft_size_ = esp_http_client_get_content_length(http_client); + + ESP_LOGD(TAG, "TFT file size: %zu bytes", this->tft_size_); + if (this->tft_size_ < 4096 || this->tft_size_ > 134217728) { + ESP_LOGE(TAG, "File size check failed."); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } else { + ESP_LOGV(TAG, "File size check passed. Proceeding..."); + } + this->content_length_ = this->tft_size_; + + ESP_LOGD(TAG, "Uploading Nextion"); + + // The Nextion will ignore the upload command if it is sleeping + ESP_LOGV(TAG, "Wake-up Nextion"); + this->ignore_is_setup_ = true; + this->send_command_("sleep=0"); + this->send_command_("dim=100"); + vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + + 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,%" PRIu32 ",1", this->content_length_, baud_rate); + + // Clear serial receive buffer + ESP_LOGV(TAG, "Clear serial receive buffer"); + this->reset_(false); + vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + + ESP_LOGV(TAG, "Send upload instruction: %s", command); + this->send_command_(command); + + if (baud_rate != this->original_baud_rate_) { + ESP_LOGD(TAG, "Changing baud rate from %" PRIu32 " to %" PRIu32 " bps", this->original_baud_rate_, baud_rate); + this->parent_->set_baud_rate(baud_rate); + this->parent_->load_settings(); + } + + std::string response; + ESP_LOGV(TAG, "Waiting for upgrade response"); + 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. + ESP_LOGD(TAG, "Upgrade response is [%s] - %zu byte(s)", + format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + response.length()); + ESP_LOGV(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + + if (response.find(0x05) != std::string::npos) { + ESP_LOGV(TAG, "Preparation for TFT upload done"); + } else { + ESP_LOGE(TAG, "Preparation for TFT upload failed %d \"%s\"", response[0], response.c_str()); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } + + ESP_LOGV(TAG, "Change the method to GET before starting the download"); + esp_err_t set_method_result = esp_http_client_set_method(http_client, HTTP_METHOD_GET); + if (set_method_result != ESP_OK) { + ESP_LOGE(TAG, "Failed to set HTTP method to GET: %s", esp_err_to_name(set_method_result)); + return this->upload_end_(false); + } + + ESP_LOGD(TAG, "Uploading TFT to Nextion:"); + ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, " File size: %" PRIu32 " bytes", this->content_length_); + ESP_LOGD(TAG, " Free heap: %" PRIu32, esp_get_free_heap_size()); + + // Proceed with the content download as before + + ESP_LOGV(TAG, "Starting transfer by chunks loop"); + + uint32_t position = 0; + while (this->content_length_ > 0) { + int upload_result = upload_by_chunks_(http_client, position); + if (upload_result < 0) { + ESP_LOGE(TAG, "Error uploading TFT to Nextion!"); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(false); + } + App.feed_wdt(); + ESP_LOGV(TAG, "Free heap: %" PRIu32 ", Bytes left: %" PRIu32, esp_get_free_heap_size(), this->content_length_); + } + + ESP_LOGD(TAG, "Successfully uploaded TFT to Nextion!"); + + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGV(TAG, "Connection closed"); + return this->upload_end_(true); +} + +bool Nextion::upload_end_(bool successful) { + ESP_LOGD(TAG, "Nextion TFT upload finished: %s", YESNO(successful)); + this->is_updating_ = false; + this->ignore_is_setup_ = false; + + uint32_t baud_rate = this->parent_->get_baud_rate(); + if (baud_rate != this->original_baud_rate_) { + ESP_LOGD(TAG, "Changing baud rate back from %" PRIu32 " to %" PRIu32 " bps", baud_rate, this->original_baud_rate_); + this->parent_->set_baud_rate(this->original_baud_rate_); + this->parent_->load_settings(); + } + + if (successful) { + ESP_LOGD(TAG, "Restarting ESPHome"); + delay(1500); // NOLINT + arch_restart(); + } else { + ESP_LOGE(TAG, "Nextion TFT upload failed"); + } + return successful; +} + +} // namespace nextion +} // namespace esphome + +#endif // USE_ESP_IDF +#endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 32bfccf9f8d9..6cc641fcf3df 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -30,7 +30,7 @@ void NextionSensor::add_to_wave_buffer(float state) { } void NextionSensor::update() { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (this->wave_chan_id_ == UINT8_MAX) { @@ -45,7 +45,7 @@ void NextionSensor::update() { } void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (std::isnan(state)) @@ -76,9 +76,15 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { } } + float published_state = state; if (this->wave_chan_id_ == UINT8_MAX) { if (publish) { - this->publish_state(state); + if (this->precision_ > 0) { + double to_multiply = pow(10, -this->precision_); + published_state = (float) (state * to_multiply); + } + + this->publish_state(published_state); } else { this->raw_state = state; this->state = state; @@ -87,7 +93,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { } this->update_component_settings(); - ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), published_state); } void NextionSensor::wave_update_() { diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 1f32ad34257c..63c1882b4865 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -18,13 +18,13 @@ void NextionSwitch::process_bool(const std::string &variable_name, bool on) { } void NextionSwitch::update() { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; this->nextion_->add_to_get_queue(this); } void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (send_to_nextion) { diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index 08f032df74f1..a3fc9390f596 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -16,13 +16,13 @@ void NextionTextSensor::process_text(const std::string &variable_name, const std } void NextionTextSensor::update() { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; this->nextion_->add_to_get_queue(this); } void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { - if (!this->nextion_->is_setup()) + if (!this->nextion_->is_setup() || this->nextion_->is_updating()) return; if (send_to_nextion) { diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index c3bbc50bf905..eea1a47b24b9 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -1,12 +1,13 @@ from esphome import automation import esphome.codegen as cg -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kbx81"] nfc_ns = cg.esphome_ns.namespace("nfc") +Nfcc = nfc_ns.class_("Nfcc") NfcTag = nfc_ns.class_("NfcTag") - +NfcTagListener = nfc_ns.class_("NfcTagListener") NfcOnTagTrigger = nfc_ns.class_( "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) ) diff --git a/esphome/components/nfc/binary_sensor/__init__.py b/esphome/components/nfc/binary_sensor/__init__.py new file mode 100644 index 000000000000..21c8298ea86b --- /dev/null +++ b/esphome/components/nfc/binary_sensor/__init__.py @@ -0,0 +1,72 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_UID +from esphome.core import HexInt +from .. import nfc_ns, Nfcc, NfcTagListener + +DEPENDENCIES = ["nfc"] + +CONF_NDEF_CONTAINS = "ndef_contains" +CONF_NFCC_ID = "nfcc_id" +CONF_TAG_ID = "tag_id" + +NfcTagBinarySensor = nfc_ns.class_( + "NfcTagBinarySensor", + binary_sensor.BinarySensor, + cg.Component, + NfcTagListener, + cg.Parented.template(Nfcc), +) + + +def validate_uid(value): + value = cv.string_strict(value) + for x in value.split("-"): + if len(x) != 2: + raise cv.Invalid( + "Each part (separated by '-') of the UID must be two characters " + "long." + ) + try: + x = int(x, 16) + except ValueError as err: + raise cv.Invalid( + "Valid characters for parts of a UID are 0123456789ABCDEF." + ) from err + if x < 0 or x > 255: + raise cv.Invalid( + "Valid values for UID parts (separated by '-') are 00 to FF" + ) + return value + + +CONFIG_SCHEMA = cv.All( + binary_sensor.binary_sensor_schema(NfcTagBinarySensor) + .extend( + { + cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc), + cv.Optional(CONF_NDEF_CONTAINS): cv.string, + cv.Optional(CONF_TAG_ID): cv.string, + cv.Optional(CONF_UID): validate_uid, + } + ) + .extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID), +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_NFCC_ID]) + + hub = await cg.get_variable(config[CONF_NFCC_ID]) + cg.add(hub.register_listener(var)) + if CONF_NDEF_CONTAINS in config: + cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS])) + if CONF_TAG_ID in config: + cg.add(var.set_tag_name(config[CONF_TAG_ID])) + elif CONF_UID in config: + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] + cg.add(var.set_uid(addr)) diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.cpp b/esphome/components/nfc/binary_sensor/binary_sensor.cpp new file mode 100644 index 000000000000..8f1f6acd51fb --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.cpp @@ -0,0 +1,114 @@ +#include "binary_sensor.h" +#include "../nfc_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.binary_sensor"; + +void NfcTagBinarySensor::setup() { + this->parent_->register_listener(this); + this->publish_initial_state(false); +} + +void NfcTagBinarySensor::dump_config() { + std::string match_str = "name"; + + LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this); + if (!this->match_string_.empty()) { + if (!this->match_tag_name_) { + match_str = "contains"; + } + ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str()); + return; + } + if (!this->uid_.empty()) { + ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str()); + } +} + +void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = false; +} + +void NfcTagBinarySensor::set_tag_name(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = true; +} + +void NfcTagBinarySensor::set_uid(const std::vector &uid) { this->uid_ = uid; } + +bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(this->match_string_) != std::string::npos) { + return true; + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) { + auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1); + if (rec_substr.find(this->match_string_) != std::string::npos) { + return true; + } + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_uid(const std::vector &data) { + if (data.size() != this->uid_.size()) { + return false; + } + + for (size_t i = 0; i < data.size(); i++) { + if (data[i] != this->uid_[i]) { + return false; + } + } + return true; +} + +void NfcTagBinarySensor::tag_off(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(false); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(false); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(false); + } +} + +void NfcTagBinarySensor::tag_on(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(true); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(true); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(true); + } +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.h b/esphome/components/nfc/binary_sensor/binary_sensor.h new file mode 100644 index 000000000000..cc313c2f2b43 --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/nfc_tag.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace nfc { + +class NfcTagBinarySensor : public binary_sensor::BinarySensor, + public Component, + public NfcTagListener, + public Parented { + public: + void setup() override; + void dump_config() override; + + void set_ndef_match_string(const std::string &str); + void set_tag_name(const std::string &str); + void set_uid(const std::vector &uid); + + bool tag_match_ndef_string(const std::shared_ptr &msg); + bool tag_match_tag_name(const std::shared_ptr &msg); + bool tag_match_uid(const std::vector &data); + + void tag_off(NfcTag &tag) override; + void tag_on(NfcTag &tag) override; + + protected: + bool match_tag_name_{false}; + std::string match_string_; + std::vector uid_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nci_core.h b/esphome/components/nfc/nci_core.h new file mode 100644 index 000000000000..fdaf6d0cc594 --- /dev/null +++ b/esphome/components/nfc/nci_core.h @@ -0,0 +1,144 @@ +#pragma once + +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace nfc { + +// Header info +static const uint8_t NCI_PKT_HEADER_SIZE = 3; // NCI packet (pkt) headers are always three bytes +static const uint8_t NCI_PKT_MT_GID_OFFSET = 0; // NCI packet (pkt) MT and GID offsets +static const uint8_t NCI_PKT_OID_OFFSET = 1; // NCI packet (pkt) OID offset +static const uint8_t NCI_PKT_LENGTH_OFFSET = 2; // NCI packet (pkt) message length (size) offset +static const uint8_t NCI_PKT_PAYLOAD_OFFSET = 3; // NCI packet (pkt) payload offset +// Important masks +static const uint8_t NCI_PKT_MT_MASK = 0xE0; // NCI packet (pkt) message type mask +static const uint8_t NCI_PKT_PBF_MASK = 0x10; // packet boundary flag bit +static const uint8_t NCI_PKT_GID_MASK = 0x0F; +static const uint8_t NCI_PKT_OID_MASK = 0x3F; +// Message types +static const uint8_t NCI_PKT_MT_DATA = 0x00; // For sending commands to NFC endpoint (card/tag) +static const uint8_t NCI_PKT_MT_CTRL_COMMAND = 0x20; // For sending commands to NFCC +static const uint8_t NCI_PKT_MT_CTRL_RESPONSE = 0x40; // Response from NFCC to commands +static const uint8_t NCI_PKT_MT_CTRL_NOTIFICATION = 0x60; // Notification from NFCC +// GIDs +static const uint8_t NCI_CORE_GID = 0x0; +static const uint8_t RF_GID = 0x1; +static const uint8_t NFCEE_GID = 0x1; +static const uint8_t NCI_PROPRIETARY_GID = 0xF; +// OIDs +static const uint8_t NCI_CORE_RESET_OID = 0x00; +static const uint8_t NCI_CORE_INIT_OID = 0x01; +static const uint8_t NCI_CORE_SET_CONFIG_OID = 0x02; +static const uint8_t NCI_CORE_GET_CONFIG_OID = 0x03; +static const uint8_t NCI_CORE_CONN_CREATE_OID = 0x04; +static const uint8_t NCI_CORE_CONN_CLOSE_OID = 0x05; +static const uint8_t NCI_CORE_CONN_CREDITS_OID = 0x06; +static const uint8_t NCI_CORE_GENERIC_ERROR_OID = 0x07; +static const uint8_t NCI_CORE_INTERFACE_ERROR_OID = 0x08; + +static const uint8_t RF_DISCOVER_MAP_OID = 0x00; +static const uint8_t RF_SET_LISTEN_MODE_ROUTING_OID = 0x01; +static const uint8_t RF_GET_LISTEN_MODE_ROUTING_OID = 0x02; +static const uint8_t RF_DISCOVER_OID = 0x03; +static const uint8_t RF_DISCOVER_SELECT_OID = 0x04; +static const uint8_t RF_INTF_ACTIVATED_OID = 0x05; +static const uint8_t RF_DEACTIVATE_OID = 0x06; +static const uint8_t RF_FIELD_INFO_OID = 0x07; +static const uint8_t RF_T3T_POLLING_OID = 0x08; +static const uint8_t RF_NFCEE_ACTION_OID = 0x09; +static const uint8_t RF_NFCEE_DISCOVERY_REQ_OID = 0x0A; +static const uint8_t RF_PARAMETER_UPDATE_OID = 0x0B; + +static const uint8_t NFCEE_DISCOVER_OID = 0x00; +static const uint8_t NFCEE_MODE_SET_OID = 0x01; +// Interfaces +static const uint8_t INTF_NFCEE_DIRECT = 0x00; +static const uint8_t INTF_FRAME = 0x01; +static const uint8_t INTF_ISODEP = 0x02; +static const uint8_t INTF_NFCDEP = 0x03; +static const uint8_t INTF_TAGCMD = 0x80; // NXP proprietary +// Bit rates +static const uint8_t NFC_BIT_RATE_106 = 0x00; +static const uint8_t NFC_BIT_RATE_212 = 0x01; +static const uint8_t NFC_BIT_RATE_424 = 0x02; +static const uint8_t NFC_BIT_RATE_848 = 0x03; +static const uint8_t NFC_BIT_RATE_1695 = 0x04; +static const uint8_t NFC_BIT_RATE_3390 = 0x05; +static const uint8_t NFC_BIT_RATE_6780 = 0x06; +// Protocols +static const uint8_t PROT_UNDETERMINED = 0x00; +static const uint8_t PROT_T1T = 0x01; +static const uint8_t PROT_T2T = 0x02; +static const uint8_t PROT_T3T = 0x03; +static const uint8_t PROT_ISODEP = 0x04; +static const uint8_t PROT_NFCDEP = 0x05; +static const uint8_t PROT_T5T = 0x06; +static const uint8_t PROT_MIFARE = 0x80; +// RF Technologies +static const uint8_t NFC_RF_TECH_A = 0x00; +static const uint8_t NFC_RF_TECH_B = 0x01; +static const uint8_t NFC_RF_TECH_F = 0x02; +static const uint8_t NFC_RF_TECH_15693 = 0x03; +// RF Technology & Modes +static const uint8_t MODE_MASK = 0xF0; +static const uint8_t MODE_LISTEN_MASK = 0x80; +static const uint8_t MODE_POLL = 0x00; + +static const uint8_t TECH_PASSIVE_NFCA = 0x00; +static const uint8_t TECH_PASSIVE_NFCB = 0x01; +static const uint8_t TECH_PASSIVE_NFCF = 0x02; +static const uint8_t TECH_ACTIVE_NFCA = 0x03; +static const uint8_t TECH_ACTIVE_NFCF = 0x05; +static const uint8_t TECH_PASSIVE_15693 = 0x06; +// Status codes +static const uint8_t STATUS_OK = 0x00; +static const uint8_t STATUS_REJECTED = 0x01; +static const uint8_t STATUS_RF_FRAME_CORRUPTED = 0x02; +static const uint8_t STATUS_FAILED = 0x03; +static const uint8_t STATUS_NOT_INITIALIZED = 0x04; +static const uint8_t STATUS_SYNTAX_ERROR = 0x05; +static const uint8_t STATUS_SEMANTIC_ERROR = 0x06; +static const uint8_t STATUS_INVALID_PARAM = 0x09; +static const uint8_t STATUS_MESSAGE_SIZE_EXCEEDED = 0x0A; +static const uint8_t DISCOVERY_ALREADY_STARTED = 0xA0; +static const uint8_t DISCOVERY_TARGET_ACTIVATION_FAILED = 0xA1; +static const uint8_t DISCOVERY_TEAR_DOWN = 0xA2; +static const uint8_t RF_TRANSMISSION_ERROR = 0xB0; +static const uint8_t RF_PROTOCOL_ERROR = 0xB1; +static const uint8_t RF_TIMEOUT_ERROR = 0xB2; +static const uint8_t NFCEE_INTERFACE_ACTIVATION_FAILED = 0xC0; +static const uint8_t NFCEE_TRANSMISSION_ERROR = 0xC1; +static const uint8_t NFCEE_PROTOCOL_ERROR = 0xC2; +static const uint8_t NFCEE_TIMEOUT_ERROR = 0xC3; +// Deactivation types/reasons +static const uint8_t DEACTIVATION_TYPE_IDLE = 0x00; +static const uint8_t DEACTIVATION_TYPE_SLEEP = 0x01; +static const uint8_t DEACTIVATION_TYPE_SLEEP_AF = 0x02; +static const uint8_t DEACTIVATION_TYPE_DISCOVERY = 0x03; +// RF discover map modes +static const uint8_t RF_DISCOVER_MAP_MODE_POLL = 0x1; +static const uint8_t RF_DISCOVER_MAP_MODE_LISTEN = 0x2; +// RF discover notification types +static const uint8_t RF_DISCOVER_NTF_NT_LAST = 0x00; +static const uint8_t RF_DISCOVER_NTF_NT_LAST_RL = 0x01; +static const uint8_t RF_DISCOVER_NTF_NT_MORE = 0x02; +// Important message offsets +static const uint8_t RF_DISCOVER_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_DISCOVER_NTF_PROTOCOL = 1 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_DISCOVER_NTF_MODE_TECH = 2 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_DISCOVER_NTF_RF_TECH_LENGTH = 3 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_DISCOVER_NTF_RF_TECH_PARAMS = 4 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_INTERFACE = 1 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_PROTOCOL = 2 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_MODE_TECH = 3 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_MAX_SIZE = 4 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_INIT_CRED = 5 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_LENGTH = 6 + NCI_PKT_HEADER_SIZE; +static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS = 7 + NCI_PKT_HEADER_SIZE; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nci_message.cpp b/esphome/components/nfc/nci_message.cpp new file mode 100644 index 000000000000..c6b21f6ae072 --- /dev/null +++ b/esphome/components/nfc/nci_message.cpp @@ -0,0 +1,166 @@ +#include "nci_core.h" +#include "nci_message.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace nfc { + +static const char *const TAG = "NciMessage"; + +NciMessage::NciMessage(const uint8_t message_type, const std::vector &payload) { + this->set_message(message_type, payload); +} + +NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) { + this->set_header(message_type, gid, oid); +} + +NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid, + const std::vector &payload) { + this->set_message(message_type, gid, oid, payload); +} + +NciMessage::NciMessage(const std::vector &raw_packet) { this->nci_message_ = raw_packet; }; + +std::vector NciMessage::encode() { + this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; + std::vector message = this->nci_message_; + return message; +} + +void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; } + +uint8_t NciMessage::get_message_type() const { + return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK; +} + +uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; } + +uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; } + +uint8_t NciMessage::get_payload_size(const bool recompute) { + if (!this->nci_message_.empty()) { + if (recompute) { + this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; + } + return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET]; + } + return 0; +} + +uint8_t NciMessage::get_simple_status_response() const { + if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) { + return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET]; + } + return STATUS_FAILED; +} + +uint8_t NciMessage::get_message_byte(const uint8_t offset) const { + if (this->nci_message_.size() > offset) { + return this->nci_message_[offset]; + } + return 0; +} + +std::vector &NciMessage::get_message() { return this->nci_message_; } + +bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; } + +bool NciMessage::message_type_is(const uint8_t message_type) const { + if (!this->nci_message_.empty()) { + return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK); + } + return false; +} + +bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) { + if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) { + if (recompute) { + this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; + } + return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET]; + } + return false; +} + +bool NciMessage::gid_is(const uint8_t gid) const { + if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) { + return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK); + } + return false; +} + +bool NciMessage::oid_is(const uint8_t oid) const { + if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) { + return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK); + } + return false; +} + +bool NciMessage::simple_status_response_is(const uint8_t response) const { + if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) { + return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET]; + } + return false; +} + +void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) { + if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + } + this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = + (message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK); + this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; +} + +void NciMessage::set_message(const uint8_t message_type, const std::vector &payload) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); + this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end()); +} + +void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid, + const std::vector &payload) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = + (message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK); + this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; + this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); + this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end()); +} + +void NciMessage::set_message_type(const uint8_t message_type) { + if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + } + auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK; + this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK); +} + +void NciMessage::set_gid(const uint8_t gid) { + if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + } + auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK; + this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK); +} + +void NciMessage::set_oid(const uint8_t oid) { + if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) { + this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + } + this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK; +} + +void NciMessage::set_payload(const std::vector &payload) { + std::vector message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE); + + message.insert(message.end(), payload.begin(), payload.end()); + message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); + this->nci_message_ = message; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nci_message.h b/esphome/components/nfc/nci_message.h new file mode 100644 index 000000000000..c6b8537402ce --- /dev/null +++ b/esphome/components/nfc/nci_message.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace nfc { + +class NciMessage { + public: + NciMessage() {} + NciMessage(uint8_t message_type, const std::vector &payload); + NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid); + NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector &payload); + NciMessage(const std::vector &raw_packet); + + std::vector encode(); + void reset(); + + uint8_t get_message_type() const; + uint8_t get_gid() const; + uint8_t get_oid() const; + uint8_t get_payload_size(bool recompute = false); + uint8_t get_simple_status_response() const; + uint8_t get_message_byte(uint8_t offset) const; + std::vector &get_message(); + + bool has_payload() const; + bool message_type_is(uint8_t message_type) const; + bool message_length_is(uint8_t message_length, bool recompute = false); + bool gid_is(uint8_t gid) const; + bool oid_is(uint8_t oid) const; + bool simple_status_response_is(uint8_t response) const; + + void set_header(uint8_t message_type, uint8_t gid, uint8_t oid); + void set_message(uint8_t message_type, const std::vector &payload); + void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector &payload); + void set_message_type(uint8_t message_type); + void set_gid(uint8_t gid); + void set_oid(uint8_t oid); + void set_payload(const std::vector &payload); + + protected: + std::vector nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index 461856d377b5..e7304445c55b 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -1,4 +1,5 @@ #include "ndef_message.h" +#include namespace esphome { namespace nfc { @@ -32,7 +33,7 @@ NdefMessage::NdefMessage(std::vector &data) { id_length = data[index++]; } - ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); + ESP_LOGVV(TAG, "Lengths: type=%d, payload=%" PRIu32 ", id=%d", type_length, payload_length, id_length); std::string type_str(data.begin() + index, data.begin() + index + type_length); diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index b7c721502816..cf5a7f5ef131 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -53,8 +53,8 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector &data) { } bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index) { - uint8_t i = get_mifare_classic_ndef_start_index(data); - if (i < 0 || data[i] != 0x03) { + auto i = get_mifare_classic_ndef_start_index(data); + if (data[i] != 0x03) { ESP_LOGE(TAG, "Error, Can't decode message length."); return false; } diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index d4d66f970f74..23bfdd8ef0cb 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -66,5 +66,19 @@ bool mifare_classic_is_trailer_block(uint8_t block_num); uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); +class NfcTagListener { + public: + virtual void tag_off(NfcTag &tag) {} + virtual void tag_on(NfcTag &tag) {} +}; + +class Nfcc { + public: + void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); } + + protected: + std::vector tag_listeners_; +}; + } // namespace nfc } // namespace esphome diff --git a/esphome/components/nfc/nfc_helpers.cpp b/esphome/components/nfc/nfc_helpers.cpp new file mode 100644 index 000000000000..bfaed6e486fa --- /dev/null +++ b/esphome/components/nfc/nfc_helpers.cpp @@ -0,0 +1,47 @@ +#include "nfc_helpers.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.helpers"; + +bool has_ha_tag_ndef(NfcTag &tag) { return !get_ha_tag_ndef(tag).empty(); } + +std::string get_ha_tag_ndef(NfcTag &tag) { + if (!tag.has_ndef_message()) { + return std::string(); + } + auto message = tag.get_ndef_message(); + auto records = message->get_records(); + for (const auto &record : records) { + std::string payload = record->get_payload(); + size_t pos = payload.find(HA_TAG_ID_PREFIX); + if (pos != std::string::npos) { + return payload.substr(pos + sizeof(HA_TAG_ID_PREFIX) - 1); + } + } + return std::string(); +} + +std::string get_random_ha_tag_ndef() { + static const char ALPHANUM[] = "0123456789abcdef"; + std::string uri = HA_TAG_ID_PREFIX; + for (int i = 0; i < 8; i++) { + uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; + } + uri += "-"; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 4; i++) { + uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; + } + uri += "-"; + } + for (int i = 0; i < 12; i++) { + uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)]; + } + ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str()); + return uri; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc_helpers.h b/esphome/components/nfc/nfc_helpers.h new file mode 100644 index 000000000000..74f5beba1344 --- /dev/null +++ b/esphome/components/nfc/nfc_helpers.h @@ -0,0 +1,17 @@ +#pragma once + +#include "nfc_tag.h" + +namespace esphome { +namespace nfc { + +static const char HA_TAG_ID_EXT_RECORD_TYPE[] = "android.com:pkg"; +static const char HA_TAG_ID_EXT_RECORD_PAYLOAD[] = "io.homeassistant.companion.android"; +static const char HA_TAG_ID_PREFIX[] = "https://www.home-assistant.io/tag/"; + +std::string get_ha_tag_ndef(NfcTag &tag); +std::string get_random_ha_tag_ndef(); +bool has_ha_tag_ndef(NfcTag &tag); + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/noblex/__init__.py b/esphome/components/noblex/__init__.py new file mode 100644 index 000000000000..ceceff9df9f0 --- /dev/null +++ b/esphome/components/noblex/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@AGalfra"] diff --git a/esphome/components/noblex/climate.py b/esphome/components/noblex/climate.py new file mode 100644 index 000000000000..2025cb5d9e81 --- /dev/null +++ b/esphome/components/noblex/climate.py @@ -0,0 +1,20 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] + +noblex_ns = cg.esphome_ns.namespace("noblex") +NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NoblexClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/noblex/noblex.cpp b/esphome/components/noblex/noblex.cpp new file mode 100644 index 000000000000..3521745bdc16 --- /dev/null +++ b/esphome/components/noblex/noblex.cpp @@ -0,0 +1,309 @@ +#include "noblex.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace noblex { + +static const char *const TAG = "noblex.climate"; + +const uint16_t NOBLEX_HEADER_MARK = 9000; +const uint16_t NOBLEX_HEADER_SPACE = 4500; +const uint16_t NOBLEX_BIT_MARK = 660; +const uint16_t NOBLEX_ONE_SPACE = 1640; +const uint16_t NOBLEX_ZERO_SPACE = 520; +const uint32_t NOBLEX_GAP = 20000; +const uint8_t NOBLEX_POWER = 0x10; + +using IRNoblexMode = enum IRNoblexMode { + IR_NOBLEX_MODE_AUTO = 0b000, + IR_NOBLEX_MODE_COOL = 0b100, + IR_NOBLEX_MODE_DRY = 0b010, + IR_NOBLEX_MODE_FAN = 0b110, + IR_NOBLEX_MODE_HEAT = 0b001, +}; + +using IRNoblexFan = enum IRNoblexFan { + IR_NOBLEX_FAN_AUTO = 0b00, + IR_NOBLEX_FAN_LOW = 0b10, + IR_NOBLEX_FAN_MEDIUM = 0b01, + IR_NOBLEX_FAN_HIGH = 0b11, +}; + +// Transmit via IR the state of this climate controller. +void NoblexClimate::transmit_state() { + uint8_t remote_state[8] = {0x80, 0x10, 0x00, 0x0A, 0x50, 0x00, 0x20, 0x00}; // OFF, COOL, 24C, FAN_AUTO + + auto powered_on = this->mode != climate::CLIMATE_MODE_OFF; + if (powered_on) { + remote_state[0] |= 0x10; // power bit + remote_state[2] = 0x02; + } + if (powered_on != this->powered_on_assumed) + this->powered_on_assumed = powered_on; + + auto temp = (uint8_t) roundf(clamp(this->target_temperature, NOBLEX_TEMP_MIN, NOBLEX_TEMP_MAX)); + remote_state[1] = reverse_bits(uint8_t((temp - NOBLEX_TEMP_MIN) & 0x0F)); + + switch (this->mode) { + case climate::CLIMATE_MODE_HEAT_COOL: + remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_AUTO << 5); + remote_state[1] = 0x90; // NOBLEX_TEMP_MAP 25C + break; + case climate::CLIMATE_MODE_COOL: + remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_COOL << 5); + break; + case climate::CLIMATE_MODE_DRY: + remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_DRY << 5); + break; + case climate::CLIMATE_MODE_FAN_ONLY: + remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_FAN << 5); + break; + case climate::CLIMATE_MODE_HEAT: + remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_HEAT << 5); + break; + case climate::CLIMATE_MODE_OFF: + default: + powered_on = false; + this->powered_on_assumed = powered_on; + remote_state[0] &= 0xEF; + remote_state[2] = 0x00; + break; + } + + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_LOW << 2); + break; + case climate::CLIMATE_FAN_MEDIUM: + remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_MEDIUM << 2); + break; + case climate::CLIMATE_FAN_HIGH: + remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_HIGH << 2); + break; + case climate::CLIMATE_FAN_AUTO: + default: + remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_AUTO << 2); + break; + } + + switch (this->swing_mode) { + case climate::CLIMATE_SWING_VERTICAL: + remote_state[0] |= 0x02; + remote_state[4] = 0x58; + break; + case climate::CLIMATE_SWING_OFF: + default: + remote_state[0] &= 0xFD; + remote_state[4] = 0x50; + break; + } + + uint8_t crc = 0; + for (uint8_t i : remote_state) { + crc += reverse_bits(i); + } + crc = reverse_bits(uint8_t(crc & 0x0F)) >> 4; + + ESP_LOGD(TAG, "Sending noblex code: %02X%02X %02X%02X %02X%02X %02X%02X", remote_state[0], remote_state[1], + remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], remote_state[7]); + + ESP_LOGV(TAG, "CRC: %01X", crc); + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(38000); + + // Header + data->mark(NOBLEX_HEADER_MARK); + data->space(NOBLEX_HEADER_SPACE); + // Data (sent remote_state from the MSB to the LSB) + for (uint8_t i : remote_state) { + for (int8_t j = 7; j >= 0; j--) { + if ((i == 4) & (j == 4)) { + // Header intermediate + data->mark(NOBLEX_BIT_MARK); + data->space(NOBLEX_GAP); // gap en bit 36 + } else { + data->mark(NOBLEX_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? NOBLEX_ONE_SPACE : NOBLEX_ZERO_SPACE); + } + } + } + // send crc + for (int8_t i = 3; i >= 0; i--) { + data->mark(NOBLEX_BIT_MARK); + bool bit = crc & (1 << i); + data->space(bit ? NOBLEX_ONE_SPACE : NOBLEX_ZERO_SPACE); + } + // Footer + data->mark(NOBLEX_BIT_MARK); + + transmit.perform(); +} // end transmit_state() + +// Handle received IR Buffer +bool NoblexClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t remote_state[8] = {0}; + uint8_t crc = 0, crc_calculated = 0; + + if (!receiving_) { + // Validate header + if (data.expect_item(NOBLEX_HEADER_MARK, NOBLEX_HEADER_SPACE)) { + ESP_LOGV(TAG, "Header"); + receiving_ = true; + // Read first 36 bits + for (int i = 0; i < 5; i++) { + // Read bit + for (int j = 7; j >= 0; j--) { + if ((i == 4) & (j == 4)) { + remote_state[i] |= 1 << j; + // Header intermediate + ESP_LOGVV(TAG, "GAP"); + return false; + } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) { + remote_state[i] |= 1 << j; + } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) { + ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j); + return false; + } + } + ESP_LOGV(TAG, "Byte %d %02X", i, remote_state[i]); + } + + } else { + ESP_LOGV(TAG, "Header fail"); + receiving_ = false; + return false; + } + + } else { + // Read the remaining 28 bits + for (int i = 4; i < 8; i++) { + // Read bit + for (int j = 7; j >= 0; j--) { + if ((i == 4) & (j >= 4)) { + // nothing + } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) { + remote_state[i] |= 1 << j; + } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) { + ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j); + return false; + } + } + ESP_LOGV(TAG, "Byte %d %02X", i, remote_state[i]); + } + + // Read crc + for (int i = 3; i >= 0; i--) { + if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) { + crc |= 1 << i; + } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) { + ESP_LOGVV(TAG, "Bit %d CRC fail", i); + return false; + } + } + ESP_LOGV(TAG, "CRC %02X", crc); + + // Validate footer + if (!data.expect_mark(NOBLEX_BIT_MARK)) { + ESP_LOGV(TAG, "Footer fail"); + return false; + } + receiving_ = false; + } + + for (uint8_t i : remote_state) + crc_calculated += reverse_bits(i); + crc_calculated = reverse_bits(uint8_t(crc_calculated & 0x0F)) >> 4; + ESP_LOGVV(TAG, "CRC calc %02X", crc_calculated); + + if (crc != crc_calculated) { + ESP_LOGV(TAG, "CRC fail"); + return false; + } + + ESP_LOGD(TAG, "Received noblex code: %02X%02X %02X%02X %02X%02X %02X%02X", remote_state[0], remote_state[1], + remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], remote_state[7]); + + auto powered_on = false; + if ((remote_state[0] & NOBLEX_POWER) == NOBLEX_POWER) { + powered_on = true; + this->powered_on_assumed = powered_on; + } else { + powered_on = false; + this->powered_on_assumed = powered_on; + this->mode = climate::CLIMATE_MODE_OFF; + } + // powr on/off button + ESP_LOGV(TAG, "Power: %01X", powered_on); + + // Set received mode + if (powered_on_assumed) { + auto mode = (remote_state[0] & 0xE0) >> 5; + ESP_LOGV(TAG, "Mode: %02X", mode); + switch (mode) { + case IRNoblexMode::IR_NOBLEX_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case IRNoblexMode::IR_NOBLEX_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case IRNoblexMode::IR_NOBLEX_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case IRNoblexMode::IR_NOBLEX_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + case IRNoblexMode::IR_NOBLEX_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + } + } + + // Set received temp + uint8_t temp = remote_state[1]; + ESP_LOGVV(TAG, "Temperature Raw: %02X", temp); + + temp = 0x0F & reverse_bits(temp); + temp += NOBLEX_TEMP_MIN; + ESP_LOGV(TAG, "Temperature Climate: %u", temp); + this->target_temperature = temp; + + // Set received fan speed + auto fan = (remote_state[0] & 0x0C) >> 2; + ESP_LOGV(TAG, "Fan: %02X", fan); + switch (fan) { + case IRNoblexFan::IR_NOBLEX_FAN_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case IRNoblexFan::IR_NOBLEX_FAN_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case IRNoblexFan::IR_NOBLEX_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case IRNoblexFan::IR_NOBLEX_FAN_AUTO: + default: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + + // Set received swing status + if (remote_state[0] & 0x02) { + ESP_LOGV(TAG, "Swing vertical"); + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else { + ESP_LOGV(TAG, "Swing OFF"); + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + + for (uint8_t &i : remote_state) + i = 0; + this->publish_state(); + return true; +} // end on_receive() + +} // namespace noblex +} // namespace esphome diff --git a/esphome/components/noblex/noblex.h b/esphome/components/noblex/noblex.h new file mode 100644 index 000000000000..a8e5f41547c7 --- /dev/null +++ b/esphome/components/noblex/noblex.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace noblex { + +// Temperature +const uint8_t NOBLEX_TEMP_MIN = 16; // Celsius +const uint8_t NOBLEX_TEMP_MAX = 30; // Celsius + +class NoblexClimate : public climate_ir::ClimateIR { + public: + NoblexClimate() + : climate_ir::ClimateIR(NOBLEX_TEMP_MIN, NOBLEX_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} + + void setup() override { + climate_ir::ClimateIR::setup(); + this->powered_on_assumed = this->mode != climate::CLIMATE_MODE_OFF; + } + + // Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override { + send_swing_cmd_ = call.get_swing_mode().has_value(); + // swing resets after unit powered off + if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF) + this->swing_mode = climate::CLIMATE_SWING_OFF; + climate_ir::ClimateIR::control(call); + } + + // used to track when to send the power toggle command. + bool powered_on_assumed; + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + /// Handle received IR Buffer. + bool on_receive(remote_base::RemoteReceiveData data) override; + bool send_swing_cmd_{false}; + bool receiving_ = false; +}; + +} // namespace noblex +} // namespace esphome diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index ba8d3df9d89b..06fc55fc4328 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -2,7 +2,7 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components import sensor +from esphome.components import sensor, resistance_sampler from esphome.const import ( CONF_CALIBRATION, CONF_REFERENCE_RESISTANCE, @@ -15,6 +15,8 @@ UNIT_CELSIUS, ) +AUTO_LOAD = ["resistance_sampler"] + ntc_ns = cg.esphome_ns.namespace("ntc") NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor) @@ -124,7 +126,7 @@ def process_calibration(value): ) .extend( { - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_SENSOR): cv.use_id(resistance_sampler.ResistanceSampler), cv.Required(CONF_CALIBRATION): process_calibration, } ) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index e6ad545d7029..6d7ec97c90cf 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -62,6 +62,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, @@ -117,6 +118,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, @@ -237,13 +239,14 @@ async def setup_number_core_( cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) - if CONF_UNIT_OF_MEASUREMENT in config: - cg.add(var.traits.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)) is not None: + cg.add(var.traits.set_unit_of_measurement(unit_of_measurement)) + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: + cg.add(var.traits.set_device_class(device_class)) + + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) - if CONF_DEVICE_CLASS in config: - cg.add(var.traits.set_device_class(config[CONF_DEVICE_CLASS])) async def register_number( @@ -257,8 +260,8 @@ async def register_number( ) -async def new_number(config, *, min_value: float, max_value: float, step: float): - var = cg.new_Pvariable(config[CONF_ID]) +async def new_number(config, *args, min_value: float, max_value: float, step: float): + var = cg.new_Pvariable(config[CONF_ID], *args) await register_number( var, config, min_value=min_value, max_value=max_value, step=step ) @@ -282,10 +285,10 @@ async def number_in_range_to_code(config, condition_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(condition_id, template_arg, paren) - if CONF_ABOVE in config: - cg.add(var.set_min(config[CONF_ABOVE])) - if CONF_BELOW in config: - cg.add(var.set_max(config[CONF_BELOW])) + if (above := config.get(CONF_ABOVE)) is not None: + cg.add(var.set_min(above)) + if (below := config.get(CONF_BELOW)) is not None: + cg.add(var.set_max(below)) return var @@ -389,14 +392,14 @@ async def number_set_to_code(config, action_id, template_arg, args): async def number_to_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) - if CONF_OPERATION in config: - to_ = await cg.templatable(config[CONF_OPERATION], args, NumberOperation) + if (operation := config.get(CONF_OPERATION)) is not None: + to_ = await cg.templatable(operation, args, NumberOperation) cg.add(var.set_operation(to_)) - if CONF_CYCLE in config: - cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool) - cg.add(var.set_cycle(cycle_)) - if CONF_MODE in config: - cg.add(var.set_operation(NUMBER_OPERATION_OPTIONS[config[CONF_MODE]])) - if CONF_CYCLE in config: - cg.add(var.set_cycle(config[CONF_CYCLE])) + if (cycle := config.get(CONF_CYCLE)) is not None: + template_ = await cg.templatable(cycle, args, bool) + cg.add(var.set_cycle(template_)) + if (mode := config.get(CONF_MODE)) is not None: + cg.add(var.set_operation(NUMBER_OPERATION_OPTIONS[mode])) + if (cycle := config.get(CONF_CYCLE)) is not None: + cg.add(var.set_cycle(cycle)) return var diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index eb2a83272db6..3c845490dc51 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -12,6 +12,7 @@ CONF_TRIGGER_ID, CONF_OTA, KEY_PAST_SAFE_MODE, + CONF_VERSION, ) from esphome.core import CORE, coroutine_with_priority @@ -41,6 +42,7 @@ { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, + cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True), cv.SplitDefault( CONF_PORT, esp8266=8266, @@ -93,6 +95,7 @@ async def to_code(config): if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") + cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) @@ -128,7 +131,7 @@ async def to_code(config): use_state_callback = True for conf in config.get(CONF_ON_ERROR, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(int, "x")], conf) + await automation.build_automation(trigger, [(cg.uint8, "x")], conf) use_state_callback = True if use_state_callback: cg.add_define("USE_OTA_STATE_CALLBACK") diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 6c8aca3705d5..0c77a18ce1d7 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -54,7 +54,7 @@ class OTAEndTrigger : public Trigger<> { } }; -class OTAErrorTrigger : public Trigger { +class OTAErrorTrigger : public Trigger { public: explicit OTAErrorTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp index db6ca1be466b..dbf6c9798815 100644 --- a/esphome/components/ota/ota_backend_arduino_libretiny.cpp +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -22,9 +22,7 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { - // not yet implemented -} +void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 41cf333be9db..15af14ff1a42 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -20,8 +20,7 @@ namespace esphome { namespace ota { static const char *const TAG = "ota"; - -static const uint8_t OTA_VERSION_1_0 = 1; +static constexpr u_int16_t OTA_BLOCK_SIZE = 8192; OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -101,6 +100,7 @@ void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, " Using Password."); } #endif + ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION); if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) { ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts", @@ -132,6 +132,9 @@ void OTAComponent::handle_() { uint8_t ota_features; std::unique_ptr backend; (void) ota_features; +#if USE_OTA_VERSION == 2 + size_t size_acknowledged = 0; +#endif if (client_ == nullptr) { struct sockaddr_storage source_addr; @@ -168,7 +171,7 @@ void OTAComponent::handle_() { // Send OK and version - 2 bytes buf[0] = OTA_RESPONSE_OK; - buf[1] = OTA_VERSION_1_0; + buf[1] = USE_OTA_VERSION; this->writeall_(buf, 2); backend = make_ota_backend(); @@ -312,6 +315,13 @@ void OTAComponent::handle_() { goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; +#if USE_OTA_VERSION == 2 + while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) { + buf[0] = OTA_RESPONSE_CHUNK_OK; + this->writeall_(buf, 1); + size_acknowledged += OTA_BLOCK_SIZE; + } +#endif uint32_t now = millis(); if (now - last_progress > 1000) { diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 50d095be6cc6..c20f4f0709f2 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -10,31 +10,32 @@ namespace esphome { namespace ota { enum OTAResponseTypes { - OTA_RESPONSE_OK = 0, - OTA_RESPONSE_REQUEST_AUTH = 1, - - OTA_RESPONSE_HEADER_OK = 64, - OTA_RESPONSE_AUTH_OK = 65, - OTA_RESPONSE_UPDATE_PREPARE_OK = 66, - OTA_RESPONSE_BIN_MD5_OK = 67, - OTA_RESPONSE_RECEIVE_OK = 68, - OTA_RESPONSE_UPDATE_END_OK = 69, - OTA_RESPONSE_SUPPORTS_COMPRESSION = 70, - - OTA_RESPONSE_ERROR_MAGIC = 128, - OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129, - OTA_RESPONSE_ERROR_AUTH_INVALID = 130, - OTA_RESPONSE_ERROR_WRITING_FLASH = 131, - OTA_RESPONSE_ERROR_UPDATE_END = 132, - OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133, - OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134, - OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135, - OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136, - OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, - OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, - OTA_RESPONSE_ERROR_MD5_MISMATCH = 139, - OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140, - OTA_RESPONSE_ERROR_UNKNOWN = 255, + OTA_RESPONSE_OK = 0x00, + OTA_RESPONSE_REQUEST_AUTH = 0x01, + + OTA_RESPONSE_HEADER_OK = 0x40, + OTA_RESPONSE_AUTH_OK = 0x41, + OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42, + OTA_RESPONSE_BIN_MD5_OK = 0x43, + OTA_RESPONSE_RECEIVE_OK = 0x44, + OTA_RESPONSE_UPDATE_END_OK = 0x45, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46, + OTA_RESPONSE_CHUNK_OK = 0x47, + + OTA_RESPONSE_ERROR_MAGIC = 0x80, + OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81, + OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82, + OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83, + OTA_RESPONSE_ERROR_UPDATE_END = 0x84, + OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85, + OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86, + OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87, + OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88, + OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89, + OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A, + OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B, + OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C, + OTA_RESPONSE_ERROR_UNKNOWN = 0xFF, }; enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; diff --git a/esphome/components/output/__init__.py b/esphome/components/output/__init__.py index 4f1fb33fe733..726d1ac08484 100644 --- a/esphome/components/output/__init__.py +++ b/esphome/components/output/__init__.py @@ -106,4 +106,5 @@ async def output_set_level_to_code(config, action_id, template_arg, args): async def to_code(config): + cg.add_define("USE_OUTPUT") cg.add_global(output_ns.using) diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 98483e8ae905..2b064a90cfa8 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -135,21 +135,26 @@ def get_packages(files) -> dict: packages[file] = new_yaml except EsphomeError as e: raise cv.Invalid( - f"{file} is not a valid YAML file. Please check the file contents." + f"{file} is not a valid YAML file. Please check the file contents.\n{e}" ) from e return packages - packages = {} + packages = None + error = "" try: packages = get_packages(files) - except cv.Invalid: - if revert is not None: - revert() - packages = get_packages(files) - finally: - if packages is None: - raise cv.Invalid("Failed to load packages") + except cv.Invalid as e: + error = e + try: + if revert is not None: + revert() + packages = get_packages(files) + except cv.Invalid as er: + error = er + + if packages is None: + raise cv.Invalid(f"Failed to load packages. {error}") return {"packages": packages} diff --git a/esphome/components/pca6416a/__init__.py b/esphome/components/pca6416a/__init__.py index 574d8dce9167..93be14816924 100644 --- a/esphome/components/pca6416a/__init__.py +++ b/esphome/components/pca6416a/__init__.py @@ -64,7 +64,7 @@ def validate_mode(value): ) -@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_PCA6416A, PCA6416A_PIN_SCHEMA) async def pca6416a_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_PCA6416A]) diff --git a/esphome/components/pca9554/__init__.py b/esphome/components/pca9554/__init__.py index 76d6ddaf3261..da31dbd9d91c 100644 --- a/esphome/components/pca9554/__init__.py +++ b/esphome/components/pca9554/__init__.py @@ -11,9 +11,10 @@ CONF_OUTPUT, ) -CODEOWNERS = ["@hwstar"] +CODEOWNERS = ["@hwstar", "@clydebarrow"] DEPENDENCIES = ["i2c"] MULTI_CONF = True +CONF_PIN_COUNT = "pin_count" pca9554_ns = cg.esphome_ns.namespace("pca9554") PCA9554Component = pca9554_ns.class_("PCA9554Component", cg.Component, i2c.I2CDevice) @@ -23,7 +24,12 @@ CONF_PCA9554 = "pca9554" CONFIG_SCHEMA = ( - cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA9554Component)}) + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(PCA9554Component), + cv.Optional(CONF_PIN_COUNT, default=8): cv.one_of(4, 8, 16), + } + ) .extend(cv.COMPONENT_SCHEMA) .extend( i2c.i2c_device_schema(0x20) @@ -33,6 +39,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_pin_count(config[CONF_PIN_COUNT])) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -45,24 +52,27 @@ def validate_mode(value): return value -PCA9554_PIN_SCHEMA = cv.All( +PCA9554_PIN_SCHEMA = pins.gpio_base_schema( + PCA9554GPIOPin, + cv.int_range(min=0, max=15), + modes=[CONF_INPUT, CONF_OUTPUT], + mode_validator=validate_mode, +).extend( { - cv.GenerateID(): cv.declare_id(PCA9554GPIOPin), cv.Required(CONF_PCA9554): cv.use_id(PCA9554Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=8), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -@pins.PIN_SCHEMA_REGISTRY.register("pca9554", PCA9554_PIN_SCHEMA) +def pca9554_pin_final_validate(pin_config, parent_config): + count = parent_config[CONF_PIN_COUNT] + if pin_config[CONF_NUMBER] >= count: + raise cv.Invalid(f"Pin number must be in range 0-{count - 1}") + + +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_PCA9554, PCA9554_PIN_SCHEMA, pca9554_pin_final_validate +) async def pca9554_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_PCA9554]) diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index 74c64dffaaad..c5a4bcfb0981 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -4,6 +4,7 @@ namespace esphome { namespace pca9554 { +// for 16 bit expanders, these addresses will be doubled. const uint8_t INPUT_REG = 0; const uint8_t OUTPUT_REG = 1; const uint8_t INVERT_REG = 2; @@ -13,9 +14,10 @@ static const char *const TAG = "pca9554"; void PCA9554Component::setup() { ESP_LOGCONFIG(TAG, "Setting up PCA9554/PCA9554A..."); + this->reg_width_ = (this->pin_count_ + 7) / 8; // Test to see if device exists if (!this->read_inputs_()) { - ESP_LOGE(TAG, "PCA9554 not available under 0x%02X", this->address_); + ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_); this->mark_failed(); return; } @@ -44,6 +46,7 @@ void PCA9554Component::loop() { void PCA9554Component::dump_config() { ESP_LOGCONFIG(TAG, "PCA9554:"); + ESP_LOGCONFIG(TAG, " I/O Pins: %d", this->pin_count_); LOG_I2C_DEVICE(this) if (this->is_failed()) { ESP_LOGE(TAG, "Communication with PCA9554 failed!"); @@ -85,25 +88,33 @@ void PCA9554Component::pin_mode(uint8_t pin, gpio::Flags flags) { } bool PCA9554Component::read_inputs_() { - uint8_t inputs; + uint8_t inputs[2]; if (this->is_failed()) { ESP_LOGD(TAG, "Device marked failed"); return false; } - if ((this->last_error_ = this->read_register(INPUT_REG, &inputs, 1, true)) != esphome::i2c::ERROR_OK) { + if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) != + esphome::i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); return false; } this->status_clear_warning(); - this->input_mask_ = inputs; + this->input_mask_ = inputs[0]; + if (this->reg_width_ == 2) { + this->input_mask_ |= inputs[1] << 8; + } return true; } -bool PCA9554Component::write_register_(uint8_t reg, uint8_t value) { - if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { +bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) { + uint8_t outputs[2]; + outputs[0] = (uint8_t) value; + outputs[1] = (uint8_t) (value >> 8); + if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) != + esphome::i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); return false; diff --git a/esphome/components/pca9554/pca9554.h b/esphome/components/pca9554/pca9554.h index c2aa5c30ed7b..c548bec619dc 100644 --- a/esphome/components/pca9554/pca9554.h +++ b/esphome/components/pca9554/pca9554.h @@ -28,19 +28,25 @@ class PCA9554Component : public Component, public i2c::I2CDevice { void dump_config() override; + void set_pin_count(size_t pin_count) { this->pin_count_ = pin_count; } + protected: bool read_inputs_(); - bool write_register_(uint8_t reg, uint8_t value); + bool write_register_(uint8_t reg, uint16_t value); + /// number of bits the expander has + size_t pin_count_{8}; + /// width of registers + size_t reg_width_{1}; /// Mask for the pin config - 1 means OUTPUT, 0 means INPUT - uint8_t config_mask_{0x00}; + uint16_t config_mask_{0x00}; /// The mask to write as output state - 1 means HIGH, 0 means LOW - uint8_t output_mask_{0x00}; + uint16_t output_mask_{0x00}; /// The state of the actual input pin states - 1 means HIGH, 0 means LOW - uint8_t input_mask_{0x00}; + uint16_t input_mask_{0x00}; /// Flags to check if read previously during this loop - uint8_t was_previously_read_ = {0x00}; + uint16_t was_previously_read_ = {0x00}; /// Storage for last I2C error seen esphome::i2c::ErrorCode last_error_; }; diff --git a/esphome/components/pcd8544/display.py b/esphome/components/pcd8544/display.py index b4c8f432cfe9..d7e72d1c814a 100644 --- a/esphome/components/pcd8544/display.py +++ b/esphome/components/pcd8544/display.py @@ -39,7 +39,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/pcd8544/pcd_8544.h b/esphome/components/pcd8544/pcd_8544.h index 9a69a9fec73a..cfdb96de6191 100644 --- a/esphome/components/pcd8544/pcd_8544.h +++ b/esphome/components/pcd8544/pcd_8544.h @@ -7,8 +7,7 @@ namespace esphome { namespace pcd8544 { -class PCD8544 : public PollingComponent, - public display::DisplayBuffer, +class PCD8544 : public display::DisplayBuffer, public spi::SPIDevice { public: diff --git a/esphome/components/pcf8574/__init__.py b/esphome/components/pcf8574/__init__.py index a5f963707ff5..ebf112b85ba1 100644 --- a/esphome/components/pcf8574/__init__.py +++ b/esphome/components/pcf8574/__init__.py @@ -48,24 +48,20 @@ def validate_mode(value): return value -PCF8574_PIN_SCHEMA = cv.All( +PCF8574_PIN_SCHEMA = pins.gpio_base_schema( + PCF8574GPIOPin, + cv.int_range(min=0, max=17), + modes=[CONF_INPUT, CONF_OUTPUT], + mode_validator=validate_mode, + invertable=True, +).extend( { - cv.GenerateID(): cv.declare_id(PCF8574GPIOPin), cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=17), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -@pins.PIN_SCHEMA_REGISTRY.register("pcf8574", PCF8574_PIN_SCHEMA) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_PCF8574, PCF8574_PIN_SCHEMA) async def pcf8574_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_PCF8574]) diff --git a/esphome/components/pid/climate.py b/esphome/components/pid/climate.py index 7cd414f912ea..2c4ef688a59b 100644 --- a/esphome/components/pid/climate.py +++ b/esphome/components/pid/climate.py @@ -2,7 +2,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import climate, sensor, output -from esphome.const import CONF_ID, CONF_SENSOR +from esphome.const import CONF_HUMIDITY_SENSOR, CONF_ID, CONF_SENSOR pid_ns = cg.esphome_ns.namespace("pid") PIDClimate = pid_ns.class_("PIDClimate", climate.Climate, cg.Component) @@ -45,6 +45,7 @@ { cv.GenerateID(): cv.declare_id(PIDClimate), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE): cv.temperature, cv.Optional(CONF_COOL_OUTPUT): cv.use_id(output.FloatOutput), cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput), @@ -86,6 +87,10 @@ async def to_code(config): sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) + if CONF_HUMIDITY_SENSOR in config: + sens = await cg.get_variable(config[CONF_HUMIDITY_SENSOR]) + cg.add(var.set_humidity_sensor(sens)) + if CONF_COOL_OUTPUT in config: out = await cg.get_variable(config[CONF_COOL_OUTPUT]) cg.add(var.set_cool_output(out)) diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 1b3ddebcc59b..28d16e17abe7 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -1,5 +1,6 @@ #include "pid_autotuner.h" #include "esphome/core/log.h" +#include #ifndef M_PI #define M_PI 3.1415926535897932384626433 @@ -108,7 +109,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce } uint32_t phase = this->relay_function_.phase_count; ESP_LOGVV(TAG, "%s: >", this->id_.c_str()); - ESP_LOGVV(TAG, " Phase %u, enough=%u", phase, enough_data_phase_); + ESP_LOGVV(TAG, " Phase %" PRIu32 ", enough=%" PRIu32, phase, enough_data_phase_); if (this->enough_data_phase_ == 0) { this->enough_data_phase_ = phase; @@ -186,8 +187,8 @@ void PIDAutotuner::dump_config() { 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: %u", relay_function_.phase_count); - ESP_LOGD(TAG, " Detected %u zero-crossings", frequency_detector_.zerocrossing_intervals.size()); // NOLINT + 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, amplitude_detector_.phase_max); } diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index dab4502d401a..93b6999a008c 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -14,6 +14,16 @@ void PIDClimate::setup() { this->update_pid_(); }); this->current_temperature = this->sensor_->state; + + // register for humidity values and get initial state + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->add_on_state_callback([this](float state) { + this->current_humidity = state; + this->publish_state(); + }); + this->current_humidity = this->humidity_sensor_->state; + } + // restore set points auto restore = this->restore_state_(); if (restore.has_value()) { @@ -47,6 +57,9 @@ climate::ClimateTraits PIDClimate::traits() { traits.set_supports_current_temperature(true); traits.set_supports_two_point_target_temperature(false); + if (this->humidity_sensor_ != nullptr) + traits.set_supports_current_humidity(true); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF}); if (supports_cool_()) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index da57209a7e7b..5ae97ee10b58 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -19,6 +19,7 @@ class PIDClimate : public climate::Climate, public Component { void dump_config() override; void set_sensor(sensor::Sensor *sensor) { sensor_ = sensor; } + void set_humidity_sensor(sensor::Sensor *sensor) { humidity_sensor_ = sensor; } void set_cool_output(output::FloatOutput *cool_output) { cool_output_ = cool_output; } void set_heat_output(output::FloatOutput *heat_output) { heat_output_ = heat_output; } void set_kp(float kp) { controller_.kp_ = kp; } @@ -85,6 +86,8 @@ class PIDClimate : public climate::Climate, public Component { /// The sensor used for getting the current temperature sensor::Sensor *sensor_; + /// The sensor used for getting the current humidity + sensor::Sensor *humidity_sensor_{nullptr}; output::FloatOutput *cool_output_{nullptr}; output::FloatOutput *heat_output_{nullptr}; PIDController controller_; diff --git a/esphome/components/pid/pid_controller.cpp b/esphome/components/pid/pid_controller.cpp index 30f6038325e2..1a16f1454269 100644 --- a/esphome/components/pid/pid_controller.cpp +++ b/esphome/components/pid/pid_controller.cpp @@ -16,7 +16,7 @@ float PIDController::update(float setpoint, float process_value) { calculate_proportional_term_(); calculate_integral_term_(); - calculate_derivative_term_(); + calculate_derivative_term_(setpoint); // u(t) := p(t) + i(t) + d(t) float output = proportional_term_ + integral_term_ + derivative_term_; @@ -69,13 +69,18 @@ void PIDController::calculate_integral_term_() { integral_term_ = accumulated_integral_; } -void PIDController::calculate_derivative_term_() { +void PIDController::calculate_derivative_term_(float setpoint) { // derivative_term_ // d(t) := K_d * de(t)/dt float derivative = 0.0f; - if (dt_ != 0.0f) + if (dt_ != 0.0f) { + // remove changes to setpoint from error + if (!std::isnan(previous_setpoint_) && previous_setpoint_ != setpoint) + previous_error_ -= previous_setpoint_ - setpoint; derivative = (error_ - previous_error_) / dt_; + } previous_error_ = error_; + previous_setpoint_ = setpoint; // smooth the derivative samples derivative = weighted_average_(derivative_list_, derivative, derivative_samples_); diff --git a/esphome/components/pid/pid_controller.h b/esphome/components/pid/pid_controller.h index 05ce5f9224e3..e2a7030b571e 100644 --- a/esphome/components/pid/pid_controller.h +++ b/esphome/components/pid/pid_controller.h @@ -49,12 +49,13 @@ struct PIDController { void calculate_proportional_term_(); void calculate_integral_term_(); - void calculate_derivative_term_(); + void calculate_derivative_term_(float setpoint); float weighted_average_(std::deque &list, float new_value, int samples); float calculate_relative_time_(); /// Error from previous update used for derivative term float previous_error_ = 0; + float previous_setpoint_ = NAN; /// Accumulated integral value float accumulated_integral_ = 0; uint32_t last_time_ = 0; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 62e4fbd3416d..2cd1aeba446d 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -769,7 +769,7 @@ uint8_t Pipsolar::check_incoming_length_(uint8_t length) { uint8_t Pipsolar::check_incoming_crc_() { uint16_t crc16; - crc16 = crc16be(read_buffer_, read_pos_ - 3); + crc16 = this->pipsolar_crc_(read_buffer_, read_pos_ - 3); ESP_LOGD(TAG, "checking crc on incoming message"); if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] && ((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) { @@ -798,7 +798,7 @@ uint8_t Pipsolar::send_next_command_() { this->command_start_millis_ = millis(); this->empty_uart_buffer_(); this->read_pos_ = 0; - crc16 = crc16be(byte_command, length); + crc16 = this->pipsolar_crc_(byte_command, length); this->write_str(command); // checksum this->write(((uint8_t) ((crc16) >> 8))); // highbyte @@ -825,8 +825,8 @@ void Pipsolar::send_next_poll_() { this->command_start_millis_ = millis(); this->empty_uart_buffer_(); this->read_pos_ = 0; - crc16 = crc16be(this->used_polling_commands_[this->last_polling_command_].command, - this->used_polling_commands_[this->last_polling_command_].length); + crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); this->write_array(this->used_polling_commands_[this->last_polling_command_].command, this->used_polling_commands_[this->last_polling_command_].length); // checksum @@ -893,5 +893,17 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll } } +uint16_t Pipsolar::pipsolar_crc_(uint8_t *msg, uint8_t len) { + uint16_t crc = crc16be(msg, len); + uint8_t crc_low = crc & 0xff; + uint8_t crc_high = crc >> 8; + if (crc_low == 0x28 || crc_low == 0x0d || crc_low == 0x0a) + crc_low++; + if (crc_high == 0x28 || crc_high == 0x0d || crc_high == 0x0a) + crc_high++; + crc = (crc_high << 8) | crc_low; + return crc; +} + } // namespace pipsolar } // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h index 65fd3c670d72..f20f44f09584 100644 --- a/esphome/components/pipsolar/pipsolar.h +++ b/esphome/components/pipsolar/pipsolar.h @@ -193,7 +193,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { void empty_uart_buffer_(); uint8_t check_incoming_crc_(); uint8_t check_incoming_length_(uint8_t length); - uint16_t cal_crc_half_(uint8_t *msg, uint8_t len); + uint16_t pipsolar_crc_(uint8_t *msg, uint8_t len); uint8_t send_next_command_(); void send_next_poll_(); void queue_command_(const char *command, uint8_t length); diff --git a/esphome/components/pmsa003i/pmsa003i.h b/esphome/components/pmsa003i/pmsa003i.h index 10176218edf6..1fe4139951ae 100644 --- a/esphome/components/pmsa003i/pmsa003i.h +++ b/esphome/components/pmsa003i/pmsa003i.h @@ -17,12 +17,12 @@ struct PM25AQIData { uint16_t pm10_env, ///< Environmental PM1.0 pm25_env, ///< Environmental PM2.5 pm100_env; ///< Environmental PM10.0 - uint16_t particles_03um, ///< 0.3um Particle Count - particles_05um, ///< 0.5um Particle Count - particles_10um, ///< 1.0um Particle Count - particles_25um, ///< 2.5um Particle Count - particles_50um, ///< 5.0um Particle Count - particles_100um; ///< 10.0um Particle Count + uint16_t particles_03um, ///> 0.3um Particle Count + particles_05um, ///> 0.5um Particle Count + particles_10um, ///> 1.0um Particle Count + particles_25um, ///> 2.5um Particle Count + particles_50um, ///> 5.0um Particle Count + particles_100um; ///> 10.0um Particle Count uint16_t unused; ///< Unused uint16_t checksum; ///< Packet checksum }; diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 04aba4382ba9..de2b23b8eb33 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -195,7 +195,7 @@ void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) { void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { - float temperature = this->get_16_bit_uint_(30) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(30) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f; ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity); @@ -279,7 +279,7 @@ void PMSX003Component::parse_data_() { // Note the pm particles 50um & 100um are not returned, // as PMS5003T uses those data values for temperature and humidity. - float temperature = this->get_16_bit_uint_(24) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(24) / 10.0f; float humidity = this->get_16_bit_uint_(26) / 10.0f; ESP_LOGD(TAG, diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index eb33f669090e..cb5c16aecff8 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -1,16 +1,17 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" namespace esphome { namespace pmsx003 { // known command bytes -#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically -#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement -#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode +static const uint8_t PMS_CMD_AUTO_MANUAL = + 0xE1; // data=0: perform measurement manually, data=1: perform measurement automatically +static const uint8_t PMS_CMD_TRIG_MANUAL = 0xE2; // trigger a manual measurement +static const uint8_t PMS_CMD_ON_STANDBY = 0xE4; // data=0: go to standby mode, data=1: go to normal mode static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index eefcb529f2b9..08ccd6096e38 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -92,66 +92,78 @@ def validate_update_interval(value): icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_3UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_5UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_1_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_5_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0UM): sensor.sensor_schema( unit_of_measurement=UNIT_COUNT_DECILITRE, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 2f120bc983b0..cdcaf4267c82 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -2,14 +2,19 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import nfc -from esphome.const import CONF_ID, CONF_ON_TAG_REMOVED, CONF_ON_TAG, CONF_TRIGGER_ID +from esphome.const import ( + CONF_ID, + CONF_ON_FINISHED_WRITE, + CONF_ON_TAG_REMOVED, + CONF_ON_TAG, + CONF_TRIGGER_ID, +) CODEOWNERS = ["@OttoWinter", "@jesserockz"] AUTO_LOAD = ["binary_sensor", "nfc"] MULTI_CONF = True CONF_PN532_ID = "pn532_id" -CONF_ON_FINISHED_WRITE = "on_finished_write" pn532_ns = cg.esphome_ns.namespace("pn532") PN532 = pn532_ns.class_("PN532", cg.PollingComponent) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index cc28d7078b1f..8088e6c022f6 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -127,8 +127,18 @@ void PN532::loop() { if (!this->requested_read_) return; + auto ready = this->read_ready_(false); + if (ready == WOULDBLOCK) + return; + + bool success = false; std::vector read; - bool success = this->read_response(PN532_COMMAND_INLISTPASSIVETARGET, read); + + if (ready == READY) { + success = this->read_response(PN532_COMMAND_INLISTPASSIVETARGET, read); + } else { + this->send_ack_(); // abort still running InListPassiveTarget + } this->requested_read_ = false; @@ -286,12 +296,58 @@ bool PN532::read_ack_() { return matches; } +void PN532::send_ack_() { + ESP_LOGV(TAG, "Sending ACK for abort"); + this->write_data({0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + delay(10); +} void PN532::send_nack_() { ESP_LOGV(TAG, "Sending NACK for retransmit"); this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}); delay(10); } +enum PN532ReadReady PN532::read_ready_(bool block) { + if (this->rd_ready_ == READY) { + if (block) { + this->rd_start_time_ = 0; + this->rd_ready_ = WOULDBLOCK; + } + return READY; + } + + if (!this->rd_start_time_) { + this->rd_start_time_ = millis(); + } + + while (true) { + if (this->is_read_ready()) { + this->rd_ready_ = READY; + break; + } + + if (millis() - this->rd_start_time_ > 100) { + ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!"); + this->rd_ready_ = TIMEOUT; + break; + } + + if (!block) { + this->rd_ready_ = WOULDBLOCK; + break; + } + + yield(); + } + + auto rdy = this->rd_ready_; + if (block || rdy == TIMEOUT) { + this->rd_start_time_ = 0; + this->rd_ready_ = WOULDBLOCK; + } + return rdy; +} + void PN532::turn_off_rf_() { ESP_LOGV(TAG, "Turning RF field OFF"); this->write_command_({ diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 73b349e32897..8194d864779e 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -7,6 +7,7 @@ #include "esphome/components/nfc/nfc.h" #include "esphome/components/nfc/automation.h" +#include #include namespace esphome { @@ -19,6 +20,12 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; static const uint8_t PN532_COMMAND_POWERDOWN = 0x16; +enum PN532ReadReady { + WOULDBLOCK = 0, + TIMEOUT, + READY, +}; + class PN532BinarySensor; class PN532 : public PollingComponent { @@ -53,8 +60,11 @@ class PN532 : public PollingComponent { void turn_off_rf_(); bool write_command_(const std::vector &data); bool read_ack_(); + void send_ack_(); void send_nack_(); + enum PN532ReadReady read_ready_(bool block); + virtual bool is_read_ready() = 0; virtual bool write_data(const std::vector &data) = 0; virtual bool read_data(std::vector &data, uint8_t len) = 0; virtual bool read_response(uint8_t command, std::vector &data) = 0; @@ -74,10 +84,11 @@ class PN532 : public PollingComponent { bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); std::unique_ptr read_mifare_ultralight_tag_(std::vector &uid); - bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); - bool is_mifare_ultralight_formatted_(); + bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data); + bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); uint16_t read_mifare_ultralight_capacity_(); - bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index); + bool find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index); bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); bool clean_mifare_ultralight_(); @@ -89,6 +100,8 @@ class PN532 : public PollingComponent { std::vector triggers_ontagremoved_; std::vector current_uid_; nfc::NdefMessage *next_task_message_to_write_; + uint32_t rd_start_time_{0}; + enum PN532ReadReady rd_ready_ { WOULDBLOCK }; enum NfcTask { READ = 0, CLEAN, diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 1b91ae919ea6..b08a7336c770 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -9,93 +9,104 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { - if (!this->is_mifare_ultralight_formatted_()) { - ESP_LOGD(TAG, "Not NDEF formatted"); + std::vector data; + // pages 3 to 6 contain various info we are interested in -- do one read to grab it all + if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE, + data)) { + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + } + + if (!this->is_mifare_ultralight_formatted_(data)) { + ESP_LOGW(TAG, "Not NDEF formatted"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; - if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { + if (!this->find_mifare_ultralight_ndef_(data, message_length, message_start_index)) { + ESP_LOGW(TAG, "Couldn't find NDEF message"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); + ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index); if (message_length == 0) { return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - std::vector data; - for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { - std::vector page_data; - if (!this->read_mifare_ultralight_page_(page, page_data)) { - ESP_LOGE(TAG, "Error reading page %d", page); + // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages + const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0; + if (read_length) { + if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) { + ESP_LOGE(TAG, "Error reading tag data"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - data.insert(data.end(), page_data.begin(), page_data.end()); - - if (data.size() >= (message_length + message_start_index)) - break; } - - data.erase(data.begin(), data.begin() + message_start_index); - data.erase(data.begin() + message_length, data.end()); + // we need to trim off page 3 as well as any bytes ahead of message_start_index + data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } -bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { - if (!this->write_command_({ - PN532_COMMAND_INDATAEXCHANGE, - 0x01, // One card - nfc::MIFARE_CMD_READ, - page_num, - })) { - return false; - } +bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data) { + const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + std::vector response; - if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { - return false; + for (uint8_t i = 0; i * read_increment < num_bytes; i++) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page), + })) { + return false; + } + + if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) { + return false; + } + uint16_t bytes_offset = (i + 1) * read_increment; + auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes); + + if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) { + data.insert(data.end(), response.begin() + 1, pages_in_end_itr); + } } - data.erase(data.begin()); - // We only want 1 page of data but the PN532 returns 4 at once. - data.erase(data.begin() + 4, data.end()); - ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str()); + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); return true; } -bool PN532::is_mifare_ultralight_formatted_() { - std::vector data; - if (this->read_mifare_ultralight_page_(4, data)) { - return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); - } - return true; +bool PN532::is_mifare_ultralight_formatted_(const std::vector &page_3_to_6) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + return (page_3_to_6.size() > p4_offset + 3) && + !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && + (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); } uint16_t PN532::read_mifare_ultralight_capacity_() { std::vector data; - if (this->read_mifare_ultralight_page_(3, data)) { + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) { + ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U); return data[2] * 8U; } return 0; } -bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) { - std::vector data; - for (int page = 4; page < 6; page++) { - std::vector page_data; - if (!this->read_mifare_ultralight_page_(page, page_data)) { - return false; - } - data.insert(data.end(), page_data.begin(), page_data.end()); +bool PN532::find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + if (!(page_3_to_6.size() > p4_offset + 5)) { + return false; } - if (data[0] == 0x03) { - message_length = data[1]; + + if (page_3_to_6[p4_offset + 0] == 0x03) { + message_length = page_3_to_6[p4_offset + 1]; message_start_index = 2; return true; - } else if (data[5] == 0x03) { - message_length = data[6]; + } else if (page_3_to_6[p4_offset + 5] == 0x03) { + message_length = page_3_to_6[p4_offset + 6]; message_start_index = 7; return true; } @@ -111,7 +122,7 @@ bool PN532::write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMes uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); if (buffer_length > capacity) { - ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity); + ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity); return false; } @@ -164,13 +175,13 @@ bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector }); data.insert(data.end(), write_data.begin(), write_data.end()); if (!this->write_command_(data)) { - ESP_LOGE(TAG, "Error writing page %d", page_num); + ESP_LOGE(TAG, "Error writing page %u", page_num); return false; } std::vector response; if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) { - ESP_LOGE(TAG, "Error writing page %d", page_num); + ESP_LOGE(TAG, "Error writing page %u", page_num); return false; } diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index e7c99e94b073..b306222a21c9 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -12,6 +12,14 @@ namespace pn532_i2c { static const char *const TAG = "pn532_i2c"; +bool PN532I2C::is_read_ready() { + uint8_t ready; + if (!this->read_bytes_raw(&ready, 1)) { + return false; + } + return ready == 0x01; +} + bool PN532I2C::write_data(const std::vector &data) { return this->write(data.data(), data.size()) == i2c::ERROR_OK; } @@ -19,19 +27,8 @@ bool PN532I2C::write_data(const std::vector &data) { bool PN532I2C::read_data(std::vector &data, uint8_t len) { delay(1); - std::vector ready; - ready.resize(1); - uint32_t start_time = millis(); - while (true) { - if (this->read_bytes_raw(ready.data(), 1)) { - if (ready[0] == 0x01) - break; - } - - if (millis() - start_time > 100) { - ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!"); - return false; - } + if (this->read_ready_(true) != pn532::PN532ReadReady::READY) { + return false; } data.resize(len + 1); diff --git a/esphome/components/pn532_i2c/pn532_i2c.h b/esphome/components/pn532_i2c/pn532_i2c.h index 95cf8eeb3660..00c0df206d67 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.h +++ b/esphome/components/pn532_i2c/pn532_i2c.h @@ -14,6 +14,7 @@ class PN532I2C : public pn532::PN532, public i2c::I2CDevice { void dump_config() override; protected: + bool is_read_ready() override; bool write_data(const std::vector &data) override; bool read_data(std::vector &data, uint8_t len) override; bool read_response(uint8_t command, std::vector &data) override; diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index be58f265b96d..d55d8161d807 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -21,6 +21,14 @@ void PN532Spi::setup() { PN532::setup(); } +bool PN532Spi::is_read_ready() { + this->enable(); + this->write_byte(0x02); + bool ready = this->read_byte() == 0x01; + this->disable(); + return ready; +} + bool PN532Spi::write_data(const std::vector &data) { this->enable(); delay(2); @@ -34,24 +42,8 @@ bool PN532Spi::write_data(const std::vector &data) { } bool PN532Spi::read_data(std::vector &data, uint8_t len) { - ESP_LOGV(TAG, "Waiting for ready byte..."); - - uint32_t start_time = millis(); - while (true) { - this->enable(); - // First byte, communication mode: Read state - this->write_byte(0x02); - bool ready = this->read_byte() == 0x01; - this->disable(); - if (ready) - break; - ESP_LOGV(TAG, "Not ready yet..."); - - if (millis() - start_time > 100) { - ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!"); - return false; - } - yield(); + if (this->read_ready_(true) != pn532::PN532ReadReady::READY) { + return false; } // Read data (transmission from the PN532 to the host) @@ -72,22 +64,8 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { bool PN532Spi::read_response(uint8_t command, std::vector &data) { ESP_LOGV(TAG, "Reading response"); - uint32_t start_time = millis(); - while (true) { - this->enable(); - // First byte, communication mode: Read state - this->write_byte(0x02); - bool ready = this->read_byte() == 0x01; - this->disable(); - if (ready) - break; - ESP_LOGV(TAG, "Not ready yet..."); - - if (millis() - start_time > 100) { - ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!"); - return false; - } - yield(); + if (this->read_ready_(true) != pn532::PN532ReadReady::READY) { + return false; } this->enable(); diff --git a/esphome/components/pn532_spi/pn532_spi.h b/esphome/components/pn532_spi/pn532_spi.h index 2d8312813d10..b7adca22e97b 100644 --- a/esphome/components/pn532_spi/pn532_spi.h +++ b/esphome/components/pn532_spi/pn532_spi.h @@ -18,6 +18,7 @@ class PN532Spi : public pn532::PN532, void dump_config() override; protected: + bool is_read_ready() override; bool write_data(const std::vector &data) override; bool read_data(std::vector &data, uint8_t len) override; bool read_response(uint8_t command, std::vector &data) override; diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py new file mode 100644 index 000000000000..e3589ea4491f --- /dev/null +++ b/esphome/components/pn7150/__init__.py @@ -0,0 +1,215 @@ +from esphome import automation, pins +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import nfc +from esphome.const import ( + CONF_ID, + CONF_IRQ_PIN, + CONF_MESSAGE, + CONF_ON_FINISHED_WRITE, + CONF_ON_TAG_REMOVED, + CONF_ON_TAG, + CONF_TRIGGER_ID, +) + +AUTO_LOAD = ["binary_sensor", "nfc"] +CODEOWNERS = ["@kbx81", "@jesserockz"] + +CONF_EMULATION_MESSAGE = "emulation_message" +CONF_EMULATION_OFF = "emulation_off" +CONF_EMULATION_ON = "emulation_on" +CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record" +CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan" +CONF_PN7150_ID = "pn7150_id" +CONF_POLLING_OFF = "polling_off" +CONF_POLLING_ON = "polling_on" +CONF_SET_CLEAN_MODE = "set_clean_mode" +CONF_SET_EMULATION_MESSAGE = "set_emulation_message" +CONF_SET_FORMAT_MODE = "set_format_mode" +CONF_SET_READ_MODE = "set_read_mode" +CONF_SET_WRITE_MESSAGE = "set_write_message" +CONF_SET_WRITE_MODE = "set_write_mode" +CONF_TAG_TTL = "tag_ttl" +CONF_VEN_PIN = "ven_pin" + +pn7150_ns = cg.esphome_ns.namespace("pn7150") +PN7150 = pn7150_ns.class_("PN7150", nfc.Nfcc, cg.Component) + +EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action) +EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action) +PollingOffAction = pn7150_ns.class_("PollingOffAction", automation.Action) +PollingOnAction = pn7150_ns.class_("PollingOnAction", automation.Action) +SetCleanModeAction = pn7150_ns.class_("SetCleanModeAction", automation.Action) +SetEmulationMessageAction = pn7150_ns.class_( + "SetEmulationMessageAction", automation.Action +) +SetFormatModeAction = pn7150_ns.class_("SetFormatModeAction", automation.Action) +SetReadModeAction = pn7150_ns.class_("SetReadModeAction", automation.Action) +SetWriteMessageAction = pn7150_ns.class_("SetWriteMessageAction", automation.Action) +SetWriteModeAction = pn7150_ns.class_("SetWriteModeAction", automation.Action) + + +PN7150OnEmulatedTagScanTrigger = pn7150_ns.class_( + "PN7150OnEmulatedTagScanTrigger", automation.Trigger.template() +) + +PN7150OnFinishedWriteTrigger = pn7150_ns.class_( + "PN7150OnFinishedWriteTrigger", automation.Trigger.template() +) + +PN7150IsWritingCondition = pn7150_ns.class_( + "PN7150IsWritingCondition", automation.Condition +) + + +IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition) + + +SIMPLE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PN7150), + } +) + +SET_MESSAGE_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(PN7150), + cv.Required(CONF_MESSAGE): cv.templatable(cv.string), + cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean, + } +) + +PN7150_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(PN7150), + cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + PN7150OnEmulatedTagScanTrigger + ), + } + ), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + PN7150OnFinishedWriteTrigger + ), + } + ), + cv.Optional(CONF_ON_TAG): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), + } + ), + cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), + } + ), + cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_EMULATION_MESSAGE): cv.string, + cv.Optional(CONF_TAG_TTL): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +@automation.register_action( + "tag.set_emulation_message", + SetEmulationMessageAction, + SET_MESSAGE_ACTION_SCHEMA, +) +@automation.register_action( + "tag.set_write_message", + SetWriteMessageAction, + SET_MESSAGE_ACTION_SCHEMA, +) +async def pn7150_set_message_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_MESSAGE], args, cg.std_string) + cg.add(var.set_message(template_)) + template_ = await cg.templatable( + config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_ + ) + cg.add(var.set_include_android_app_record(template_)) + return var + + +@automation.register_action( + "tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action( + "tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA +) +async def pn7150_simple_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 + + +async def setup_pn7150(var, config): + await cg.register_component(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) + cg.add(var.set_irq_pin(pin)) + + pin = await cg.gpio_pin_expression(config[CONF_VEN_PIN]) + cg.add(var.set_ven_pin(pin)) + + if emulation_message_config := config.get(CONF_EMULATION_MESSAGE): + cg.add(var.set_tag_emulation_message(emulation_message_config)) + cg.add(var.set_tag_emulation_on()) + + if CONF_TAG_TTL in config: + cg.add(var.set_tag_ttl(config[CONF_TAG_TTL])) + + for conf in config.get(CONF_ON_TAG, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontag_trigger(trigger)) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf + ) + + for conf in config.get(CONF_ON_TAG_REMOVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontagremoved_trigger(trigger)) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf + ) + + for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_FINISHED_WRITE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + +@automation.register_condition( + "pn7150.is_writing", + PN7150IsWritingCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(PN7150), + } + ), +) +async def pn7150_is_writing_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/pn7150/automation.h b/esphome/components/pn7150/automation.h new file mode 100644 index 000000000000..aebb1b7573e6 --- /dev/null +++ b/esphome/components/pn7150/automation.h @@ -0,0 +1,82 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/pn7150/pn7150.h" + +namespace esphome { +namespace pn7150 { + +class PN7150OnEmulatedTagScanTrigger : public Trigger<> { + public: + explicit PN7150OnEmulatedTagScanTrigger(PN7150 *parent) { + parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); }); + } +}; + +class PN7150OnFinishedWriteTrigger : public Trigger<> { + public: + explicit PN7150OnFinishedWriteTrigger(PN7150 *parent) { + parent->add_on_finished_write_callback([this]() { this->trigger(); }); + } +}; + +template class PN7150IsWritingCondition : public Condition, public Parented { + public: + bool check(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(); } +}; + +template class EmulationOnAction : public Action, public Parented { + void play(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(); } +}; + +template class PollingOnAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->set_polling_on(); } +}; + +template class SetCleanModeAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->clean_mode(); } +}; + +template class SetFormatModeAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->format_mode(); } +}; + +template class SetReadModeAction : public Action, public Parented { + void play(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 { + this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), + this->include_android_app_record_.optional_value(x...)); + } +}; + +template class SetWriteMessageAction : public Action, public Parented { + TEMPLATABLE_VALUE(std::string, message) + TEMPLATABLE_VALUE(bool, include_android_app_record) + + void play(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(); } +}; + +} // namespace pn7150 +} // namespace esphome diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp new file mode 100644 index 000000000000..be4d6c1bb77a --- /dev/null +++ b/esphome/components/pn7150/pn7150.cpp @@ -0,0 +1,1143 @@ +#include "automation.h" +#include "pn7150.h" + +#include + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7150 { + +static const char *const TAG = "pn7150"; + +void PN7150::setup() { + this->irq_pin_->setup(); + this->ven_pin_->setup(); + + this->nci_fsm_transition_(); // kick off reset & init processes +} + +void PN7150::dump_config() { + ESP_LOGCONFIG(TAG, "PN7150:"); + LOG_PIN(" IRQ pin: ", this->irq_pin_); + LOG_PIN(" VEN pin: ", this->ven_pin_); +} + +void PN7150::loop() { + this->nci_fsm_transition_(); + this->purge_old_tags_(); +} + +void PN7150::set_tag_emulation_message(std::shared_ptr message) { + this->card_emulation_message_ = std::move(message); + ESP_LOGD(TAG, "Tag emulation message set"); +} + +void PN7150::set_tag_emulation_message(const optional &message, + const optional include_android_app_record) { + if (!message.has_value()) { + return; + } + + auto ndef_message = make_unique(); + + ndef_message->add_uri_record(message.value()); + + if (!include_android_app_record.has_value() || include_android_app_record.value()) { + auto ext_record = make_unique(); + ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE); + ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE); + ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD); + ndef_message->add_record(std::move(ext_record)); + } + + this->card_emulation_message_ = std::move(ndef_message); + ESP_LOGD(TAG, "Tag emulation message set"); +} + +void PN7150::set_tag_emulation_message(const char *message, const bool include_android_app_record) { + this->set_tag_emulation_message(std::string(message), include_android_app_record); +} + +void PN7150::set_tag_emulation_off() { + if (this->listening_enabled_) { + this->listening_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag emulation disabled"); +} + +void PN7150::set_tag_emulation_on() { + if (this->card_emulation_message_ == nullptr) { + ESP_LOGE(TAG, "No NDEF message is set; tag emulation cannot be enabled"); + return; + } + if (!this->listening_enabled_) { + this->listening_enabled_ = true; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag emulation enabled"); +} + +void PN7150::set_polling_off() { + if (this->polling_enabled_) { + this->polling_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling disabled"); +} + +void PN7150::set_polling_on() { + if (!this->polling_enabled_) { + this->polling_enabled_ = true; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling enabled"); +} + +void PN7150::read_mode() { + this->next_task_ = EP_READ; + ESP_LOGD(TAG, "Waiting to read next tag"); +} + +void PN7150::clean_mode() { + this->next_task_ = EP_CLEAN; + ESP_LOGD(TAG, "Waiting to clean next tag"); +} + +void PN7150::format_mode() { + this->next_task_ = EP_FORMAT; + ESP_LOGD(TAG, "Waiting to format next tag"); +} + +void PN7150::write_mode() { + if (this->next_task_message_to_write_ == nullptr) { + ESP_LOGW(TAG, "Message to write must be set before setting write mode"); + return; + } + + this->next_task_ = EP_WRITE; + ESP_LOGD(TAG, "Waiting to write next tag"); +} + +void PN7150::set_tag_write_message(std::shared_ptr message) { + this->next_task_message_to_write_ = std::move(message); + ESP_LOGD(TAG, "Message to write has been set"); +} + +void PN7150::set_tag_write_message(optional message, optional include_android_app_record) { + if (!message.has_value()) { + return; + } + + auto ndef_message = make_unique(); + + ndef_message->add_uri_record(message.value()); + + if (!include_android_app_record.has_value() || include_android_app_record.value()) { + auto ext_record = make_unique(); + ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE); + ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE); + ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD); + ndef_message->add_record(std::move(ext_record)); + } + + this->next_task_message_to_write_ = std::move(ndef_message); + ESP_LOGD(TAG, "Message to write has been set"); +} + +uint8_t PN7150::set_test_mode(const TestMode test_mode, const std::vector &data, + std::vector &result) { + auto test_oid = TEST_PRBS_OID; + + switch (test_mode) { + case TestMode::TEST_PRBS: + // test_oid = TEST_PRBS_OID; + break; + + case TestMode::TEST_ANTENNA: + test_oid = TEST_ANTENNA_OID; + break; + + case TestMode::TEST_GET_REGISTER: + test_oid = TEST_GET_REGISTER_OID; + break; + + case TestMode::TEST_NONE: + default: + ESP_LOGD(TAG, "Exiting test mode"); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + return nfc::STATUS_OK; + } + + if (this->reset_core_(true, true) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to reset NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_RESET); + result.clear(); + return nfc::STATUS_FAILED; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_INIT); + } + if (this->init_core_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to initialise NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_INIT); + result.clear(); + return nfc::STATUS_FAILED; + } else { + this->nci_fsm_set_state_(NCIState::TEST); + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, test_oid, data); + + ESP_LOGW(TAG, "Starting test mode, OID 0x%02X", test_oid); + auto status = this->transceive_(tx, rx, NFCC_INIT_TIMEOUT); + + if (status != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to start test mode, OID 0x%02X", test_oid); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + result.clear(); + } else { + result = rx.get_message(); + result.erase(result.begin(), result.begin() + 4); // remove NCI header + if (!result.empty()) { + ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes(result).c_str()); + } + } + return status; +} + +uint8_t PN7150::reset_core_(const bool reset_config, const bool power) { + if (power) { + this->ven_pin_->digital_write(true); + delay(NFCC_DEFAULT_TIMEOUT); + this->ven_pin_->digital_write(false); + delay(NFCC_DEFAULT_TIMEOUT); + this->ven_pin_->digital_write(true); + delay(NFCC_INIT_TIMEOUT); + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_RESET_OID, + {(uint8_t) reset_config}); + + if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending reset command"); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return rx.get_simple_status_response(); + } + // verify reset response + if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_RESPONSE)) || (!rx.message_length_is(3)) || + (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != 0x11) || + (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] != (uint8_t) reset_config)) { + ESP_LOGE(TAG, "Reset response was malformed: %s", nfc::format_bytes(rx.get_message()).c_str()); + 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"); + + return nfc::STATUS_OK; +} + +uint8_t PN7150::init_core_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_INIT_OID); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending initialise command"); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + uint8_t manf_id = rx.get_message()[15 + rx.get_message()[8]]; + uint8_t hw_version = rx.get_message()[16 + rx.get_message()[8]]; + uint8_t rom_code_version = rx.get_message()[17 + rx.get_message()[8]]; + 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); + + return rx.get_simple_status_response(); +} + +uint8_t PN7150::send_init_config_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, nfc::NCI_CORE_SET_CONFIG_OID); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error enabling proprietary extensions"); + return nfc::STATUS_FAILED; + } + + tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, + std::vector(std::begin(PMU_CFG), std::end(PMU_CFG))); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending PMU config"); + return nfc::STATUS_FAILED; + } + + return this->send_core_config_(); +} + +uint8_t PN7150::send_core_config_() { + const auto *core_config_begin = std::begin(CORE_CONFIG_SOLO); + const auto *core_config_end = std::end(CORE_CONFIG_SOLO); + this->core_config_is_solo_ = true; + + if (this->listening_enabled_ && this->polling_enabled_) { + core_config_begin = std::begin(CORE_CONFIG_RW_CE); + core_config_end = std::end(CORE_CONFIG_RW_CE); + this->core_config_is_solo_ = false; + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, + std::vector(core_config_begin, core_config_end)); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error sending core config"); + return nfc::STATUS_FAILED; + } + + return nfc::STATUS_OK; +} + +uint8_t PN7150::refresh_core_config_() { + bool core_config_should_be_solo = !(this->listening_enabled_ && this->polling_enabled_); + + if (this->nci_state_ == NCIState::RFST_DISCOVERY) { + if (this->stop_discovery_() != nfc::STATUS_OK) { + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + return nfc::STATUS_FAILED; + } + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + + if (this->core_config_is_solo_ != core_config_should_be_solo) { + if (this->send_core_config_() != nfc::STATUS_OK) { + ESP_LOGV(TAG, "Failed to refresh core config"); + return nfc::STATUS_FAILED; + } + } + this->config_refresh_pending_ = false; + return nfc::STATUS_OK; +} + +uint8_t PN7150::set_discover_map_() { + std::vector discover_map = {sizeof(RF_DISCOVER_MAP_CONFIG) / 3}; + discover_map.insert(discover_map.end(), std::begin(RF_DISCOVER_MAP_CONFIG), std::end(RF_DISCOVER_MAP_CONFIG)); + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_MAP_OID, discover_map); + + if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending discover map poll config"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7150::set_listen_mode_routing_() { + nfc::NciMessage rx; + nfc::NciMessage tx( + nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_SET_LISTEN_MODE_ROUTING_OID, + std::vector(std::begin(RF_LISTEN_MODE_ROUTING_CONFIG), std::end(RF_LISTEN_MODE_ROUTING_CONFIG))); + + if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error setting listen mode routing config"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7150::start_discovery_() { + const uint8_t *rf_discovery_config = RF_DISCOVERY_CONFIG; + uint8_t length = sizeof(RF_DISCOVERY_CONFIG); + + if (!this->listening_enabled_) { + length = sizeof(RF_DISCOVERY_POLL_CONFIG); + rf_discovery_config = RF_DISCOVERY_POLL_CONFIG; + } else if (!this->polling_enabled_) { + length = sizeof(RF_DISCOVERY_LISTEN_CONFIG); + rf_discovery_config = RF_DISCOVERY_LISTEN_CONFIG; + } + + std::vector discover_config = std::vector((length * 2) + 1); + + discover_config[0] = length; + for (uint8_t i = 0; i < length; i++) { + discover_config[(i * 2) + 1] = rf_discovery_config[i]; + discover_config[(i * 2) + 2] = 0x01; // RF Technology and Mode will be executed in every discovery period + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, discover_config); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + switch (rx.get_simple_status_response()) { + // in any of these cases, we are either already in or will remain in discovery, which satisfies the function call + case nfc::STATUS_OK: + case nfc::DISCOVERY_ALREADY_STARTED: + case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED: + case nfc::DISCOVERY_TEAR_DOWN: + return nfc::STATUS_OK; + + default: + ESP_LOGE(TAG, "Error starting discovery"); + return nfc::STATUS_FAILED; + } + } + + return nfc::STATUS_OK; +} + +uint8_t PN7150::stop_discovery_() { return this->deactivate_(nfc::DEACTIVATION_TYPE_IDLE, NFCC_TAG_WRITE_TIMEOUT); } + +uint8_t PN7150::deactivate_(const uint8_t type, const uint16_t timeout) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {type}); + + auto status = this->transceive_(tx, rx, timeout); + // if (status != nfc::STATUS_OK) { + // ESP_LOGE(TAG, "Error sending deactivate type %u", type); + // return nfc::STATUS_FAILED; + // } + return status; +} + +void PN7150::select_endpoint_() { + if (this->discovered_endpoint_.empty()) { + ESP_LOGW(TAG, "No cached tags to select"); + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + return; + } + std::vector endpoint_data = {this->discovered_endpoint_[0].id, this->discovered_endpoint_[0].protocol, + 0x01}; // that last byte is the interface ID + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + if (!this->discovered_endpoint_[i].trig_called) { + endpoint_data = {this->discovered_endpoint_[i].id, this->discovered_endpoint_[i].protocol, + 0x01}; // that last byte is the interface ID + this->selecting_endpoint_ = i; + break; + } + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_SELECT_OID, endpoint_data); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error selecting endpoint"); + } else { + this->nci_fsm_set_state_(NCIState::EP_SELECTING); + } +} + +uint8_t PN7150::read_endpoint_data_(nfc::NfcTag &tag) { + uint8_t type = nfc::guess_tag_type(tag.get_uid().size()); + + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + ESP_LOGV(TAG, "Reading Mifare classic"); + return this->read_mifare_classic_tag_(tag); + + case nfc::TAG_TYPE_2: + ESP_LOGV(TAG, "Reading Mifare ultralight"); + return this->read_mifare_ultralight_tag_(tag); + + case nfc::TAG_TYPE_UNKNOWN: + default: + ESP_LOGV(TAG, "Cannot determine tag type"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7150::clean_endpoint_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->format_mifare_classic_mifare_(); + + case nfc::TAG_TYPE_2: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for cleaning"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7150::format_endpoint_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->format_mifare_classic_ndef_(); + + case nfc::TAG_TYPE_2: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for formatting"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7150::write_endpoint_(std::vector &uid, std::shared_ptr &message) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->write_mifare_classic_tag_(message); + + case nfc::TAG_TYPE_2: + return this->write_mifare_ultralight_tag_(uid, message); + + default: + ESP_LOGE(TAG, "Unsupported tag for writing"); + break; + } + return nfc::STATUS_FAILED; +} + +std::unique_ptr PN7150::build_tag_(const uint8_t mode_tech, const std::vector &data) { + switch (mode_tech) { + case (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA): { + uint8_t uid_length = data[2]; + if (!uid_length) { + ESP_LOGE(TAG, "UID length cannot be zero"); + return nullptr; + } + std::vector uid(data.begin() + 3, data.begin() + 3 + uid_length); + const auto *tag_type_str = + nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2; + return make_unique(uid, tag_type_str); + } + } + return nullptr; +} + +optional PN7150::find_tag_uid_(const std::vector &uid) { + if (!this->discovered_endpoint_.empty()) { + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid(); + bool uid_match = (uid.size() == existing_tag_uid.size()); + + if (uid_match) { + for (size_t i = 0; i < uid.size(); i++) { + uid_match &= (uid[i] == existing_tag_uid[i]); + } + if (uid_match) { + return i; + } + } + } + } + return nullopt; +} + +void PN7150::purge_old_tags_() { + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + if (millis() - this->discovered_endpoint_[i].last_seen > this->tag_ttl_) { + this->erase_tag_(i); + } + } +} + +void PN7150::erase_tag_(const uint8_t tag_index) { + if (tag_index < this->discovered_endpoint_.size()) { + for (auto *trigger : this->triggers_ontagremoved_) { + trigger->process(this->discovered_endpoint_[tag_index].tag); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } + ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); + this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); + } +} + +void PN7150::nci_fsm_transition_() { + switch (this->nci_state_) { + case NCIState::NFCC_RESET: + if (this->reset_core_(true, true) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to reset NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_RESET); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_INIT); + } + // fall through + + case NCIState::NFCC_INIT: + if (this->init_core_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to initialise NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_INIT); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); + } + // fall through + + case NCIState::NFCC_CONFIG: + if (this->send_init_config_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to send initial config"); + this->nci_fsm_set_error_state_(NCIState::NFCC_CONFIG); + return; + } else { + this->config_refresh_pending_ = false; + this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); + } + // fall through + + case NCIState::NFCC_SET_DISCOVER_MAP: + if (this->set_discover_map_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to set discover map"); + this->nci_fsm_set_error_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); + } + // fall through + + case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: + if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to set listen mode routing"); + this->nci_fsm_set_error_state_(NCIState::RFST_IDLE); + return; + } else { + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + // fall through + + case NCIState::RFST_IDLE: + if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { + this->stop_discovery_(); + } + + if (this->config_refresh_pending_) { + this->refresh_core_config_(); + } + + if (!this->listening_enabled_ && !this->polling_enabled_) { + return; + } + + if (this->start_discovery_() != nfc::STATUS_OK) { + ESP_LOGV(TAG, "Failed to start discovery"); + this->nci_fsm_set_error_state_(NCIState::RFST_DISCOVERY); + } else { + this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY); + } + return; + + case NCIState::RFST_W4_HOST_SELECT: + select_endpoint_(); + // fall through + + // All cases below are waiting for NOTIFICATION messages + case NCIState::RFST_DISCOVERY: + if (this->config_refresh_pending_) { + this->refresh_core_config_(); + } + // fall through + + case NCIState::RFST_LISTEN_ACTIVE: + case NCIState::RFST_LISTEN_SLEEP: + case NCIState::RFST_POLL_ACTIVE: + case NCIState::EP_SELECTING: + case NCIState::EP_DEACTIVATING: + if (this->irq_pin_->digital_read()) { + this->process_message_(); + } + break; + + case NCIState::TEST: + case NCIState::FAILED: + case NCIState::NONE: + default: + return; + } +} + +void PN7150::nci_fsm_set_state_(NCIState new_state) { + ESP_LOGVV(TAG, "nci_fsm_set_state_(%u)", (uint8_t) new_state); + this->nci_state_ = new_state; + this->nci_state_error_ = NCIState::NONE; + this->error_count_ = 0; + this->last_nci_state_change_ = millis(); +} + +bool PN7150::nci_fsm_set_error_state_(NCIState new_state) { + ESP_LOGVV(TAG, "nci_fsm_set_error_state_(%u); error_count_ = %u", (uint8_t) new_state, this->error_count_); + this->nci_state_error_ = new_state; + if (this->error_count_++ > NFCC_MAX_ERROR_COUNT) { + if ((this->nci_state_error_ == NCIState::NFCC_RESET) || (this->nci_state_error_ == NCIState::NFCC_INIT) || + (this->nci_state_error_ == NCIState::NFCC_CONFIG)) { + ESP_LOGE(TAG, "Too many initialization failures -- check device connections"); + this->mark_failed(); + this->nci_fsm_set_state_(NCIState::FAILED); + } else { + ESP_LOGW(TAG, "Too many errors transitioning to state %u; resetting NFCC", (uint8_t) this->nci_state_error_); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + } + } + return this->error_count_ > NFCC_MAX_ERROR_COUNT; +} + +void PN7150::process_message_() { + nfc::NciMessage rx; + if (this->read_nfcc(rx, NFCC_DEFAULT_TIMEOUT) != nfc::STATUS_OK) { + return; // No data + } + + switch (rx.get_message_type()) { + case nfc::NCI_PKT_MT_CTRL_NOTIFICATION: + if (rx.get_gid() == nfc::RF_GID) { + switch (rx.get_oid()) { + case nfc::RF_INTF_ACTIVATED_OID: + ESP_LOGVV(TAG, "RF_INTF_ACTIVATED_OID"); + this->process_rf_intf_activated_oid_(rx); + return; + + case nfc::RF_DISCOVER_OID: + ESP_LOGVV(TAG, "RF_DISCOVER_OID"); + this->process_rf_discover_oid_(rx); + return; + + case nfc::RF_DEACTIVATE_OID: + ESP_LOGVV(TAG, "RF_DEACTIVATE_OID: type: 0x%02X, reason: 0x%02X", rx.get_message()[3], rx.get_message()[4]); + this->process_rf_deactivate_oid_(rx); + return; + + default: + ESP_LOGV(TAG, "Unimplemented RF OID received: 0x%02X", rx.get_oid()); + } + } else if (rx.get_gid() == nfc::NCI_CORE_GID) { + switch (rx.get_oid()) { + case nfc::NCI_CORE_GENERIC_ERROR_OID: + ESP_LOGV(TAG, "NCI_CORE_GENERIC_ERROR_OID:"); + switch (rx.get_simple_status_response()) { + case nfc::DISCOVERY_ALREADY_STARTED: + ESP_LOGV(TAG, " DISCOVERY_ALREADY_STARTED"); + break; + + case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED: + // Tag removed too soon + ESP_LOGV(TAG, " DISCOVERY_TARGET_ACTIVATION_FAILED"); + if (this->nci_state_ == NCIState::EP_SELECTING) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + if (!this->discovered_endpoint_.empty()) { + this->erase_tag_(this->selecting_endpoint_); + } + } else { + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + break; + + case nfc::DISCOVERY_TEAR_DOWN: + ESP_LOGV(TAG, " DISCOVERY_TEAR_DOWN"); + break; + + default: + ESP_LOGW(TAG, "Unknown error: 0x%02X", rx.get_simple_status_response()); + break; + } + break; + + default: + ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid()); + } + } else { + ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + break; + + case nfc::NCI_PKT_MT_CTRL_RESPONSE: + ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), + nfc::format_bytes(rx.get_message()).c_str()); + break; + + case nfc::NCI_PKT_MT_CTRL_COMMAND: + ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str()); + break; + + case nfc::NCI_PKT_MT_DATA: + this->process_data_message_(rx); + break; + + default: + ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str()); + break; + } +} + +void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoint was activated + uint8_t discovery_id = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_DISCOVERY_ID); + uint8_t interface = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_INTERFACE); + uint8_t protocol = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_PROTOCOL); + uint8_t mode_tech = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MODE_TECH); + uint8_t max_size = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MAX_SIZE); + + ESP_LOGVV(TAG, "Endpoint activated -- interface: 0x%02X, protocol: 0x%02X, mode&tech: 0x%02X, max payload: %u", + interface, protocol, mode_tech, max_size); + + if (mode_tech & nfc::MODE_LISTEN_MASK) { + ESP_LOGVV(TAG, "Tag activated in listen mode"); + this->nci_fsm_set_state_(NCIState::RFST_LISTEN_ACTIVE); + return; + } + + this->nci_fsm_set_state_(NCIState::RFST_POLL_ACTIVE); + auto incoming_tag = + this->build_tag_(mode_tech, std::vector(rx.get_message().begin() + 10, rx.get_message().end())); + + if (incoming_tag == nullptr) { + ESP_LOGE(TAG, "Could not build tag"); + } else { + auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid()); + if (tag_loc.has_value()) { + this->discovered_endpoint_[tag_loc.value()].id = discovery_id; + this->discovered_endpoint_[tag_loc.value()].protocol = protocol; + this->discovered_endpoint_[tag_loc.value()].last_seen = millis(); + ESP_LOGVV(TAG, "Tag cache updated"); + } else { + this->discovered_endpoint_.emplace_back( + DiscoveredEndpoint{discovery_id, protocol, millis(), std::move(incoming_tag), false}); + tag_loc = this->discovered_endpoint_.size() - 1; + ESP_LOGVV(TAG, "Tag added to cache"); + } + + auto &working_endpoint = this->discovered_endpoint_[tag_loc.value()]; + + switch (this->next_task_) { + case EP_CLEAN: + ESP_LOGD(TAG, " Tag cleaning..."); + if (this->clean_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { + ESP_LOGE(TAG, " Tag cleaning incomplete"); + } + ESP_LOGD(TAG, " Tag cleaned!"); + break; + + case EP_FORMAT: + ESP_LOGD(TAG, " Tag formatting..."); + if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error formatting tag as NDEF"); + } + ESP_LOGD(TAG, " Tag formatted!"); + break; + + case EP_WRITE: + if (this->next_task_message_to_write_ != nullptr) { + ESP_LOGD(TAG, " Tag writing..."); + ESP_LOGD(TAG, " 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 { + ESP_LOGD(TAG, " Writing NDEF data"); + if (this->write_endpoint_(working_endpoint.tag->get_uid(), this->next_task_message_to_write_) != + nfc::STATUS_OK) { + ESP_LOGE(TAG, " Failed to write message to tag"); + } + ESP_LOGD(TAG, " Finished writing NDEF data"); + this->next_task_message_to_write_ = nullptr; + this->on_finished_write_callback_.call(); + } + } + break; + + case EP_READ: + default: + if (!working_endpoint.trig_called) { + ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), + nfc::format_uid(working_endpoint.tag->get_uid()).c_str()); + if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) { + ESP_LOGW(TAG, " Unable to read NDEF record(s)"); + } else if (working_endpoint.tag->has_ndef_message()) { + const auto message = working_endpoint.tag->get_ndef_message(); + const auto records = message->get_records(); + ESP_LOGD(TAG, " NDEF record(s):"); + for (const auto &record : records) { + ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); + } + } else { + ESP_LOGW(TAG, " No NDEF records found"); + } + for (auto *trigger : this->triggers_ontag_) { + trigger->process(working_endpoint.tag); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } + working_endpoint.trig_called = true; + break; + } + } + if (working_endpoint.tag->get_tag_type() == nfc::MIFARE_CLASSIC) { + this->halt_mifare_classic_tag_(); + } + } + if (this->next_task_ != EP_READ) { + this->read_mode(); + } + + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::EP_DEACTIVATING); +} + +void PN7150::process_rf_discover_oid_(nfc::NciMessage &rx) { + auto incoming_tag = this->build_tag_(rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH), + std::vector(rx.get_message().begin() + 7, rx.get_message().end())); + + if (incoming_tag == nullptr) { + ESP_LOGE(TAG, "Could not build tag!"); + } else { + auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid()); + if (tag_loc.has_value()) { + this->discovered_endpoint_[tag_loc.value()].id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID); + this->discovered_endpoint_[tag_loc.value()].protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL); + this->discovered_endpoint_[tag_loc.value()].last_seen = millis(); + ESP_LOGVV(TAG, "Tag found & updated"); + } else { + this->discovered_endpoint_.emplace_back(DiscoveredEndpoint{rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID), + rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL), + millis(), std::move(incoming_tag), false}); + ESP_LOGVV(TAG, "Tag saved"); + } + } + + if (rx.get_message().back() != nfc::RF_DISCOVER_NTF_NT_MORE) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + ESP_LOGVV(TAG, "Discovered %u endpoints", this->discovered_endpoint_.size()); + } +} + +void PN7150::process_rf_deactivate_oid_(nfc::NciMessage &rx) { + this->ce_state_ = CardEmulationState::CARD_EMU_IDLE; + + switch (rx.get_simple_status_response()) { + case nfc::DEACTIVATION_TYPE_DISCOVERY: + this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY); + break; + + case nfc::DEACTIVATION_TYPE_IDLE: + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + break; + + case nfc::DEACTIVATION_TYPE_SLEEP: + case nfc::DEACTIVATION_TYPE_SLEEP_AF: + if (this->nci_state_ == NCIState::RFST_LISTEN_ACTIVE) { + this->nci_fsm_set_state_(NCIState::RFST_LISTEN_SLEEP); + } else if (this->nci_state_ == NCIState::RFST_POLL_ACTIVE) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + } else { + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + break; + + default: + break; + } +} + +void PN7150::process_data_message_(nfc::NciMessage &rx) { + ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + + std::vector ndef_response; + this->card_emu_t4t_get_response_(rx.get_message(), ndef_response); + + uint16_t ndef_response_size = ndef_response.size(); + if (!ndef_response_size) { + return; // no message returned, we cannot respond + } + + std::vector tx_msg = {nfc::NCI_PKT_MT_DATA, uint8_t((ndef_response_size & 0xFF00) >> 8), + uint8_t(ndef_response_size & 0x00FF)}; + tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end()); + nfc::NciMessage tx(tx_msg); + ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending reply for card emulation failed"); + } +} + +void PN7150::card_emu_t4t_get_response_(std::vector &response, std::vector &ndef_response) { + if (this->card_emulation_message_ == nullptr) { + ESP_LOGE(TAG, "No NDEF message is set; tag emulation not possible"); + ndef_response.clear(); + return; + } + + if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_APP_SELECT))) { + // CARD_EMU_T4T_APP_SELECT + ESP_LOGVV(TAG, "CARD_EMU_NDEF_APP_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_APP_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_CC_SELECT))) { + // CARD_EMU_T4T_CC_SELECT + if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_APP_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_CC_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_CC_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_NDEF_SELECT))) { + // CARD_EMU_T4T_NDEF_SELECT + ESP_LOGVV(TAG, "CARD_EMU_NDEF_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_READ), + std::begin(CARD_EMU_T4T_READ))) { + // CARD_EMU_T4T_READ + if (this->ce_state_ == CardEmulationState::CARD_EMU_CC_SELECTED) { + // CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED"); + uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + + if (length <= (sizeof(CARD_EMU_T4T_CC) + offset + 2)) { + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_CC) + offset, + std::begin(CARD_EMU_T4T_CC) + offset + length); + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } else if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) { + // CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED"); + auto ndef_message = this->card_emulation_message_->encode(); + uint16_t ndef_msg_size = ndef_message.size(); + uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + + ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str()); + + if (length <= (ndef_msg_size + offset + 2)) { + if (offset == 0) { + ndef_response.resize(2); + ndef_response[0] = (ndef_msg_size & 0xFF00) >> 8; + ndef_response[1] = (ndef_msg_size & 0x00FF); + if (length > 2) { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 2); + } + } else if (offset == 1) { + ndef_response.resize(1); + ndef_response[0] = (ndef_msg_size & 0x00FF); + if (length > 1) { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 1); + } + } else { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length); + } + + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + + if ((offset + length) >= (ndef_msg_size + 2)) { + ESP_LOGD(TAG, "NDEF message sent"); + this->on_emulated_tag_scan_callback_.call(); + } + } + } + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_WRITE), + std::begin(CARD_EMU_T4T_WRITE))) { + // CARD_EMU_T4T_WRITE + if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_T4T_WRITE"); + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + std::vector ndef_msg_written; + + ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length); + ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str()); + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } +} + +uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, + const bool expect_notification) { + uint8_t retries = NFCC_MAX_COMM_FAILS; + + while (retries) { + // first, send the message we need to send + if (this->write_nfcc(tx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending message"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str()); + // next, the NFCC should send back a response + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error receiving message"); + if (!retries--) { + ESP_LOGE(TAG, " ...giving up"); + return nfc::STATUS_FAILED; + } + } else { + break; + } + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + // validate the response based on the message type that was sent (command vs. data) + if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) { + // for commands, the GID and OID should match and the status should be OK + if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) { + ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + return rx.get_simple_status_response(); + } else { + // when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first + if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) || + (!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) { + ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + if (expect_notification) { + // if the NFCC said "OK", there will be additional data to read; this comes back in a notification message + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error receiving data from endpoint"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + + return nfc::STATUS_OK; + } +} + +uint8_t PN7150::wait_for_irq_(uint16_t timeout, bool pin_state) { + auto start_time = millis(); + + while (millis() - start_time < timeout) { + if (this->irq_pin_->digital_read() == pin_state) { + return nfc::STATUS_OK; + } + } + ESP_LOGW(TAG, "Timed out waiting for IRQ state"); + return nfc::STATUS_FAILED; +} + +} // namespace pn7150 +} // namespace esphome diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h new file mode 100644 index 000000000000..54038f5085d1 --- /dev/null +++ b/esphome/components/pn7150/pn7150.h @@ -0,0 +1,296 @@ +#pragma once + +#include "esphome/components/nfc/automation.h" +#include "esphome/components/nfc/nci_core.h" +#include "esphome/components/nfc/nci_message.h" +#include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/nfc_helpers.h" +#include "esphome/core/component.h" +#include "esphome/core/gpio.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace pn7150 { + +static const uint16_t NFCC_DEFAULT_TIMEOUT = 10; +static const uint16_t NFCC_INIT_TIMEOUT = 50; +static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15; + +static const uint8_t NFCC_MAX_COMM_FAILS = 3; +static const uint8_t NFCC_MAX_ERROR_COUNT = 10; + +static const uint8_t XCHG_DATA_OID = 0x10; +static const uint8_t MF_SECTORSEL_OID = 0x32; +static const uint8_t MFC_AUTHENTICATE_OID = 0x40; +static const uint8_t TEST_PRBS_OID = 0x30; +static const uint8_t TEST_ANTENNA_OID = 0x3D; +static const uint8_t TEST_GET_REGISTER_OID = 0x33; + +static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A +static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B +static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10; + +static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, + 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}; +static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04, + 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00}; +static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}; +static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}; +static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0}; +static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6}; +static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00}; +static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82}; + +static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields + 0x00, // config param identifier (TOTAL_DURATION) + 0x02, // length of value + 0x01, // TOTAL_DURATION (low)... + 0x00}; // TOTAL_DURATION (high): 1 ms + +static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields + 0x00, // config param identifier (TOTAL_DURATION) + 0x02, // length of value + 0xF8, // TOTAL_DURATION (low)... + 0x02}; // TOTAL_DURATION (high): 760 ms + +static const uint8_t PMU_CFG[] = { + 0x01, // Number of parameters + 0xA0, 0x0E, // ext. tag + 3, // length + 0x06, // VBAT1 connected to 5V (CFG2) + 0x64, // TVDD monitoring threshold = 5.0V; TxLDO voltage = 4.7V (in reader & card modes) + 0x01, // RFU; must be 0x00 for CFG1 and 0x01 for CFG2 +}; + +static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes + nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN, + nfc::INTF_ISODEP, // poll & listen mode + nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_TAGCMD}; // poll mode + +static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode + +static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode + +static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode + +static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming) + 1, // number of table entries + 0x01, // type = protocol-based + 3, // length + 0, // DH NFCEE ID, a static ID representing the DH-NFCEE + 0x01, // power state + nfc::PROT_ISODEP}; // protocol + +enum class CardEmulationState : uint8_t { + CARD_EMU_IDLE, + CARD_EMU_NDEF_APP_SELECTED, + CARD_EMU_CC_SELECTED, + CARD_EMU_NDEF_SELECTED, + CARD_EMU_DESFIRE_PROD, +}; + +enum class NCIState : uint8_t { + NONE = 0x00, + NFCC_RESET, + NFCC_INIT, + NFCC_CONFIG, + NFCC_SET_DISCOVER_MAP, + NFCC_SET_LISTEN_MODE_ROUTING, + RFST_IDLE, + RFST_DISCOVERY, + RFST_W4_ALL_DISCOVERIES, + RFST_W4_HOST_SELECT, + RFST_LISTEN_ACTIVE, + RFST_LISTEN_SLEEP, + RFST_POLL_ACTIVE, + EP_DEACTIVATING, + EP_SELECTING, + TEST = 0XFE, + FAILED = 0XFF, +}; + +enum class TestMode : uint8_t { + TEST_NONE = 0x00, + TEST_PRBS, + TEST_ANTENNA, + TEST_GET_REGISTER, +}; + +struct DiscoveredEndpoint { + uint8_t id; + uint8_t protocol; + uint32_t last_seen; + std::unique_ptr tag; + bool trig_called; +}; + +class PN7150 : public nfc::Nfcc, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void loop() override; + + void set_irq_pin(GPIOPin *irq_pin) { this->irq_pin_ = irq_pin; } + void set_ven_pin(GPIOPin *ven_pin) { this->ven_pin_ = ven_pin; } + + void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; } + void set_tag_emulation_message(std::shared_ptr message); + void set_tag_emulation_message(const optional &message, optional include_android_app_record); + void set_tag_emulation_message(const char *message, bool include_android_app_record = true); + void set_tag_emulation_off(); + void set_tag_emulation_on(); + bool tag_emulation_enabled() { return this->listening_enabled_; } + + void set_polling_off(); + void set_polling_on(); + bool polling_enabled() { return this->polling_enabled_; } + + void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } + void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } + + void add_on_emulated_tag_scan_callback(std::function callback) { + this->on_emulated_tag_scan_callback_.add(std::move(callback)); + } + + void add_on_finished_write_callback(std::function callback) { + this->on_finished_write_callback_.add(std::move(callback)); + } + + bool is_writing() { return this->next_task_ != EP_READ; }; + + void read_mode(); + void clean_mode(); + void format_mode(); + void write_mode(); + void set_tag_write_message(std::shared_ptr message); + void set_tag_write_message(optional message, optional include_android_app_record); + + uint8_t set_test_mode(TestMode test_mode, const std::vector &data, std::vector &result); + + protected: + uint8_t reset_core_(bool reset_config, bool power); + uint8_t init_core_(); + uint8_t send_init_config_(); + uint8_t send_core_config_(); + uint8_t refresh_core_config_(); + + uint8_t set_discover_map_(); + + uint8_t set_listen_mode_routing_(); + + uint8_t start_discovery_(); + uint8_t stop_discovery_(); + uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT); + + void select_endpoint_(); + + uint8_t read_endpoint_data_(nfc::NfcTag &tag); + uint8_t clean_endpoint_(std::vector &uid); + uint8_t format_endpoint_(std::vector &uid); + uint8_t write_endpoint_(std::vector &uid, std::shared_ptr &message); + + std::unique_ptr build_tag_(uint8_t mode_tech, const std::vector &data); + optional find_tag_uid_(const std::vector &uid); + void purge_old_tags_(); + void erase_tag_(uint8_t tag_index); + + /// advance controller state as required + void nci_fsm_transition_(); + /// set new controller state + void nci_fsm_set_state_(NCIState new_state); + /// setting controller to this state caused an error; returns true if too many errors/failures + bool nci_fsm_set_error_state_(NCIState new_state); + /// parse & process incoming messages from the NFCC + void process_message_(); + void process_rf_intf_activated_oid_(nfc::NciMessage &rx); + void process_rf_discover_oid_(nfc::NciMessage &rx); + void process_rf_deactivate_oid_(nfc::NciMessage &rx); + void process_data_message_(nfc::NciMessage &rx); + + void card_emu_t4t_get_response_(std::vector &response, std::vector &ndef_response); + + uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT, + bool expect_notification = true); + virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0; + virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0; + + uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true); + + uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag); + uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key); + uint8_t sect_to_auth_(uint8_t block_num); + uint8_t format_mifare_classic_mifare_(); + uint8_t format_mifare_classic_ndef_(); + uint8_t write_mifare_classic_tag_(const std::shared_ptr &message); + uint8_t halt_mifare_classic_tag_(); + + uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag); + uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data); + bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); + uint16_t read_mifare_ultralight_capacity_(); + uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index); + uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + uint8_t write_mifare_ultralight_tag_(std::vector &uid, const std::shared_ptr &message); + uint8_t clean_mifare_ultralight_(); + + enum NfcTask : uint8_t { + EP_READ = 0, + EP_CLEAN, + EP_FORMAT, + EP_WRITE, + } next_task_{EP_READ}; + + bool config_refresh_pending_{false}; + bool core_config_is_solo_{false}; + bool listening_enabled_{false}; + bool polling_enabled_{true}; + + uint8_t error_count_{0}; + uint8_t fail_count_{0}; + uint32_t last_nci_state_change_{0}; + uint8_t selecting_endpoint_{0}; + uint32_t tag_ttl_{250}; + + GPIOPin *irq_pin_{nullptr}; + GPIOPin *ven_pin_{nullptr}; + + CallbackManager on_emulated_tag_scan_callback_; + CallbackManager on_finished_write_callback_; + + std::vector discovered_endpoint_; + + CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE}; + NCIState nci_state_{NCIState::NFCC_RESET}; + NCIState nci_state_error_{NCIState::NONE}; + + std::shared_ptr card_emulation_message_; + std::shared_ptr next_task_message_to_write_; + + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; +}; + +} // namespace pn7150 +} // namespace esphome diff --git a/esphome/components/pn7150/pn7150_mifare_classic.cpp b/esphome/components/pn7150/pn7150_mifare_classic.cpp new file mode 100644 index 000000000000..0443929f6932 --- /dev/null +++ b/esphome/components/pn7150/pn7150_mifare_classic.cpp @@ -0,0 +1,322 @@ +#include + +#include "pn7150.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7150 { + +static const char *const TAG = "pn7150.mifare_classic"; + +uint8_t PN7150::read_mifare_classic_tag_(nfc::NfcTag &tag) { + uint8_t current_block = 4; + uint8_t message_start_index = 0; + uint32_t message_length = 0; + + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data"); + return nfc::STATUS_FAILED; + } + std::vector data; + + if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) { + if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { + return nfc::STATUS_FAILED; + } + } else { + ESP_LOGE(TAG, "Failed to read block %u", current_block); + return nfc::STATUS_FAILED; + } + + uint32_t index = 0; + uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length); + std::vector buffer; + + while (index < buffer_size) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Block authentication failed for %u", current_block); + return nfc::STATUS_FAILED; + } + } + std::vector block_data; + if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading block %u", current_block); + return nfc::STATUS_FAILED; + } else { + buffer.insert(buffer.end(), block_data.begin(), block_data.end()); + } + + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + current_block++; + } + } + + if (buffer.begin() + message_start_index < buffer.end()) { + buffer.erase(buffer.begin(), buffer.begin() + message_start_index); + } else { + return nfc::STATUS_FAILED; + } + + tag.set_ndef_message(make_unique(buffer)); + + return nfc::STATUS_OK; +} + +uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num}); + + ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Timeout reading tag data"); + return nfc::STATUS_FAILED; + } + + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) || + (!rx.message_length_is(18))) { + ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num); + ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1); + + ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str()); + return nfc::STATUS_OK; +} + +uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num}); + + switch (key_num) { + case nfc::MIFARE_CMD_AUTH_A: + tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A; + break; + + case nfc::MIFARE_CMD_AUTH_B: + tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B; + break; + + default: + break; + } + + if (key != nullptr) { + tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY; + tx.get_message().insert(tx.get_message().end(), key, key + 6); + } + + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed"); + return nfc::STATUS_FAILED; + } + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) || + (rx.get_message()[4] != nfc::STATUS_OK)) { + ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num); + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num); + return nfc::STATUS_OK; +} + +uint8_t PN7150::sect_to_auth_(const uint8_t block_num) { + const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START; + if (block_num >= first_high_block) { + return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) + + nfc::MIFARE_CLASSIC_16BLOCK_SECT_START; + } + return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW; +} + +uint8_t PN7150::format_mifare_classic_mifare_() { + std::vector blank_buffer( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector trailer_buffer( + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + auto status = nfc::STATUS_OK; + + for (int block = 0; block < 64; block += 4) { + if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + continue; + } + if (block != 0) { + if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } + if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 1); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 2); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 3); + status = nfc::STATUS_FAILED; + } + } + + return status; +} + +uint8_t PN7150::format_mifare_classic_ndef_() { + std::vector empty_ndef_message( + {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector blank_block( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector block_1_data( + {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_2_data( + {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_3_trailer( + {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + std::vector ndef_trailer( + {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting"); + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + + ESP_LOGD(TAG, "Sector 0 formatted with NDEF"); + + auto status = nfc::STATUS_OK; + + for (int block = 4; block < 64; block += 4) { + if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (block == 4) { + if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } else { + if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } + if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 1); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 2); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3); + status = nfc::STATUS_FAILED; + } + } + return status; +} + +uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); + + ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed"); + return nfc::STATUS_FAILED; + } + // write command part two + tx.set_payload({XCHG_DATA_OID}); + tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end()); + + ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write"); + return nfc::STATUS_FAILED; + } + + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) || + (rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) { + ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num); + ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + return nfc::STATUS_OK; +} + +uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr &message) { + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length); + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 3, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_block = 4; + + while (index < buffer_length) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + } + + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); + if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + // Skipping as cannot write to trailer + current_block++; + } + } + return nfc::STATUS_OK; +} + +uint8_t PN7150::halt_mifare_classic_tag_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0}); + + ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +} // namespace pn7150 +} // namespace esphome diff --git a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp new file mode 100644 index 000000000000..791b0634d628 --- /dev/null +++ b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp @@ -0,0 +1,186 @@ +#include +#include + +#include "pn7150.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7150 { + +static const char *const TAG = "pn7150.mifare_ultralight"; + +uint8_t PN7150::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { + std::vector data; + // pages 3 to 6 contain various info we are interested in -- do one read to grab it all + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE, + data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + + if (!this->is_mifare_ultralight_formatted_(data)) { + ESP_LOGW(TAG, "Not NDEF formatted"); + return nfc::STATUS_FAILED; + } + + uint8_t message_length; + uint8_t message_start_index; + if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Couldn't find NDEF message"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index); + + if (message_length == 0) { + return nfc::STATUS_FAILED; + } + // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages + const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0; + if (read_length) { + if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) != + nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading tag data"); + return nfc::STATUS_FAILED; + } + } + // we need to trim off page 3 as well as any bytes ahead of message_start_index + data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + + tag.set_ndef_message(make_unique(data)); + + return nfc::STATUS_OK; +} + +uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data) { + const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page}); + + for (size_t i = 0; i * read_increment < num_bytes; i++) { + tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page; + do { // loop because sometimes we struggle here...???... + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading tag data"); + return nfc::STATUS_FAILED; + } + } while (rx.get_payload_size() < read_increment); + uint16_t bytes_offset = (i + 1) * read_increment; + auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1 + : rx.get_message().end() - (bytes_offset - num_bytes + 1); + + if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) { + data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr); + } + } + + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); + + return nfc::STATUS_OK; +} + +bool PN7150::is_mifare_ultralight_formatted_(const std::vector &page_3_to_6) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + return (page_3_to_6.size() > p4_offset + 3) && + !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && + (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); +} + +uint16_t PN7150::read_mifare_ultralight_capacity_() { + std::vector data; + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) { + ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U); + return data[2] * 8U; + } + return 0; +} + +uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + if (!(page_3_to_6.size() > p4_offset + 5)) { + return nfc::STATUS_FAILED; + } + + if (page_3_to_6[p4_offset + 0] == 0x03) { + message_length = page_3_to_6[p4_offset + 1]; + message_start_index = 2; + return nfc::STATUS_OK; + } else if (page_3_to_6[p4_offset + 5] == 0x03) { + message_length = page_3_to_6[p4_offset + 6]; + message_start_index = 7; + return nfc::STATUS_OK; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7150::write_mifare_ultralight_tag_(std::vector &uid, + const std::shared_ptr &message) { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); + + if (buffer_length > capacity) { + ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity); + return nfc::STATUS_FAILED; + } + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 2, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + while (index < buffer_length) { + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + current_page++; + } + return nfc::STATUS_OK; +} + +uint8_t PN7150::clean_mifare_ultralight_() { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + + for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { + if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + } + return nfc::STATUS_OK; +} + +uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { + std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; + payload.insert(payload.end(), write_data.begin(), write_data.end()); + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload); + + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error writing page %u", page_num); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +} // namespace pn7150 +} // namespace esphome diff --git a/esphome/components/pn7150_i2c/__init__.py b/esphome/components/pn7150_i2c/__init__.py new file mode 100644 index 000000000000..5f48a0f3cb1d --- /dev/null +++ b/esphome/components/pn7150_i2c/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, pn7150 +from esphome.const import CONF_ID + +AUTO_LOAD = ["pn7150"] +CODEOWNERS = ["@kbx81", "@jesserockz"] +DEPENDENCIES = ["i2c"] + +pn7150_i2c_ns = cg.esphome_ns.namespace("pn7150_i2c") +PN7150I2C = pn7150_i2c_ns.class_("PN7150I2C", pn7150.PN7150, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All( + pn7150.PN7150_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN7150I2C), + } + ).extend(i2c.i2c_device_schema(0x28)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await pn7150.setup_pn7150(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/pn7150_i2c/pn7150_i2c.cpp b/esphome/components/pn7150_i2c/pn7150_i2c.cpp new file mode 100644 index 000000000000..38b3102b3740 --- /dev/null +++ b/esphome/components/pn7150_i2c/pn7150_i2c.cpp @@ -0,0 +1,49 @@ +#include "pn7150_i2c.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace pn7150_i2c { + +static const char *const TAG = "pn7150_i2c"; + +uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { + if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ"); + return nfc::STATUS_FAILED; + } + + rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE); + if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) { + return nfc::STATUS_FAILED; + } + + uint8_t length = rx.get_payload_size(); + if (length > 0) { + rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE); + if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) { + return nfc::STATUS_FAILED; + } + } + // semaphore to ensure transaction is complete before returning + if (this->wait_for_irq_(pn7150::NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() post-read timeout waiting for IRQ line to clear"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7150I2C::write_nfcc(nfc::NciMessage &tx) { + if (this->write(tx.encode().data(), tx.encode().size()) == i2c::ERROR_OK) { + return nfc::STATUS_OK; + } + return nfc::STATUS_FAILED; +} + +void PN7150I2C::dump_config() { + PN7150::dump_config(); + LOG_I2C_DEVICE(this); +} + +} // namespace pn7150_i2c +} // namespace esphome diff --git a/esphome/components/pn7150_i2c/pn7150_i2c.h b/esphome/components/pn7150_i2c/pn7150_i2c.h new file mode 100644 index 000000000000..9308dddd26b2 --- /dev/null +++ b/esphome/components/pn7150_i2c/pn7150_i2c.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/pn7150/pn7150.h" +#include "esphome/components/i2c/i2c.h" + +#include + +namespace esphome { +namespace pn7150_i2c { + +class PN7150I2C : public pn7150::PN7150, public i2c::I2CDevice { + public: + void dump_config() override; + + protected: + uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override; + uint8_t write_nfcc(nfc::NciMessage &tx) override; +}; + +} // namespace pn7150_i2c +} // namespace esphome diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py new file mode 100644 index 000000000000..b102b38f980e --- /dev/null +++ b/esphome/components/pn7160/__init__.py @@ -0,0 +1,227 @@ +from esphome import automation, pins +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import nfc +from esphome.const import ( + CONF_ID, + CONF_IRQ_PIN, + CONF_MESSAGE, + CONF_ON_FINISHED_WRITE, + CONF_ON_TAG_REMOVED, + CONF_ON_TAG, + CONF_TRIGGER_ID, +) + +AUTO_LOAD = ["binary_sensor", "nfc"] +CODEOWNERS = ["@kbx81", "@jesserockz"] + +CONF_DWL_REQ_PIN = "dwl_req_pin" +CONF_EMULATION_MESSAGE = "emulation_message" +CONF_EMULATION_OFF = "emulation_off" +CONF_EMULATION_ON = "emulation_on" +CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record" +CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan" +CONF_PN7160_ID = "pn7160_id" +CONF_POLLING_OFF = "polling_off" +CONF_POLLING_ON = "polling_on" +CONF_SET_CLEAN_MODE = "set_clean_mode" +CONF_SET_EMULATION_MESSAGE = "set_emulation_message" +CONF_SET_FORMAT_MODE = "set_format_mode" +CONF_SET_READ_MODE = "set_read_mode" +CONF_SET_WRITE_MESSAGE = "set_write_message" +CONF_SET_WRITE_MODE = "set_write_mode" +CONF_TAG_TTL = "tag_ttl" +CONF_VEN_PIN = "ven_pin" +CONF_WKUP_REQ_PIN = "wkup_req_pin" + +pn7160_ns = cg.esphome_ns.namespace("pn7160") +PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component) + +EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action) +EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action) +PollingOffAction = pn7160_ns.class_("PollingOffAction", automation.Action) +PollingOnAction = pn7160_ns.class_("PollingOnAction", automation.Action) +SetCleanModeAction = pn7160_ns.class_("SetCleanModeAction", automation.Action) +SetEmulationMessageAction = pn7160_ns.class_( + "SetEmulationMessageAction", automation.Action +) +SetFormatModeAction = pn7160_ns.class_("SetFormatModeAction", automation.Action) +SetReadModeAction = pn7160_ns.class_("SetReadModeAction", automation.Action) +SetWriteMessageAction = pn7160_ns.class_("SetWriteMessageAction", automation.Action) +SetWriteModeAction = pn7160_ns.class_("SetWriteModeAction", automation.Action) + + +PN7160OnEmulatedTagScanTrigger = pn7160_ns.class_( + "PN7160OnEmulatedTagScanTrigger", automation.Trigger.template() +) + +PN7160OnFinishedWriteTrigger = pn7160_ns.class_( + "PN7160OnFinishedWriteTrigger", automation.Trigger.template() +) + +PN7160IsWritingCondition = pn7160_ns.class_( + "PN7160IsWritingCondition", automation.Condition +) + + +IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition) + + +SIMPLE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PN7160), + } +) + +SET_MESSAGE_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(PN7160), + cv.Required(CONF_MESSAGE): cv.templatable(cv.string), + cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean, + } +) + +PN7160_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(PN7160), + cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + PN7160OnEmulatedTagScanTrigger + ), + } + ), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + PN7160OnFinishedWriteTrigger + ), + } + ), + cv.Optional(CONF_ON_TAG): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), + } + ), + cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), + } + ), + cv.Optional(CONF_DWL_REQ_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_WKUP_REQ_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_EMULATION_MESSAGE): cv.string, + cv.Optional(CONF_TAG_TTL): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +@automation.register_action( + "tag.set_emulation_message", + SetEmulationMessageAction, + SET_MESSAGE_ACTION_SCHEMA, +) +@automation.register_action( + "tag.set_write_message", + SetWriteMessageAction, + SET_MESSAGE_ACTION_SCHEMA, +) +async def pn7160_set_message_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_MESSAGE], args, cg.std_string) + cg.add(var.set_message(template_)) + template_ = await cg.templatable( + config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_ + ) + cg.add(var.set_include_android_app_record(template_)) + return var + + +@automation.register_action( + "tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA) +@automation.register_action( + "tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA +) +@automation.register_action( + "tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA +) +async def pn7160_simple_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 + + +async def setup_pn7160(var, config): + await cg.register_component(var, config) + + if dwl_req_pin_config := config.get(CONF_DWL_REQ_PIN): + pin = await cg.gpio_pin_expression(dwl_req_pin_config) + cg.add(var.set_dwl_req_pin(pin)) + + pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) + cg.add(var.set_irq_pin(pin)) + + pin = await cg.gpio_pin_expression(config[CONF_VEN_PIN]) + cg.add(var.set_ven_pin(pin)) + + if wakeup_req_pin_config := config.get(CONF_WKUP_REQ_PIN): + pin = await cg.gpio_pin_expression(wakeup_req_pin_config) + cg.add(var.set_wkup_req_pin(pin)) + + if emulation_message_config := config.get(CONF_EMULATION_MESSAGE): + cg.add(var.set_tag_emulation_message(emulation_message_config)) + cg.add(var.set_tag_emulation_on()) + + if CONF_TAG_TTL in config: + cg.add(var.set_tag_ttl(config[CONF_TAG_TTL])) + + for conf in config.get(CONF_ON_TAG, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontag_trigger(trigger)) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf + ) + + for conf in config.get(CONF_ON_TAG_REMOVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontagremoved_trigger(trigger)) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf + ) + + for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_FINISHED_WRITE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + +@automation.register_condition( + "pn7160.is_writing", + PN7160IsWritingCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(PN7160), + } + ), +) +async def pn7160_is_writing_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/pn7160/automation.h b/esphome/components/pn7160/automation.h new file mode 100644 index 000000000000..854fb1168482 --- /dev/null +++ b/esphome/components/pn7160/automation.h @@ -0,0 +1,82 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/pn7160/pn7160.h" + +namespace esphome { +namespace pn7160 { + +class PN7160OnEmulatedTagScanTrigger : public Trigger<> { + public: + explicit PN7160OnEmulatedTagScanTrigger(PN7160 *parent) { + parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); }); + } +}; + +class PN7160OnFinishedWriteTrigger : public Trigger<> { + public: + explicit PN7160OnFinishedWriteTrigger(PN7160 *parent) { + parent->add_on_finished_write_callback([this]() { this->trigger(); }); + } +}; + +template class PN7160IsWritingCondition : public Condition, public Parented { + public: + bool check(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(); } +}; + +template class EmulationOnAction : public Action, public Parented { + void play(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(); } +}; + +template class PollingOnAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->set_polling_on(); } +}; + +template class SetCleanModeAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->clean_mode(); } +}; + +template class SetFormatModeAction : public Action, public Parented { + void play(Ts... x) override { this->parent_->format_mode(); } +}; + +template class SetReadModeAction : public Action, public Parented { + void play(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 { + this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), + this->include_android_app_record_.optional_value(x...)); + } +}; + +template class SetWriteMessageAction : public Action, public Parented { + TEMPLATABLE_VALUE(std::string, message) + TEMPLATABLE_VALUE(bool, include_android_app_record) + + void play(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(); } +}; + +} // namespace pn7160 +} // namespace esphome diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp new file mode 100644 index 000000000000..a7d3b38fb77e --- /dev/null +++ b/esphome/components/pn7160/pn7160.cpp @@ -0,0 +1,1167 @@ +#include + +#include "automation.h" +#include "pn7160.h" + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7160 { + +static const char *const TAG = "pn7160"; + +void PN7160::setup() { + this->irq_pin_->setup(); + this->ven_pin_->setup(); + if (this->dwl_req_pin_ != nullptr) { + this->dwl_req_pin_->setup(); + } + if (this->wkup_req_pin_ != nullptr) { + this->wkup_req_pin_->setup(); + } + + this->nci_fsm_transition_(); // kick off reset & init processes +} + +void PN7160::dump_config() { + ESP_LOGCONFIG(TAG, "PN7160:"); + if (this->dwl_req_pin_ != nullptr) { + LOG_PIN(" DWL_REQ pin: ", this->dwl_req_pin_); + } + LOG_PIN(" IRQ pin: ", this->irq_pin_); + LOG_PIN(" VEN pin: ", this->ven_pin_); + if (this->wkup_req_pin_ != nullptr) { + LOG_PIN(" WKUP_REQ pin: ", this->wkup_req_pin_); + } +} + +void PN7160::loop() { + this->nci_fsm_transition_(); + this->purge_old_tags_(); +} + +void PN7160::set_tag_emulation_message(std::shared_ptr message) { + this->card_emulation_message_ = std::move(message); + ESP_LOGD(TAG, "Tag emulation message set"); +} + +void PN7160::set_tag_emulation_message(const optional &message, + const optional include_android_app_record) { + if (!message.has_value()) { + return; + } + + auto ndef_message = make_unique(); + + ndef_message->add_uri_record(message.value()); + + if (!include_android_app_record.has_value() || include_android_app_record.value()) { + auto ext_record = make_unique(); + ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE); + ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE); + ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD); + ndef_message->add_record(std::move(ext_record)); + } + + this->card_emulation_message_ = std::move(ndef_message); + ESP_LOGD(TAG, "Tag emulation message set"); +} + +void PN7160::set_tag_emulation_message(const char *message, const bool include_android_app_record) { + this->set_tag_emulation_message(std::string(message), include_android_app_record); +} + +void PN7160::set_tag_emulation_off() { + if (this->listening_enabled_) { + this->listening_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag emulation disabled"); +} + +void PN7160::set_tag_emulation_on() { + if (this->card_emulation_message_ == nullptr) { + ESP_LOGE(TAG, "No NDEF message is set; tag emulation cannot be enabled"); + return; + } + if (!this->listening_enabled_) { + this->listening_enabled_ = true; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag emulation enabled"); +} + +void PN7160::set_polling_off() { + if (this->polling_enabled_) { + this->polling_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling disabled"); +} + +void PN7160::set_polling_on() { + if (!this->polling_enabled_) { + this->polling_enabled_ = true; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling enabled"); +} + +void PN7160::read_mode() { + this->next_task_ = EP_READ; + ESP_LOGD(TAG, "Waiting to read next tag"); +} + +void PN7160::clean_mode() { + this->next_task_ = EP_CLEAN; + ESP_LOGD(TAG, "Waiting to clean next tag"); +} + +void PN7160::format_mode() { + this->next_task_ = EP_FORMAT; + ESP_LOGD(TAG, "Waiting to format next tag"); +} + +void PN7160::write_mode() { + if (this->next_task_message_to_write_ == nullptr) { + ESP_LOGW(TAG, "Message to write must be set before setting write mode"); + return; + } + + this->next_task_ = EP_WRITE; + ESP_LOGD(TAG, "Waiting to write next tag"); +} + +void PN7160::set_tag_write_message(std::shared_ptr message) { + this->next_task_message_to_write_ = std::move(message); + ESP_LOGD(TAG, "Message to write has been set"); +} + +void PN7160::set_tag_write_message(optional message, optional include_android_app_record) { + if (!message.has_value()) { + return; + } + + auto ndef_message = make_unique(); + + ndef_message->add_uri_record(message.value()); + + if (!include_android_app_record.has_value() || include_android_app_record.value()) { + auto ext_record = make_unique(); + ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE); + ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE); + ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD); + ndef_message->add_record(std::move(ext_record)); + } + + this->next_task_message_to_write_ = std::move(ndef_message); + ESP_LOGD(TAG, "Message to write has been set"); +} + +uint8_t PN7160::set_test_mode(const TestMode test_mode, const std::vector &data, + std::vector &result) { + auto test_oid = TEST_PRBS_OID; + + switch (test_mode) { + case TestMode::TEST_PRBS: + // test_oid = TEST_PRBS_OID; + break; + + case TestMode::TEST_ANTENNA: + test_oid = TEST_ANTENNA_OID; + break; + + case TestMode::TEST_GET_REGISTER: + test_oid = TEST_GET_REGISTER_OID; + break; + + case TestMode::TEST_NONE: + default: + ESP_LOGD(TAG, "Exiting test mode"); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + return nfc::STATUS_OK; + } + + if (this->reset_core_(true, true) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to reset NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_RESET); + result.clear(); + return nfc::STATUS_FAILED; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_INIT); + } + if (this->init_core_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to initialise NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_INIT); + result.clear(); + return nfc::STATUS_FAILED; + } else { + this->nci_fsm_set_state_(NCIState::TEST); + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, test_oid, data); + + ESP_LOGW(TAG, "Starting test mode, OID 0x%02X", test_oid); + auto status = this->transceive_(tx, rx, NFCC_INIT_TIMEOUT); + + if (status != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to start test mode, OID 0x%02X", test_oid); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + result.clear(); + } else { + result = rx.get_message(); + result.erase(result.begin(), result.begin() + 4); // remove NCI header + if (!result.empty()) { + ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes(result).c_str()); + } + } + return status; +} + +uint8_t PN7160::reset_core_(const bool reset_config, const bool power) { + if (this->dwl_req_pin_ != nullptr) { + this->dwl_req_pin_->digital_write(false); + delay(NFCC_DEFAULT_TIMEOUT); + } + + if (power) { + this->ven_pin_->digital_write(true); + delay(NFCC_DEFAULT_TIMEOUT); + this->ven_pin_->digital_write(false); + delay(NFCC_DEFAULT_TIMEOUT); + this->ven_pin_->digital_write(true); + delay(NFCC_INIT_TIMEOUT); + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_RESET_OID, + {(uint8_t) reset_config}); + + if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending reset command"); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return rx.get_simple_status_response(); + } + // read reset notification + if (this->read_nfcc(rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Reset notification was not received"); + return nfc::STATUS_FAILED; + } + // verify reset notification + if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.message_length_is(9)) || + (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET] != 0x02) || + (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != (uint8_t) reset_config)) { + ESP_LOGE(TAG, "Reset notification was malformed: %s", nfc::format_bytes(rx.get_message()).c_str()); + 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]); + 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()); + + return nfc::STATUS_OK; +} + +uint8_t PN7160::init_core_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_INIT_OID); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending initialise command"); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + uint8_t hw_version = rx.get_message()[17 + rx.get_message()[8]]; + uint8_t rom_code_version = rx.get_message()[18 + rx.get_message()[8]]; + uint8_t flash_major_version = rx.get_message()[19 + rx.get_message()[8]]; + 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()); + + return rx.get_simple_status_response(); +} + +uint8_t PN7160::send_init_config_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, nfc::NCI_CORE_SET_CONFIG_OID); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error enabling proprietary extensions"); + return nfc::STATUS_FAILED; + } + + tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, + std::vector(std::begin(PMU_CFG), std::end(PMU_CFG))); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending PMU config"); + return nfc::STATUS_FAILED; + } + + return this->send_core_config_(); +} + +uint8_t PN7160::send_core_config_() { + const auto *core_config_begin = std::begin(CORE_CONFIG_SOLO); + const auto *core_config_end = std::end(CORE_CONFIG_SOLO); + this->core_config_is_solo_ = true; + + if (this->listening_enabled_ && this->polling_enabled_) { + core_config_begin = std::begin(CORE_CONFIG_RW_CE); + core_config_end = std::end(CORE_CONFIG_RW_CE); + this->core_config_is_solo_ = false; + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, + std::vector(core_config_begin, core_config_end)); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error sending core config"); + return nfc::STATUS_FAILED; + } + + return nfc::STATUS_OK; +} + +uint8_t PN7160::refresh_core_config_() { + bool core_config_should_be_solo = !(this->listening_enabled_ && this->polling_enabled_); + + if (this->nci_state_ == NCIState::RFST_DISCOVERY) { + if (this->stop_discovery_() != nfc::STATUS_OK) { + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + return nfc::STATUS_FAILED; + } + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + + if (this->core_config_is_solo_ != core_config_should_be_solo) { + if (this->send_core_config_() != nfc::STATUS_OK) { + ESP_LOGV(TAG, "Failed to refresh core config"); + return nfc::STATUS_FAILED; + } + } + this->config_refresh_pending_ = false; + return nfc::STATUS_OK; +} + +uint8_t PN7160::set_discover_map_() { + std::vector discover_map = {sizeof(RF_DISCOVER_MAP_CONFIG) / 3}; + discover_map.insert(discover_map.end(), std::begin(RF_DISCOVER_MAP_CONFIG), std::end(RF_DISCOVER_MAP_CONFIG)); + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_MAP_OID, discover_map); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending discover map poll config"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7160::set_listen_mode_routing_() { + nfc::NciMessage rx; + nfc::NciMessage tx( + nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_SET_LISTEN_MODE_ROUTING_OID, + std::vector(std::begin(RF_LISTEN_MODE_ROUTING_CONFIG), std::end(RF_LISTEN_MODE_ROUTING_CONFIG))); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error setting listen mode routing config"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7160::start_discovery_() { + const uint8_t *rf_discovery_config = RF_DISCOVERY_CONFIG; + uint8_t length = sizeof(RF_DISCOVERY_CONFIG); + + if (!this->listening_enabled_) { + length = sizeof(RF_DISCOVERY_POLL_CONFIG); + rf_discovery_config = RF_DISCOVERY_POLL_CONFIG; + } else if (!this->polling_enabled_) { + length = sizeof(RF_DISCOVERY_LISTEN_CONFIG); + rf_discovery_config = RF_DISCOVERY_LISTEN_CONFIG; + } + + std::vector discover_config = std::vector((length * 2) + 1); + + discover_config[0] = length; + for (uint8_t i = 0; i < length; i++) { + discover_config[(i * 2) + 1] = rf_discovery_config[i]; + discover_config[(i * 2) + 2] = 0x01; // RF Technology and Mode will be executed in every discovery period + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, discover_config); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + switch (rx.get_simple_status_response()) { + // in any of these cases, we are either already in or will remain in discovery, which satisfies the function call + case nfc::STATUS_OK: + case nfc::DISCOVERY_ALREADY_STARTED: + case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED: + case nfc::DISCOVERY_TEAR_DOWN: + return nfc::STATUS_OK; + + default: + ESP_LOGE(TAG, "Error starting discovery"); + return nfc::STATUS_FAILED; + } + } + + return nfc::STATUS_OK; +} + +uint8_t PN7160::stop_discovery_() { return this->deactivate_(nfc::DEACTIVATION_TYPE_IDLE, NFCC_TAG_WRITE_TIMEOUT); } + +uint8_t PN7160::deactivate_(const uint8_t type, const uint16_t timeout) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {type}); + + auto status = this->transceive_(tx, rx, timeout); + // if (status != nfc::STATUS_OK) { + // ESP_LOGE(TAG, "Error sending deactivate type %u", type); + // return nfc::STATUS_FAILED; + // } + return status; +} + +void PN7160::select_endpoint_() { + if (this->discovered_endpoint_.empty()) { + ESP_LOGW(TAG, "No cached tags to select"); + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + return; + } + std::vector endpoint_data = {this->discovered_endpoint_[0].id, this->discovered_endpoint_[0].protocol, + 0x01}; // that last byte is the interface ID + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + if (!this->discovered_endpoint_[i].trig_called) { + endpoint_data = {this->discovered_endpoint_[i].id, this->discovered_endpoint_[i].protocol, + 0x01}; // that last byte is the interface ID + this->selecting_endpoint_ = i; + break; + } + } + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_SELECT_OID, endpoint_data); + + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error selecting endpoint"); + } else { + this->nci_fsm_set_state_(NCIState::EP_SELECTING); + } +} + +uint8_t PN7160::read_endpoint_data_(nfc::NfcTag &tag) { + uint8_t type = nfc::guess_tag_type(tag.get_uid().size()); + + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + ESP_LOGV(TAG, "Reading Mifare classic"); + return this->read_mifare_classic_tag_(tag); + + case nfc::TAG_TYPE_2: + ESP_LOGV(TAG, "Reading Mifare ultralight"); + return this->read_mifare_ultralight_tag_(tag); + + case nfc::TAG_TYPE_UNKNOWN: + default: + ESP_LOGV(TAG, "Cannot determine tag type"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7160::clean_endpoint_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->format_mifare_classic_mifare_(); + + case nfc::TAG_TYPE_2: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for cleaning"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7160::format_endpoint_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->format_mifare_classic_ndef_(); + + case nfc::TAG_TYPE_2: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for formatting"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7160::write_endpoint_(std::vector &uid, std::shared_ptr &message) { + uint8_t type = nfc::guess_tag_type(uid.size()); + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + return this->write_mifare_classic_tag_(message); + + case nfc::TAG_TYPE_2: + return this->write_mifare_ultralight_tag_(uid, message); + + default: + ESP_LOGE(TAG, "Unsupported tag for writing"); + break; + } + return nfc::STATUS_FAILED; +} + +std::unique_ptr PN7160::build_tag_(const uint8_t mode_tech, const std::vector &data) { + switch (mode_tech) { + case (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA): { + uint8_t uid_length = data[2]; + if (!uid_length) { + ESP_LOGE(TAG, "UID length cannot be zero"); + return nullptr; + } + std::vector uid(data.begin() + 3, data.begin() + 3 + uid_length); + const auto *tag_type_str = + nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2; + return make_unique(uid, tag_type_str); + } + } + return nullptr; +} + +optional PN7160::find_tag_uid_(const std::vector &uid) { + if (!this->discovered_endpoint_.empty()) { + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid(); + bool uid_match = (uid.size() == existing_tag_uid.size()); + + if (uid_match) { + for (size_t i = 0; i < uid.size(); i++) { + uid_match &= (uid[i] == existing_tag_uid[i]); + } + if (uid_match) { + return i; + } + } + } + } + return nullopt; +} + +void PN7160::purge_old_tags_() { + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + if (millis() - this->discovered_endpoint_[i].last_seen > this->tag_ttl_) { + this->erase_tag_(i); + } + } +} + +void PN7160::erase_tag_(const uint8_t tag_index) { + if (tag_index < this->discovered_endpoint_.size()) { + for (auto *trigger : this->triggers_ontagremoved_) { + trigger->process(this->discovered_endpoint_[tag_index].tag); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } + ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); + this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); + } +} + +void PN7160::nci_fsm_transition_() { + switch (this->nci_state_) { + case NCIState::NFCC_RESET: + if (this->reset_core_(true, true) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to reset NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_RESET); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_INIT); + } + // fall through + + case NCIState::NFCC_INIT: + if (this->init_core_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to initialise NCI core"); + this->nci_fsm_set_error_state_(NCIState::NFCC_INIT); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); + } + // fall through + + case NCIState::NFCC_CONFIG: + if (this->send_init_config_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to send initial config"); + this->nci_fsm_set_error_state_(NCIState::NFCC_CONFIG); + return; + } else { + this->config_refresh_pending_ = false; + this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); + } + // fall through + + case NCIState::NFCC_SET_DISCOVER_MAP: + if (this->set_discover_map_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to set discover map"); + this->nci_fsm_set_error_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); + } + // fall through + + case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: + if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Failed to set listen mode routing"); + this->nci_fsm_set_error_state_(NCIState::RFST_IDLE); + return; + } else { + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + // fall through + + case NCIState::RFST_IDLE: + if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { + this->stop_discovery_(); + } + + if (this->config_refresh_pending_) { + this->refresh_core_config_(); + } + + if (!this->listening_enabled_ && !this->polling_enabled_) { + return; + } + + if (this->start_discovery_() != nfc::STATUS_OK) { + ESP_LOGV(TAG, "Failed to start discovery"); + this->nci_fsm_set_error_state_(NCIState::RFST_DISCOVERY); + } else { + this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY); + } + return; + + case NCIState::RFST_W4_HOST_SELECT: + select_endpoint_(); + // fall through + + // All cases below are waiting for NOTIFICATION messages + case NCIState::RFST_DISCOVERY: + if (this->config_refresh_pending_) { + this->refresh_core_config_(); + } + // fall through + + case NCIState::RFST_LISTEN_ACTIVE: + case NCIState::RFST_LISTEN_SLEEP: + case NCIState::RFST_POLL_ACTIVE: + case NCIState::EP_SELECTING: + case NCIState::EP_DEACTIVATING: + if (this->irq_pin_->digital_read()) { + this->process_message_(); + } + break; + + case NCIState::FAILED: + case NCIState::NONE: + default: + return; + } +} + +void PN7160::nci_fsm_set_state_(NCIState new_state) { + ESP_LOGVV(TAG, "nci_fsm_set_state_(%u)", (uint8_t) new_state); + this->nci_state_ = new_state; + this->nci_state_error_ = NCIState::NONE; + this->error_count_ = 0; + this->last_nci_state_change_ = millis(); +} + +bool PN7160::nci_fsm_set_error_state_(NCIState new_state) { + ESP_LOGVV(TAG, "nci_fsm_set_error_state_(%u); error_count_ = %u", (uint8_t) new_state, this->error_count_); + this->nci_state_error_ = new_state; + if (this->error_count_++ > NFCC_MAX_ERROR_COUNT) { + if ((this->nci_state_error_ == NCIState::NFCC_RESET) || (this->nci_state_error_ == NCIState::NFCC_INIT) || + (this->nci_state_error_ == NCIState::NFCC_CONFIG)) { + ESP_LOGE(TAG, "Too many initialization failures -- check device connections"); + this->mark_failed(); + this->nci_fsm_set_state_(NCIState::FAILED); + } else { + ESP_LOGW(TAG, "Too many errors transitioning to state %u; resetting NFCC", (uint8_t) this->nci_state_error_); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + } + } + return this->error_count_ > NFCC_MAX_ERROR_COUNT; +} + +void PN7160::process_message_() { + nfc::NciMessage rx; + if (this->read_nfcc(rx, NFCC_DEFAULT_TIMEOUT) != nfc::STATUS_OK) { + return; // No data + } + + switch (rx.get_message_type()) { + case nfc::NCI_PKT_MT_CTRL_NOTIFICATION: + if (rx.get_gid() == nfc::RF_GID) { + switch (rx.get_oid()) { + case nfc::RF_INTF_ACTIVATED_OID: + ESP_LOGVV(TAG, "RF_INTF_ACTIVATED_OID"); + this->process_rf_intf_activated_oid_(rx); + return; + + case nfc::RF_DISCOVER_OID: + ESP_LOGVV(TAG, "RF_DISCOVER_OID"); + this->process_rf_discover_oid_(rx); + return; + + case nfc::RF_DEACTIVATE_OID: + ESP_LOGVV(TAG, "RF_DEACTIVATE_OID: type: 0x%02X, reason: 0x%02X", rx.get_message()[3], rx.get_message()[4]); + this->process_rf_deactivate_oid_(rx); + return; + + default: + ESP_LOGV(TAG, "Unimplemented RF OID received: 0x%02X", rx.get_oid()); + } + } else if (rx.get_gid() == nfc::NCI_CORE_GID) { + switch (rx.get_oid()) { + case nfc::NCI_CORE_GENERIC_ERROR_OID: + ESP_LOGV(TAG, "NCI_CORE_GENERIC_ERROR_OID:"); + switch (rx.get_simple_status_response()) { + case nfc::DISCOVERY_ALREADY_STARTED: + ESP_LOGV(TAG, " DISCOVERY_ALREADY_STARTED"); + break; + + case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED: + // Tag removed too soon + ESP_LOGV(TAG, " DISCOVERY_TARGET_ACTIVATION_FAILED"); + if (this->nci_state_ == NCIState::EP_SELECTING) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + if (!this->discovered_endpoint_.empty()) { + this->erase_tag_(this->selecting_endpoint_); + } + } else { + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + break; + + case nfc::DISCOVERY_TEAR_DOWN: + ESP_LOGV(TAG, " DISCOVERY_TEAR_DOWN"); + break; + + default: + ESP_LOGW(TAG, "Unknown error: 0x%02X", rx.get_simple_status_response()); + break; + } + break; + + default: + ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid()); + } + } else { + ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + break; + + case nfc::NCI_PKT_MT_CTRL_RESPONSE: + ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), + nfc::format_bytes(rx.get_message()).c_str()); + break; + + case nfc::NCI_PKT_MT_CTRL_COMMAND: + ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str()); + break; + + case nfc::NCI_PKT_MT_DATA: + this->process_data_message_(rx); + break; + + default: + ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str()); + break; + } +} + +void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoint was activated + uint8_t discovery_id = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_DISCOVERY_ID); + uint8_t interface = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_INTERFACE); + uint8_t protocol = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_PROTOCOL); + uint8_t mode_tech = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MODE_TECH); + uint8_t max_size = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MAX_SIZE); + + ESP_LOGVV(TAG, "Endpoint activated -- interface: 0x%02X, protocol: 0x%02X, mode&tech: 0x%02X, max payload: %u", + interface, protocol, mode_tech, max_size); + + if (mode_tech & nfc::MODE_LISTEN_MASK) { + ESP_LOGVV(TAG, "Tag activated in listen mode"); + this->nci_fsm_set_state_(NCIState::RFST_LISTEN_ACTIVE); + return; + } + + this->nci_fsm_set_state_(NCIState::RFST_POLL_ACTIVE); + auto incoming_tag = + this->build_tag_(mode_tech, std::vector(rx.get_message().begin() + 10, rx.get_message().end())); + + if (incoming_tag == nullptr) { + ESP_LOGE(TAG, "Could not build tag"); + } else { + auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid()); + if (tag_loc.has_value()) { + this->discovered_endpoint_[tag_loc.value()].id = discovery_id; + this->discovered_endpoint_[tag_loc.value()].protocol = protocol; + this->discovered_endpoint_[tag_loc.value()].last_seen = millis(); + ESP_LOGVV(TAG, "Tag cache updated"); + } else { + this->discovered_endpoint_.emplace_back( + DiscoveredEndpoint{discovery_id, protocol, millis(), std::move(incoming_tag), false}); + tag_loc = this->discovered_endpoint_.size() - 1; + ESP_LOGVV(TAG, "Tag added to cache"); + } + + auto &working_endpoint = this->discovered_endpoint_[tag_loc.value()]; + + switch (this->next_task_) { + case EP_CLEAN: + ESP_LOGD(TAG, " Tag cleaning..."); + if (this->clean_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { + ESP_LOGE(TAG, " Tag cleaning incomplete"); + } + ESP_LOGD(TAG, " Tag cleaned!"); + break; + + case EP_FORMAT: + ESP_LOGD(TAG, " Tag formatting..."); + if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error formatting tag as NDEF"); + } + ESP_LOGD(TAG, " Tag formatted!"); + break; + + case EP_WRITE: + if (this->next_task_message_to_write_ != nullptr) { + ESP_LOGD(TAG, " Tag writing..."); + ESP_LOGD(TAG, " 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 { + ESP_LOGD(TAG, " Writing NDEF data"); + if (this->write_endpoint_(working_endpoint.tag->get_uid(), this->next_task_message_to_write_) != + nfc::STATUS_OK) { + ESP_LOGE(TAG, " Failed to write message to tag"); + } + ESP_LOGD(TAG, " Finished writing NDEF data"); + this->next_task_message_to_write_ = nullptr; + this->on_finished_write_callback_.call(); + } + } + break; + + case EP_READ: + default: + if (!working_endpoint.trig_called) { + ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), + nfc::format_uid(working_endpoint.tag->get_uid()).c_str()); + if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) { + ESP_LOGW(TAG, " Unable to read NDEF record(s)"); + } else if (working_endpoint.tag->has_ndef_message()) { + const auto message = working_endpoint.tag->get_ndef_message(); + const auto records = message->get_records(); + ESP_LOGD(TAG, " NDEF record(s):"); + for (const auto &record : records) { + ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); + } + } else { + ESP_LOGW(TAG, " No NDEF records found"); + } + for (auto *trigger : this->triggers_ontag_) { + trigger->process(working_endpoint.tag); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } + working_endpoint.trig_called = true; + break; + } + } + if (working_endpoint.tag->get_tag_type() == nfc::MIFARE_CLASSIC) { + this->halt_mifare_classic_tag_(); + } + } + if (this->next_task_ != EP_READ) { + this->read_mode(); + } + + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::EP_DEACTIVATING); +} + +void PN7160::process_rf_discover_oid_(nfc::NciMessage &rx) { + auto incoming_tag = this->build_tag_(rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH), + std::vector(rx.get_message().begin() + 7, rx.get_message().end())); + + if (incoming_tag == nullptr) { + ESP_LOGE(TAG, "Could not build tag!"); + } else { + auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid()); + if (tag_loc.has_value()) { + this->discovered_endpoint_[tag_loc.value()].id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID); + this->discovered_endpoint_[tag_loc.value()].protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL); + this->discovered_endpoint_[tag_loc.value()].last_seen = millis(); + ESP_LOGVV(TAG, "Tag found & updated"); + } else { + this->discovered_endpoint_.emplace_back(DiscoveredEndpoint{rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID), + rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL), + millis(), std::move(incoming_tag), false}); + ESP_LOGVV(TAG, "Tag saved"); + } + } + + if (rx.get_message().back() != nfc::RF_DISCOVER_NTF_NT_MORE) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + ESP_LOGVV(TAG, "Discovered %u endpoints", this->discovered_endpoint_.size()); + } +} + +void PN7160::process_rf_deactivate_oid_(nfc::NciMessage &rx) { + this->ce_state_ = CardEmulationState::CARD_EMU_IDLE; + + switch (rx.get_simple_status_response()) { + case nfc::DEACTIVATION_TYPE_DISCOVERY: + this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY); + break; + + case nfc::DEACTIVATION_TYPE_IDLE: + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + break; + + case nfc::DEACTIVATION_TYPE_SLEEP: + case nfc::DEACTIVATION_TYPE_SLEEP_AF: + if (this->nci_state_ == NCIState::RFST_LISTEN_ACTIVE) { + this->nci_fsm_set_state_(NCIState::RFST_LISTEN_SLEEP); + } else if (this->nci_state_ == NCIState::RFST_POLL_ACTIVE) { + this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT); + } else { + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + break; + + default: + break; + } +} + +void PN7160::process_data_message_(nfc::NciMessage &rx) { + ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + + std::vector ndef_response; + this->card_emu_t4t_get_response_(rx.get_message(), ndef_response); + + uint16_t ndef_response_size = ndef_response.size(); + if (!ndef_response_size) { + return; // no message returned, we cannot respond + } + + std::vector tx_msg = {nfc::NCI_PKT_MT_DATA, uint8_t((ndef_response_size & 0xFF00) >> 8), + uint8_t(ndef_response_size & 0x00FF)}; + tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end()); + nfc::NciMessage tx(tx_msg); + ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending reply for card emulation failed"); + } +} + +void PN7160::card_emu_t4t_get_response_(std::vector &response, std::vector &ndef_response) { + if (this->card_emulation_message_ == nullptr) { + ESP_LOGE(TAG, "No NDEF message is set; tag emulation not possible"); + ndef_response.clear(); + return; + } + + if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_APP_SELECT))) { + // CARD_EMU_T4T_APP_SELECT + ESP_LOGVV(TAG, "CARD_EMU_NDEF_APP_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_APP_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_CC_SELECT))) { + // CARD_EMU_T4T_CC_SELECT + if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_APP_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_CC_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_CC_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_NDEF_SELECT))) { + // CARD_EMU_T4T_NDEF_SELECT + ESP_LOGVV(TAG, "CARD_EMU_NDEF_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_SELECTED; + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_READ), + std::begin(CARD_EMU_T4T_READ))) { + // CARD_EMU_T4T_READ + if (this->ce_state_ == CardEmulationState::CARD_EMU_CC_SELECTED) { + // CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED"); + uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + + if (length <= (sizeof(CARD_EMU_T4T_CC) + offset + 2)) { + ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_CC) + offset, + std::begin(CARD_EMU_T4T_CC) + offset + length); + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } else if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) { + // CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED"); + auto ndef_message = this->card_emulation_message_->encode(); + uint16_t ndef_msg_size = ndef_message.size(); + uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + + ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str()); + + if (length <= (ndef_msg_size + offset + 2)) { + if (offset == 0) { + ndef_response.resize(2); + ndef_response[0] = (ndef_msg_size & 0xFF00) >> 8; + ndef_response[1] = (ndef_msg_size & 0x00FF); + if (length > 2) { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 2); + } + } else if (offset == 1) { + ndef_response.resize(1); + ndef_response[0] = (ndef_msg_size & 0x00FF); + if (length > 1) { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 1); + } + } else { + ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length); + } + + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + + if ((offset + length) >= (ndef_msg_size + 2)) { + ESP_LOGD(TAG, "NDEF message sent"); + this->on_emulated_tag_scan_callback_.call(); + } + } + } + } else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_WRITE), + std::begin(CARD_EMU_T4T_WRITE))) { + // CARD_EMU_T4T_WRITE + if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_T4T_WRITE"); + uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; + std::vector ndef_msg_written; + + ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5, + response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length); + ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str()); + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } + } +} + +uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, + const bool expect_notification) { + uint8_t retries = NFCC_MAX_COMM_FAILS; + + while (retries) { + // first, send the message we need to send + if (this->write_nfcc(tx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error sending message"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str()); + // next, the NFCC should send back a response + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error receiving message"); + if (!retries--) { + ESP_LOGE(TAG, " ...giving up"); + return nfc::STATUS_FAILED; + } + } else { + break; + } + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + // validate the response based on the message type that was sent (command vs. data) + if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) { + // for commands, the GID and OID should match and the status should be OK + if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) { + ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + if (!rx.simple_status_response_is(nfc::STATUS_OK)) { + ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + return rx.get_simple_status_response(); + } else { + // when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first + if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) || + (!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) { + ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + if (expect_notification) { + // if the NFCC said "OK", there will be additional data to read; this comes back in a notification message + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error receiving data from endpoint"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + } + + return nfc::STATUS_OK; + } +} + +uint8_t PN7160::wait_for_irq_(uint16_t timeout, bool pin_state) { + auto start_time = millis(); + + while (millis() - start_time < timeout) { + if (this->irq_pin_->digital_read() == pin_state) { + return nfc::STATUS_OK; + } + } + ESP_LOGW(TAG, "Timed out waiting for IRQ state"); + return nfc::STATUS_FAILED; +} + +} // namespace pn7160 +} // namespace esphome diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h new file mode 100644 index 000000000000..f2e05ea1d01e --- /dev/null +++ b/esphome/components/pn7160/pn7160.h @@ -0,0 +1,315 @@ +#pragma once + +#include "esphome/components/nfc/automation.h" +#include "esphome/components/nfc/nci_core.h" +#include "esphome/components/nfc/nci_message.h" +#include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/nfc_helpers.h" +#include "esphome/core/component.h" +#include "esphome/core/gpio.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace pn7160 { + +static const uint16_t NFCC_DEFAULT_TIMEOUT = 10; +static const uint16_t NFCC_INIT_TIMEOUT = 50; +static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15; + +static const uint8_t NFCC_MAX_COMM_FAILS = 3; +static const uint8_t NFCC_MAX_ERROR_COUNT = 10; + +static const uint8_t XCHG_DATA_OID = 0x10; +static const uint8_t MF_SECTORSEL_OID = 0x32; +static const uint8_t MFC_AUTHENTICATE_OID = 0x40; +static const uint8_t TEST_PRBS_OID = 0x30; +static const uint8_t TEST_ANTENNA_OID = 0x3D; +static const uint8_t TEST_GET_REGISTER_OID = 0x33; + +static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A +static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B +static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10; + +static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, + 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}; +static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04, + 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00}; +static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}; +static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}; +static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0}; +static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6}; +static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00}; +static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82}; + +static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields + 0x00, // config param identifier (TOTAL_DURATION) + 0x02, // length of value + 0x01, // TOTAL_DURATION (low)... + 0x00}; // TOTAL_DURATION (high): 1 ms + +static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields + 0x00, // config param identifier (TOTAL_DURATION) + 0x02, // length of value + 0xF8, // TOTAL_DURATION (low)... + 0x02}; // TOTAL_DURATION (high): 760 ms + +static const uint8_t PMU_CFG[] = { + 0x01, // Number of parameters + 0xA0, 0x0E, // ext. tag + 11, // length + 0x11, // IRQ Enable: PVDD + temp sensor IRQs + 0x01, // RFU + 0x01, // Power and Clock Configuration, device on (CFG1) + 0x01, // Power and Clock Configuration, device off (CFG1) + 0x00, // RFU + 0x00, // DC-DC 0 + 0x00, // DC-DC 1 + // 0x14, // TXLDO (3.3V / 4.75V) + // 0xBB, // TXLDO (4.7V / 4.7V) + 0xFF, // TXLDO (5.0V / 5.0V) + 0x00, // RFU + 0xD0, // TXLDO check + 0x0C, // RFU +}; + +static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes + nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_FRAME, // poll mode + nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN, + nfc::INTF_ISODEP, // poll & listen mode + nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL, + nfc::INTF_TAGCMD}; // poll mode + +static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode + +static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode + +static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode + nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode + nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode + +static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming) + 2, // number of table entries + 0x01, // type = protocol-based + 3, // length + 0, // DH NFCEE ID, a static ID representing the DH-NFCEE + 0x07, // power state + nfc::PROT_ISODEP, // protocol + 0x00, // type = technology-based + 3, // length + 0, // DH NFCEE ID, a static ID representing the DH-NFCEE + 0x07, // power state + nfc::TECH_PASSIVE_NFCA}; // technology + +enum class CardEmulationState : uint8_t { + CARD_EMU_IDLE, + CARD_EMU_NDEF_APP_SELECTED, + CARD_EMU_CC_SELECTED, + CARD_EMU_NDEF_SELECTED, + CARD_EMU_DESFIRE_PROD, +}; + +enum class NCIState : uint8_t { + NONE = 0x00, + NFCC_RESET, + NFCC_INIT, + NFCC_CONFIG, + NFCC_SET_DISCOVER_MAP, + NFCC_SET_LISTEN_MODE_ROUTING, + RFST_IDLE, + RFST_DISCOVERY, + RFST_W4_ALL_DISCOVERIES, + RFST_W4_HOST_SELECT, + RFST_LISTEN_ACTIVE, + RFST_LISTEN_SLEEP, + RFST_POLL_ACTIVE, + EP_DEACTIVATING, + EP_SELECTING, + TEST = 0XFE, + FAILED = 0XFF, +}; + +enum class TestMode : uint8_t { + TEST_NONE = 0x00, + TEST_PRBS, + TEST_ANTENNA, + TEST_GET_REGISTER, +}; + +struct DiscoveredEndpoint { + uint8_t id; + uint8_t protocol; + uint32_t last_seen; + std::unique_ptr tag; + bool trig_called; +}; + +class PN7160 : public nfc::Nfcc, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void loop() override; + + void set_dwl_req_pin(GPIOPin *dwl_req_pin) { this->dwl_req_pin_ = dwl_req_pin; } + void set_irq_pin(GPIOPin *irq_pin) { this->irq_pin_ = irq_pin; } + void set_ven_pin(GPIOPin *ven_pin) { this->ven_pin_ = ven_pin; } + void set_wkup_req_pin(GPIOPin *wkup_req_pin) { this->wkup_req_pin_ = wkup_req_pin; } + + void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; } + void set_tag_emulation_message(std::shared_ptr message); + void set_tag_emulation_message(const optional &message, optional include_android_app_record); + void set_tag_emulation_message(const char *message, bool include_android_app_record = true); + void set_tag_emulation_off(); + void set_tag_emulation_on(); + bool tag_emulation_enabled() { return this->listening_enabled_; } + + void set_polling_off(); + void set_polling_on(); + bool polling_enabled() { return this->polling_enabled_; } + + void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } + void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } + + void add_on_emulated_tag_scan_callback(std::function callback) { + this->on_emulated_tag_scan_callback_.add(std::move(callback)); + } + + void add_on_finished_write_callback(std::function callback) { + this->on_finished_write_callback_.add(std::move(callback)); + } + + bool is_writing() { return this->next_task_ != EP_READ; }; + + void read_mode(); + void clean_mode(); + void format_mode(); + void write_mode(); + void set_tag_write_message(std::shared_ptr message); + void set_tag_write_message(optional message, optional include_android_app_record); + + uint8_t set_test_mode(TestMode test_mode, const std::vector &data, std::vector &result); + + protected: + uint8_t reset_core_(bool reset_config, bool power); + uint8_t init_core_(); + uint8_t send_init_config_(); + uint8_t send_core_config_(); + uint8_t refresh_core_config_(); + + uint8_t set_discover_map_(); + + uint8_t set_listen_mode_routing_(); + + uint8_t start_discovery_(); + uint8_t stop_discovery_(); + uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT); + + void select_endpoint_(); + + uint8_t read_endpoint_data_(nfc::NfcTag &tag); + uint8_t clean_endpoint_(std::vector &uid); + uint8_t format_endpoint_(std::vector &uid); + uint8_t write_endpoint_(std::vector &uid, std::shared_ptr &message); + + std::unique_ptr build_tag_(uint8_t mode_tech, const std::vector &data); + optional find_tag_uid_(const std::vector &uid); + void purge_old_tags_(); + void erase_tag_(uint8_t tag_index); + + /// advance controller state as required + void nci_fsm_transition_(); + /// set new controller state + void nci_fsm_set_state_(NCIState new_state); + /// setting controller to this state caused an error; returns true if too many errors/failures + bool nci_fsm_set_error_state_(NCIState new_state); + /// parse & process incoming messages from the NFCC + void process_message_(); + void process_rf_intf_activated_oid_(nfc::NciMessage &rx); + void process_rf_discover_oid_(nfc::NciMessage &rx); + void process_rf_deactivate_oid_(nfc::NciMessage &rx); + void process_data_message_(nfc::NciMessage &rx); + + void card_emu_t4t_get_response_(std::vector &response, std::vector &ndef_response); + + uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT, + bool expect_notification = true); + virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0; + virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0; + + uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true); + + uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag); + uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key); + uint8_t sect_to_auth_(uint8_t block_num); + uint8_t format_mifare_classic_mifare_(); + uint8_t format_mifare_classic_ndef_(); + uint8_t write_mifare_classic_tag_(const std::shared_ptr &message); + uint8_t halt_mifare_classic_tag_(); + + uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag); + uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data); + bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); + uint16_t read_mifare_ultralight_capacity_(); + uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index); + uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + uint8_t write_mifare_ultralight_tag_(std::vector &uid, const std::shared_ptr &message); + uint8_t clean_mifare_ultralight_(); + + enum NfcTask : uint8_t { + EP_READ = 0, + EP_CLEAN, + EP_FORMAT, + EP_WRITE, + } next_task_{EP_READ}; + + bool config_refresh_pending_{false}; + bool core_config_is_solo_{false}; + bool listening_enabled_{false}; + bool polling_enabled_{true}; + + uint8_t error_count_{0}; + uint8_t fail_count_{0}; + uint32_t last_nci_state_change_{0}; + uint8_t selecting_endpoint_{0}; + uint32_t tag_ttl_{250}; + + GPIOPin *dwl_req_pin_{nullptr}; + GPIOPin *irq_pin_{nullptr}; + GPIOPin *ven_pin_{nullptr}; + GPIOPin *wkup_req_pin_{nullptr}; + + CallbackManager on_emulated_tag_scan_callback_; + CallbackManager on_finished_write_callback_; + + std::vector discovered_endpoint_; + + CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE}; + NCIState nci_state_{NCIState::NFCC_RESET}; + NCIState nci_state_error_{NCIState::NONE}; + + std::shared_ptr card_emulation_message_; + std::shared_ptr next_task_message_to_write_; + + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; +}; + +} // namespace pn7160 +} // namespace esphome diff --git a/esphome/components/pn7160/pn7160_mifare_classic.cpp b/esphome/components/pn7160/pn7160_mifare_classic.cpp new file mode 100644 index 000000000000..fa63cc00d50b --- /dev/null +++ b/esphome/components/pn7160/pn7160_mifare_classic.cpp @@ -0,0 +1,322 @@ +#include + +#include "pn7160.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7160 { + +static const char *const TAG = "pn7160.mifare_classic"; + +uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) { + uint8_t current_block = 4; + uint8_t message_start_index = 0; + uint32_t message_length = 0; + + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data"); + return nfc::STATUS_FAILED; + } + std::vector data; + + if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) { + if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { + return nfc::STATUS_FAILED; + } + } else { + ESP_LOGE(TAG, "Failed to read block %u", current_block); + return nfc::STATUS_FAILED; + } + + uint32_t index = 0; + uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length); + std::vector buffer; + + while (index < buffer_size) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Block authentication failed for %u", current_block); + return nfc::STATUS_FAILED; + } + } + std::vector block_data; + if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading block %u", current_block); + return nfc::STATUS_FAILED; + } else { + buffer.insert(buffer.end(), block_data.begin(), block_data.end()); + } + + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + current_block++; + } + } + + if (buffer.begin() + message_start_index < buffer.end()) { + buffer.erase(buffer.begin(), buffer.begin() + message_start_index); + } else { + return nfc::STATUS_FAILED; + } + + tag.set_ndef_message(make_unique(buffer)); + + return nfc::STATUS_OK; +} + +uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num}); + + ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Timeout reading tag data"); + return nfc::STATUS_FAILED; + } + + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) || + (!rx.message_length_is(18))) { + ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num); + ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1); + + ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str()); + return nfc::STATUS_OK; +} + +uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num}); + + switch (key_num) { + case nfc::MIFARE_CMD_AUTH_A: + tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A; + break; + + case nfc::MIFARE_CMD_AUTH_B: + tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B; + break; + + default: + break; + } + + if (key != nullptr) { + tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY; + tx.get_message().insert(tx.get_message().end(), key, key + 6); + } + + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed"); + return nfc::STATUS_FAILED; + } + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) || + (rx.get_message()[4] != nfc::STATUS_OK)) { + ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num); + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num); + return nfc::STATUS_OK; +} + +uint8_t PN7160::sect_to_auth_(const uint8_t block_num) { + const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START; + if (block_num >= first_high_block) { + return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) + + nfc::MIFARE_CLASSIC_16BLOCK_SECT_START; + } + return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW; +} + +uint8_t PN7160::format_mifare_classic_mifare_() { + std::vector blank_buffer( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector trailer_buffer( + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + auto status = nfc::STATUS_OK; + + for (int block = 0; block < 64; block += 4) { + if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + continue; + } + if (block != 0) { + if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } + if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 1); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 2); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 3); + status = nfc::STATUS_FAILED; + } + } + + return status; +} + +uint8_t PN7160::format_mifare_classic_ndef_() { + std::vector empty_ndef_message( + {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector blank_block( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector block_1_data( + {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_2_data( + {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_3_trailer( + {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + std::vector ndef_trailer( + {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting"); + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + + ESP_LOGD(TAG, "Sector 0 formatted with NDEF"); + + auto status = nfc::STATUS_OK; + + for (int block = 4; block < 64; block += 4) { + if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + if (block == 4) { + if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } else { + if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block); + status = nfc::STATUS_FAILED; + } + } + if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 1); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write block %u", block + 2); + status = nfc::STATUS_FAILED; + } + if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3); + status = nfc::STATUS_FAILED; + } + } + return status; +} + +uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); + + ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed"); + return nfc::STATUS_FAILED; + } + // write command part two + tx.set_payload({XCHG_DATA_OID}); + tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end()); + + ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write"); + return nfc::STATUS_FAILED; + } + + if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) || + (rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) { + ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num); + ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str()); + return nfc::STATUS_FAILED; + } + + return nfc::STATUS_OK; +} + +uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr &message) { + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length); + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 3, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_block = 4; + + while (index < buffer_length) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + } + + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); + if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + // Skipping as cannot write to trailer + current_block++; + } + } + return nfc::STATUS_OK; +} + +uint8_t PN7160::halt_mifare_classic_tag_() { + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0}); + + ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +} // namespace pn7160 +} // namespace esphome diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp new file mode 100644 index 000000000000..a74f23d4f23b --- /dev/null +++ b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp @@ -0,0 +1,186 @@ +#include +#include + +#include "pn7160.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7160 { + +static const char *const TAG = "pn7160.mifare_ultralight"; + +uint8_t PN7160::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { + std::vector data; + // pages 3 to 6 contain various info we are interested in -- do one read to grab it all + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE, + data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + + if (!this->is_mifare_ultralight_formatted_(data)) { + ESP_LOGW(TAG, "Not NDEF formatted"); + return nfc::STATUS_FAILED; + } + + uint8_t message_length; + uint8_t message_start_index; + if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Couldn't find NDEF message"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index); + + if (message_length == 0) { + return nfc::STATUS_FAILED; + } + // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages + const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0; + if (read_length) { + if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) != + nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading tag data"); + return nfc::STATUS_FAILED; + } + } + // we need to trim off page 3 as well as any bytes ahead of message_start_index + data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + + tag.set_ndef_message(make_unique(data)); + + return nfc::STATUS_OK; +} + +uint8_t PN7160::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data) { + const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page}); + + for (size_t i = 0; i * read_increment < num_bytes; i++) { + tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page; + do { // loop because sometimes we struggle here...???... + if (this->transceive_(tx, rx) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error reading tag data"); + return nfc::STATUS_FAILED; + } + } while (rx.get_payload_size() < read_increment); + uint16_t bytes_offset = (i + 1) * read_increment; + auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1 + : rx.get_message().end() - (bytes_offset - num_bytes + 1); + + if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) { + data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr); + } + } + + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); + + return nfc::STATUS_OK; +} + +bool PN7160::is_mifare_ultralight_formatted_(const std::vector &page_3_to_6) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + return (page_3_to_6.size() > p4_offset + 3) && + !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && + (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); +} + +uint16_t PN7160::read_mifare_ultralight_capacity_() { + std::vector data; + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) { + ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U); + return data[2] * 8U; + } + return 0; +} + +uint8_t PN7160::find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + if (!(page_3_to_6.size() > p4_offset + 5)) { + return nfc::STATUS_FAILED; + } + + if (page_3_to_6[p4_offset + 0] == 0x03) { + message_length = page_3_to_6[p4_offset + 1]; + message_start_index = 2; + return nfc::STATUS_OK; + } else if (page_3_to_6[p4_offset + 5] == 0x03) { + message_length = page_3_to_6[p4_offset + 6]; + message_start_index = 7; + return nfc::STATUS_OK; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN7160::write_mifare_ultralight_tag_(std::vector &uid, + const std::shared_ptr &message) { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); + + if (buffer_length > capacity) { + ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity); + return nfc::STATUS_FAILED; + } + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 2, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + while (index < buffer_length) { + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + current_page++; + } + return nfc::STATUS_OK; +} + +uint8_t PN7160::clean_mifare_ultralight_() { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + + for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { + if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) { + return nfc::STATUS_FAILED; + } + } + return nfc::STATUS_OK; +} + +uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { + std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; + payload.insert(payload.end(), write_data.begin(), write_data.end()); + + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload); + + if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Error writing page %u", page_num); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +} // namespace pn7160 +} // namespace esphome diff --git a/esphome/components/pn7160_i2c/__init__.py b/esphome/components/pn7160_i2c/__init__.py new file mode 100644 index 000000000000..87c4719ca854 --- /dev/null +++ b/esphome/components/pn7160_i2c/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, pn7160 +from esphome.const import CONF_ID + +AUTO_LOAD = ["pn7160"] +CODEOWNERS = ["@kbx81", "@jesserockz"] +DEPENDENCIES = ["i2c"] + +pn7160_i2c_ns = cg.esphome_ns.namespace("pn7160_i2c") +PN7160I2C = pn7160_i2c_ns.class_("PN7160I2C", pn7160.PN7160, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All( + pn7160.PN7160_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN7160I2C), + } + ).extend(i2c.i2c_device_schema(0x28)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await pn7160.setup_pn7160(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/pn7160_i2c/pn7160_i2c.cpp b/esphome/components/pn7160_i2c/pn7160_i2c.cpp new file mode 100644 index 000000000000..7c6da9dd0684 --- /dev/null +++ b/esphome/components/pn7160_i2c/pn7160_i2c.cpp @@ -0,0 +1,49 @@ +#include "pn7160_i2c.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace pn7160_i2c { + +static const char *const TAG = "pn7160_i2c"; + +uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { + if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ"); + return nfc::STATUS_FAILED; + } + + rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE); + if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) { + return nfc::STATUS_FAILED; + } + + uint8_t length = rx.get_payload_size(); + if (length > 0) { + rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE); + if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) { + return nfc::STATUS_FAILED; + } + } + // semaphore to ensure transaction is complete before returning + if (this->wait_for_irq_(pn7160::NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() post-read timeout waiting for IRQ line to clear"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7160I2C::write_nfcc(nfc::NciMessage &tx) { + if (this->write(tx.encode().data(), tx.encode().size()) == i2c::ERROR_OK) { + return nfc::STATUS_OK; + } + return nfc::STATUS_FAILED; +} + +void PN7160I2C::dump_config() { + PN7160::dump_config(); + LOG_I2C_DEVICE(this); +} + +} // namespace pn7160_i2c +} // namespace esphome diff --git a/esphome/components/pn7160_i2c/pn7160_i2c.h b/esphome/components/pn7160_i2c/pn7160_i2c.h new file mode 100644 index 000000000000..eb253085eb74 --- /dev/null +++ b/esphome/components/pn7160_i2c/pn7160_i2c.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/pn7160/pn7160.h" +#include "esphome/components/i2c/i2c.h" + +#include + +namespace esphome { +namespace pn7160_i2c { + +class PN7160I2C : public pn7160::PN7160, public i2c::I2CDevice { + public: + void dump_config() override; + + protected: + uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override; + uint8_t write_nfcc(nfc::NciMessage &tx) override; +}; + +} // namespace pn7160_i2c +} // namespace esphome diff --git a/esphome/components/pn7160_spi/__init__.py b/esphome/components/pn7160_spi/__init__.py new file mode 100644 index 000000000000..ae1235655a73 --- /dev/null +++ b/esphome/components/pn7160_spi/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi, pn7160 +from esphome.const import CONF_ID + +AUTO_LOAD = ["pn7160"] +CODEOWNERS = ["@kbx81", "@jesserockz"] +DEPENDENCIES = ["spi"] +MULTI_CONF = True + +pn7160_spi_ns = cg.esphome_ns.namespace("pn7160_spi") +PN7160Spi = pn7160_spi_ns.class_("PN7160Spi", pn7160.PN7160, spi.SPIDevice) + +CONFIG_SCHEMA = cv.All( + pn7160.PN7160_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN7160Spi), + } + ).extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await pn7160.setup_pn7160(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/pn7160_spi/pn7160_spi.cpp b/esphome/components/pn7160_spi/pn7160_spi.cpp new file mode 100644 index 000000000000..09f673f700ca --- /dev/null +++ b/esphome/components/pn7160_spi/pn7160_spi.cpp @@ -0,0 +1,54 @@ +#include "pn7160_spi.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn7160_spi { + +static const char *const TAG = "pn7160_spi"; + +void PN7160Spi::setup() { + this->spi_setup(); + this->cs_->digital_write(false); + PN7160::setup(); +} + +uint8_t PN7160Spi::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { + if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ"); + return nfc::STATUS_FAILED; + } + + rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE); + this->enable(); + this->write_byte(TDD_SPI_READ); // send "transfer direction detector" + this->read_array(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE); + + uint8_t length = rx.get_payload_size(); + if (length > 0) { + rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE); + this->read_array(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length); + } + this->disable(); + // semaphore to ensure transaction is complete before returning + if (this->wait_for_irq_(pn7160::NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "read_nfcc_() post-read timeout waiting for IRQ line to clear"); + return nfc::STATUS_FAILED; + } + return nfc::STATUS_OK; +} + +uint8_t PN7160Spi::write_nfcc(nfc::NciMessage &tx) { + this->enable(); + this->write_byte(TDD_SPI_WRITE); // send "transfer direction detector" + this->write_array(tx.encode().data(), tx.encode().size()); + this->disable(); + return nfc::STATUS_OK; +} + +void PN7160Spi::dump_config() { + PN7160::dump_config(); + LOG_PIN(" CS Pin: ", this->cs_); +} + +} // namespace pn7160_spi +} // namespace esphome diff --git a/esphome/components/pn7160_spi/pn7160_spi.h b/esphome/components/pn7160_spi/pn7160_spi.h new file mode 100644 index 000000000000..7d4460a76de9 --- /dev/null +++ b/esphome/components/pn7160_spi/pn7160_spi.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/nfc/nci_core.h" +#include "esphome/components/pn7160/pn7160.h" +#include "esphome/components/spi/spi.h" + +#include + +namespace esphome { +namespace pn7160_spi { + +static const uint8_t TDD_SPI_READ = 0xFF; +static const uint8_t TDD_SPI_WRITE = 0x0A; + +class PN7160Spi : public pn7160::PN7160, + public spi::SPIDevice { + public: + void setup() override; + + void dump_config() override; + + protected: + uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override; + uint8_t write_nfcc(nfc::NciMessage &tx) override; +}; + +} // namespace pn7160_spi +} // namespace esphome diff --git a/esphome/components/power_supply/__init__.py b/esphome/components/power_supply/__init__.py index f7dd8bca84c7..01b541e4b571 100644 --- a/esphome/components/power_supply/__init__.py +++ b/esphome/components/power_supply/__init__.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN +from esphome.const import ( + CONF_ENABLE_ON_BOOT, + CONF_ENABLE_TIME, + CONF_ID, + CONF_KEEP_ON_TIME, + CONF_PIN, +) CODEOWNERS = ["@esphome/core"] power_supply_ns = cg.esphome_ns.namespace("power_supply") @@ -18,6 +24,7 @@ cv.Optional( CONF_KEEP_ON_TIME, default="10s" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ENABLE_ON_BOOT, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -30,5 +37,6 @@ async def to_code(config): cg.add(var.set_pin(pin)) cg.add(var.set_enable_time(config[CONF_ENABLE_TIME])) cg.add(var.set_keep_on_time(config[CONF_KEEP_ON_TIME])) + cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add_define("USE_POWER_SUPPLY") diff --git a/esphome/components/power_supply/power_supply.cpp b/esphome/components/power_supply/power_supply.cpp index a492919202c9..7474075302b7 100644 --- a/esphome/components/power_supply/power_supply.cpp +++ b/esphome/components/power_supply/power_supply.cpp @@ -11,47 +11,42 @@ void PowerSupply::setup() { this->pin_->setup(); this->pin_->digital_write(false); - this->enabled_ = false; + if (this->enable_on_boot_) + this->request_high_power(); } void PowerSupply::dump_config() { ESP_LOGCONFIG(TAG, "Power Supply:"); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Time to enable: %u ms", this->enable_time_); + ESP_LOGCONFIG(TAG, " Time to enable: %" PRIu32 " ms", this->enable_time_); ESP_LOGCONFIG(TAG, " Keep on time: %.1f s", this->keep_on_time_ / 1000.0f); + if (this->enable_on_boot_) + ESP_LOGCONFIG(TAG, " Enabled at startup: True"); } float PowerSupply::get_setup_priority() const { return setup_priority::IO; } -bool PowerSupply::is_enabled() const { return this->enabled_; } +bool PowerSupply::is_enabled() const { return this->active_requests_ != 0; } void PowerSupply::request_high_power() { - this->cancel_timeout("power-supply-off"); - this->pin_->digital_write(true); - if (this->active_requests_ == 0) { - // we need to enable the power supply. - // cancel old timeout if it exists because we now definitely have a high power mode. + this->cancel_timeout("power-supply-off"); ESP_LOGD(TAG, "Enabling power supply."); + this->pin_->digital_write(true); delay(this->enable_time_); } - this->enabled_ = true; - // increase active requests this->active_requests_++; } void PowerSupply::unrequest_high_power() { - this->active_requests_--; - if (this->active_requests_ < 0) { - // we're just going to use 0 as our new counter. - this->active_requests_ = 0; + if (this->active_requests_ == 0) { + ESP_LOGW(TAG, "Invalid call to unrequest_high_power"); + return; } - + this->active_requests_--; if (this->active_requests_ == 0) { - // set timeout for power supply off this->set_timeout("power-supply-off", this->keep_on_time_, [this]() { ESP_LOGD(TAG, "Disabling power supply."); this->pin_->digital_write(false); - this->enabled_ = false; }); } } diff --git a/esphome/components/power_supply/power_supply.h b/esphome/components/power_supply/power_supply.h index 66e4a7565aac..0b06105ae9c0 100644 --- a/esphome/components/power_supply/power_supply.h +++ b/esphome/components/power_supply/power_supply.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include + namespace esphome { namespace power_supply { @@ -11,6 +13,7 @@ class PowerSupply : public Component { void set_pin(GPIOPin *pin) { pin_ = pin; } void set_enable_time(uint32_t enable_time) { enable_time_ = enable_time; } void set_keep_on_time(uint32_t keep_on_time) { keep_on_time_ = keep_on_time; } + void set_enable_on_boot(bool enable_on_boot) { enable_on_boot_ = enable_on_boot; } /// Is this power supply currently on? bool is_enabled() const; @@ -33,7 +36,7 @@ class PowerSupply : public Component { protected: GPIOPin *pin_; - bool enabled_{false}; + bool enable_on_boot_{false}; uint32_t enable_time_; uint32_t keep_on_time_; int16_t active_requests_{0}; // use signed integer to make catching negative requests easier. diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index abb5111aaf57..68bca95a2122 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "prometheus_handler.h" #include "esphome/core/application.h" @@ -89,7 +87,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor stream->print(obj->get_unit_of_measurement().c_str()); stream->print(F("\"} ")); stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); - stream->print('\n'); + stream->print(F("\n")); } else { // Invalid state stream->print(F("esphome_sensor_failed{id=\"")); @@ -124,7 +122,7 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); - stream->print('\n'); + stream->print(F("\n")); } else { // Invalid state stream->print(F("esphome_binary_sensor_failed{id=\"")); @@ -158,7 +156,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); - stream->print('\n'); + stream->print(F("\n")); // Speed if available if (obj->get_traits().supports_speed()) { stream->print(F("esphome_fan_speed{id=\"")); @@ -167,7 +165,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->speed); - stream->print('\n'); + stream->print(F("\n")); } // Oscillation if available if (obj->get_traits().supports_oscillation()) { @@ -177,7 +175,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->oscillating); - stream->print('\n'); + stream->print(F("\n")); } } #endif @@ -281,7 +279,7 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->position); - stream->print('\n'); + stream->print(F("\n")); if (obj->get_traits().get_supports_tilt()) { stream->print(F("esphome_cover_tilt{id=\"")); stream->print(relabel_id_(obj).c_str()); @@ -289,7 +287,7 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->tilt); - stream->print('\n'); + stream->print(F("\n")); } } else { // Invalid state @@ -322,7 +320,7 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); - stream->print('\n'); + stream->print(F("\n")); } #endif @@ -346,11 +344,9 @@ void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) stream->print(relabel_name_(obj).c_str()); stream->print(F("\"} ")); stream->print(obj->state); - stream->print('\n'); + stream->print(F("\n")); } #endif } // namespace prometheus } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 0ae2856ce43f..a9505a3572e6 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -1,14 +1,12 @@ #pragma once -#ifdef USE_ARDUINO - #include #include -#include "esphome/core/entity_base.h" #include "esphome/components/web_server_base/web_server_base.h" -#include "esphome/core/controller.h" #include "esphome/core/component.h" +#include "esphome/core/controller.h" +#include "esphome/core/entity_base.h" namespace esphome { namespace prometheus { @@ -119,5 +117,3 @@ class PrometheusHandler : public AsyncWebHandler, public Component { } // namespace prometheus } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index ac6d034514ec..796957c315a1 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -1,9 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.core import CORE from esphome.const import ( CONF_ID, + CONF_MODE, + CONF_SPEED, ) CODEOWNERS = ["@esphome/core"] @@ -11,8 +13,26 @@ psram_ns = cg.esphome_ns.namespace("psram") PsramComponent = psram_ns.class_("PsramComponent", cg.Component) +SPIRAM_MODES = { + "quad": "CONFIG_SPIRAM_MODE_QUAD", + "octal": "CONFIG_SPIRAM_MODE_OCT", +} + +SPIRAM_SPEEDS = { + 40e6: "CONFIG_SPIRAM_SPEED_40M", + 80e6: "CONFIG_SPIRAM_SPEED_80M", + 120e6: "CONFIG_SPIRAM_SPEED_120M", +} + CONFIG_SCHEMA = cv.All( - cv.Schema({cv.GenerateID(): cv.declare_id(PsramComponent)}), cv.only_on_esp32 + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PsramComponent), + cv.Optional(CONF_MODE): cv.enum(SPIRAM_MODES, lower=True), + cv.Optional(CONF_SPEED): cv.All(cv.frequency, cv.one_of(*SPIRAM_SPEEDS)), + } + ), + cv.only_on_esp32, ) @@ -21,9 +41,20 @@ async def to_code(config): cg.add_build_flag("-DBOARD_HAS_PSRAM") if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True) + add_idf_sdkconfig_option( + f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True + ) + 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) + if CONF_MODE in config: + add_idf_sdkconfig_option(f"{SPIRAM_MODES[config[CONF_MODE]]}", True) + if CONF_SPEED in config: + add_idf_sdkconfig_option(f"{SPIRAM_SPEEDS[config[CONF_SPEED]]}", True) + + cg.add_define("USE_PSRAM") + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 1f50360fed76..281a61a66a11 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -104,7 +104,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { if (this->filter_us != 0) { uint16_t filter_val = std::min(static_cast(this->filter_us * 80u), 1023u); - ESP_LOGCONFIG(TAG, " Filter Value: %uus (val=%u)", this->filter_us, filter_val); + ESP_LOGCONFIG(TAG, " Filter Value: %" PRIu32 "us (val=%u)", this->filter_us, filter_val); error = pcnt_set_filter_value(this->pcnt_unit, filter_val); if (error != ESP_OK) { ESP_LOGE(TAG, "Setting filter value failed: %s", esp_err_to_name(error)); @@ -161,7 +161,7 @@ void PulseCounterSensor::dump_config() { LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, " Rising Edge: %s", EDGE_MODE_TO_STRING[this->storage_.rising_edge_mode]); ESP_LOGCONFIG(TAG, " Falling Edge: %s", EDGE_MODE_TO_STRING[this->storage_.falling_edge_mode]); - ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->storage_.filter_us); + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->storage_.filter_us); LOG_UPDATE_INTERVAL(this); } @@ -177,7 +177,7 @@ void PulseCounterSensor::update() { if (this->total_sensor_ != nullptr) { current_total_ += raw; - ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_); + ESP_LOGD(TAG, "'%s': Total : %" PRIu32 " pulses", this->get_name().c_str(), current_total_); this->total_sensor_->publish_state(current_total_); } this->last_time_ = now; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index ef944f106f73..ef9f73f95cb4 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -4,6 +4,8 @@ #include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" +#include + #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) #include #define HAS_PCNT diff --git a/esphome/components/pulse_meter/pulse_filter.md b/esphome/components/pulse_meter/pulse_filter.md new file mode 100644 index 000000000000..240c479d54ff --- /dev/null +++ b/esphome/components/pulse_meter/pulse_filter.md @@ -0,0 +1,61 @@ +# PULSE Filter + +The PULSE filter filters noisy pulses by ensuring that the pulse is in a steady state for at least `filter_length` before allowing the state change to occur. +It counts the pulse time from the rising edge that stayed high for at least `filter_length`, so noise before this won't be considered the start of a pulse. +It then must see a low pulse that is at least `filter_length` long before a subsequent rising edge is considered a new pulse start. + +It's operation should be the same as delayed_on_off from the Binary Sensor component. + +There are three moving parts in the algorithm that are used to determine the state of the filter. + +1. The time between interrupts, measured from the last interrupt, this is compared to filter_length to determine if the pulse has been in a steady state for long enough. +2. A latch variable that is set true when a high pulse is long enough to be considered a valid pulse and is reset when a low pulse is long enough to allow for another pulse to begin. +3. The previous and current state of the pin used to determine if the pulse is rising or falling. + +## Ghost interrupts + +Observations from the devices show that even though the interrupt should trigger on every rising or falling edge, sometimes interrupts show the same state twice in a row. +The current theory is an interprets occurs, but then the pin changing back faster than the ISR can be called and read the value, meaning it sees the same state twice in a row. +The algorithm interprets these when it sees them as two edges in a row, so will potentially reset a pulse if + +## Pulse Filter Truth table + +The following is all of the possible states of the filter along with the new inputs. +It also shows a diagram of a possible series of interrupts that would cause the filter to enter that state. +It then has the action that the filter should take and a description of what is happening. + +Diagram legend + +- `/` rising edge +- `\` falling edge +- `‾` high steady state of at least `filter_length` +- `_` low steady state of at least `filter_length` +- `¦` ghost interrupt + +| Length | Latch | From | To | Diagram | Action | Description | +| ------ | ----- | ---- | --- | ------- | ------------------ | ---------------------------------------------------------------------------------------------------- | +| T | 1 | 0 | 0 | `‾\_¦ ` | Reset | `filter_length` low, reset the latch | +| T | 1 | 0 | 1 | `‾\_/ ` | Reset, Rising Edge | `filter_length` low, reset the latch, rising edge could be a new pulse | +| T | 1 | 1 | 0 | `‾\/‾\` | - | Already latched from a previous `filter_length` high | +| T | 1 | 1 | 1 | `‾\/‾¦` | - | Already latched from a previous `filter_length` high | +| T | 0 | 1 | 1 | `_/‾¦` | Set and Publish | `filter_length` high, set the latch and publish the pulse | +| T | 0 | 1 | 0 | `_/‾\ ` | Set and Publish | `filter_length` high, set the latch and publish the pulse | +| T | 0 | 0 | 1 | `_/\_/` | Rising Edge | Already unlatched from a previous `filter_length` low | +| T | 0 | 0 | 0 | `_/\_¦` | - | Already unlatched from a previous `filter_length` low | +| F | 1 | 0 | 0 | `‾\¦ ` | - | Low was not long enough to reset the latch | +| F | 1 | 0 | 1 | `‾\/ ` | - | Low was not long enough to reset the latch | +| F | 1 | 1 | 0 | `‾\/\ ` | - | Low was not long enough to reset the latch | +| F | 1 | 1 | 1 | `‾¦ ` | - | Ghost of 0 length definitely was not long was not long enough to reset the latch | +| F | 0 | 1 | 1 | `_/¦ ` | Rising Edge | High was not long enough to set the latch, but the second half of the ghost can be a new rising edge | +| F | 0 | 1 | 0 | `_/\ ` | - | High was not long enough to set the latch | +| F | 0 | 0 | 1 | `_/\/ ` | Rising Edge | High was not long enough to set the latch, but this may be a rising edge | +| F | 0 | 0 | 0 | `_¦ ` | - | Ghost of 0 length definitely was not long was not long enough to set the latch | + +## Startup + +On startup the filter should not consider whatever state it is in as valid so it does not count a strange pulse. +There are two possible starting configurations, either the pin is high or the pin is low. +If the pin is high, the subsequent falling edge should not count as a pulse as we never saw the rising edge. +Therefore we start in the latched state. +If the pin is low, the subsequent rising edge can be counted as the first pulse. +Therefore we start in the unlatched state. diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 7eef18e5e004..530425563cd0 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -7,18 +7,33 @@ namespace pulse_meter { static const char *const TAG = "pulse_meter"; +void PulseMeterSensor::set_total_pulses(uint32_t pulses) { + this->total_pulses_ = pulses; + if (this->total_sensor_ != nullptr) { + this->total_sensor_->publish_state(this->total_pulses_); + } +} + void PulseMeterSensor::setup() { this->pin_->setup(); this->isr_pin_ = pin_->to_isr(); + // Set the last processed edge to now for the first timeout + this->last_processed_edge_us_ = micros(); + if (this->filter_mode_ == FILTER_EDGE) { this->pin_->attach_interrupt(PulseMeterSensor::edge_intr, this, gpio::INTERRUPT_RISING_EDGE); } else if (this->filter_mode_ == FILTER_PULSE) { + // Set the pin value to the current value to avoid a false edge + this->pulse_state_.last_pin_val_ = this->isr_pin_.digital_read(); + this->pulse_state_.latched_ = this->pulse_state_.last_pin_val_; this->pin_->attach_interrupt(PulseMeterSensor::pulse_intr, this, gpio::INTERRUPT_ANY_EDGE); } } void PulseMeterSensor::loop() { + const uint32_t now = micros(); + // Reset the count in get before we pass it back to the ISR as set this->get_->count_ = 0; @@ -28,6 +43,20 @@ void PulseMeterSensor::loop() { this->set_ = this->get_; this->get_ = temp; + // If an edge was peeked, repay the debt + if (this->peeked_edge_ && this->get_->count_ > 0) { + this->peeked_edge_ = false; + this->get_->count_--; + } + + // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early + if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ && + now - this->get_->last_rising_edge_us_ >= this->filter_us_) { + this->peeked_edge_ = true; + this->get_->last_detected_edge_us_ = this->get_->last_rising_edge_us_; + this->get_->count_++; + } + // Check if we detected a pulse this loop if (this->get_->count_ > 0) { // Keep a running total of pulses if a total sensor is configured @@ -38,25 +67,37 @@ void PulseMeterSensor::loop() { } // We need to detect at least two edges to have a valid pulse width - if (!this->initialized_) { - this->initialized_ = true; - } else { - uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_; - float pulse_width_us = delta_us / float(this->get_->count_); - this->publish_state((60.0f * 1000000.0f) / pulse_width_us); + switch (this->meter_state_) { + case MeterState::INITIAL: + case MeterState::TIMED_OUT: { + this->meter_state_ = MeterState::RUNNING; + } break; + case MeterState::RUNNING: { + uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_; + float pulse_width_us = delta_us / float(this->get_->count_); + this->publish_state((60.0f * 1000000.0f) / pulse_width_us); + } break; } this->last_processed_edge_us_ = this->get_->last_detected_edge_us_; } // No detected edges this loop else { - const uint32_t now = micros(); const uint32_t time_since_valid_edge_us = now - this->last_processed_edge_us_; - if (this->initialized_ && time_since_valid_edge_us > this->timeout_us_) { - ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); - this->initialized_ = false; - this->publish_state(0.0f); + switch (this->meter_state_) { + // Running and initial states can timeout + case MeterState::INITIAL: + case MeterState::RUNNING: { + if (time_since_valid_edge_us > this->timeout_us_) { + this->meter_state_ = MeterState::TIMED_OUT; + ESP_LOGD(TAG, "No pulse detected for %" PRIu32 "s, assuming 0 pulses/min", + time_since_valid_edge_us / 1000000); + this->publish_state(0.0f); + } + } break; + default: + break; } } } @@ -67,22 +108,26 @@ void PulseMeterSensor::dump_config() { LOG_SENSOR("", "Pulse Meter", this); LOG_PIN(" Pin: ", this->pin_); if (this->filter_mode_ == FILTER_EDGE) { - ESP_LOGCONFIG(TAG, " Filtering rising edges less than %u µs apart", this->filter_us_); + ESP_LOGCONFIG(TAG, " Filtering rising edges less than %" PRIu32 " µs apart", this->filter_us_); } else { - ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %" PRIu32 " µs", this->filter_us_); } - ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); + ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %" PRIu32 "s", + this->timeout_us_ / 1000000); } void IRAM_ATTR PulseMeterSensor::edge_intr(PulseMeterSensor *sensor) { // This is an interrupt handler - we can't call any virtual method from this method // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); - - if ((now - sensor->last_edge_candidate_us_) >= sensor->filter_us_) { - sensor->last_edge_candidate_us_ = now; - sensor->set_->last_detected_edge_us_ = now; - sensor->set_->count_++; + auto &state = sensor->edge_state_; + auto &set = *sensor->set_; + + if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) { + state.last_sent_edge_us_ = now; + set.last_detected_edge_us_ = now; + set.last_rising_edge_us_ = now; + set.count_++; } } @@ -91,33 +136,27 @@ void IRAM_ATTR PulseMeterSensor::pulse_intr(PulseMeterSensor *sensor) { // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); const bool pin_val = sensor->isr_pin_.digital_read(); + auto &state = sensor->pulse_state_; + auto &set = *sensor->set_; + + // Filter length has passed since the last interrupt + const bool length = now - state.last_intr_ >= sensor->filter_us_; + + if (length && state.latched_ && !state.last_pin_val_) { // Long enough low edge + state.latched_ = false; + } else if (length && !state.latched_ && state.last_pin_val_) { // Long enough high edge + state.latched_ = true; + set.last_detected_edge_us_ = state.last_intr_; + set.count_++; + } - // A pulse occurred faster than we can detect - if (sensor->last_pin_val_ == pin_val) { - // If we haven't reached the filter length yet we need to reset our last_intr_ to now - // otherwise we can consider this noise as the "pulse" was certainly less than filter_us_ - if (now - sensor->last_intr_ < sensor->filter_us_) { - sensor->last_intr_ = now; - } - } else { - // Check if the last interrupt was long enough in the past - if (now - sensor->last_intr_ > sensor->filter_us_) { - // High pulse of filter length now falling (therefore last_intr_ was the rising edge) - if (!sensor->in_pulse_ && sensor->last_pin_val_) { - sensor->last_edge_candidate_us_ = sensor->last_intr_; - sensor->in_pulse_ = true; - } - // Low pulse of filter length now rising (therefore last_intr_ was the falling edge) - else if (sensor->in_pulse_ && !sensor->last_pin_val_) { - sensor->set_->last_detected_edge_us_ = sensor->last_edge_candidate_us_; - sensor->set_->count_++; - sensor->in_pulse_ = false; - } - } + // Due to order of operations this includes + // length && latched && rising (just reset from a long low edge) + // !latched && (rising || high) (noise on the line resetting the potential rising edge) + set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_; - sensor->last_intr_ = now; - sensor->last_pin_val_ = pin_val; - } + state.last_intr_ = now; + state.last_pin_val_ = pin_val; } } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index ddd42c2ed52f..76c4a35f03b0 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -5,6 +5,8 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include + namespace esphome { namespace pulse_meter { @@ -20,7 +22,8 @@ class PulseMeterSensor : public sensor::Sensor, public Component { void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; } - void set_total_pulses(uint32_t pulses) { this->total_pulses_ = pulses; } + + void set_total_pulses(uint32_t pulses); void setup() override; void loop() override; @@ -38,7 +41,9 @@ class PulseMeterSensor : public sensor::Sensor, public Component { InternalFilterMode filter_mode_{FILTER_EDGE}; // Variables used in the loop - bool initialized_ = false; + enum class MeterState { INITIAL, RUNNING, TIMED_OUT }; + MeterState meter_state_ = MeterState::INITIAL; + bool peeked_edge_ = false; uint32_t total_pulses_ = 0; uint32_t last_processed_edge_us_ = 0; @@ -49,6 +54,7 @@ class PulseMeterSensor : public sensor::Sensor, public Component { // (except for resetting the values) struct State { uint32_t last_detected_edge_us_ = 0; + uint32_t last_rising_edge_us_ = 0; uint32_t count_ = 0; }; State state_[2]; @@ -57,10 +63,20 @@ class PulseMeterSensor : public sensor::Sensor, public Component { // Only use these variables in the ISR ISRInternalGPIOPin isr_pin_; - uint32_t last_edge_candidate_us_ = 0; - uint32_t last_intr_ = 0; - bool in_pulse_ = false; - bool last_pin_val_ = false; + + /// Filter state for edge mode + struct EdgeState { + uint32_t last_sent_edge_us_ = 0; + }; + EdgeState edge_state_{}; + + /// Filter state for pulse mode + struct PulseState { + uint32_t last_intr_ = 0; + bool latched_ = false; + bool last_pin_val_ = false; + }; + PulseState pulse_state_{}; }; } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 26bc6b189b77..59ffa58c21a9 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -19,7 +19,7 @@ ) from esphome.core import CORE -CODEOWNERS = ["@stevebaxter", "@cstaahl"] +CODEOWNERS = ["@stevebaxter", "@cstaahl", "@TrentHouliston"] pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") diff --git a/esphome/components/pvvx_mithermometer/display/__init__.py b/esphome/components/pvvx_mithermometer/display/__init__.py index d93563893396..70c568c1e373 100644 --- a/esphome/components/pvvx_mithermometer/display/__init__.py +++ b/esphome/components/pvvx_mithermometer/display/__init__.py @@ -38,7 +38,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) await ble_client.register_ble_node(var, config) cg.add(var.set_disconnect_delay(config[CONF_DISCONNECT_DELAY].total_milliseconds)) diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 384537e5d706..1856a023cc3b 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -13,8 +13,10 @@ void PVVXDisplay::dump_config() { ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str()); ESP_LOGCONFIG(TAG, " Characteristic UUID : %s", this->char_uuid_.to_string().c_str()); ESP_LOGCONFIG(TAG, " Auto clear : %s", YESNO(this->auto_clear_enabled_)); +#ifdef USE_TIME ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); - ESP_LOGCONFIG(TAG, " Disconnect delay : %dms", this->disconnect_delay_ms_); +#endif + ESP_LOGCONFIG(TAG, " Disconnect delay : %" PRIu32 "ms", this->disconnect_delay_ms_); LOG_UPDATE_INTERVAL(this); } @@ -22,8 +24,10 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_ble_gattc_cb_param_t *param) { switch (event) { case ESP_GATTC_OPEN_EVT: - ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str()); - this->delayed_disconnect_(); + if (param->open.status == ESP_GATT_OK) { + ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str()); + this->delayed_disconnect_(); + } break; case ESP_GATTC_DISCONNECT_EVT: ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str()); @@ -139,7 +143,11 @@ void PVVXDisplay::sync_time_() { } time.recalc_timestamp_utc(true); // calculate timestamp of local time uint8_t blk[5] = {}; +#if ESP_IDF_VERSION_MAJOR >= 5 + ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str().c_str(), time.timestamp); +#else ESP_LOGD(TAG, "[%s] Sync time with timestamp %lu.", this->parent_->address_str().c_str(), time.timestamp); +#endif blk[0] = 0x23; blk[1] = time.timestamp & 0xff; blk[2] = (time.timestamp >> 8) & 0xff; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index c7e7cc04fb99..dfeb49c49d44 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -4,6 +4,8 @@ #include "esphome/core/defines.h" #include "esphome/components/ble_client/ble_client.h" +#include + #ifdef USE_ESP32 #include #ifdef USE_TIME diff --git a/esphome/components/pylontech/__init__.py b/esphome/components/pylontech/__init__.py new file mode 100644 index 000000000000..197f7e7904a2 --- /dev/null +++ b/esphome/components/pylontech/__init__.py @@ -0,0 +1,46 @@ +import logging +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +_LOGGER = logging.getLogger(__name__) + +CODEOWNERS = ["@functionpointer"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +CONF_PYLONTECH_ID = "pylontech_id" +CONF_BATTERY = "battery" + +pylontech_ns = cg.esphome_ns.namespace("pylontech") +PylontechComponent = pylontech_ns.class_( + "PylontechComponent", cg.PollingComponent, uart.UARTDevice +) +PylontechBattery = pylontech_ns.class_("PylontechBattery") + +CV_NUM_BATTERIES = cv.int_range(1, 16) + +PYLONTECH_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_PYLONTECH_ID): cv.use_id(PylontechComponent), + cv.Required(CONF_BATTERY): CV_NUM_BATTERIES, + } +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PylontechComponent), + } + ) + .extend(cv.polling_component_schema("60s")) + .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) diff --git a/esphome/components/pylontech/pylontech.cpp b/esphome/components/pylontech/pylontech.cpp new file mode 100644 index 000000000000..b33f4d487472 --- /dev/null +++ b/esphome/components/pylontech/pylontech.cpp @@ -0,0 +1,104 @@ +#include "pylontech.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace pylontech { + +static const char *const TAG = "pylontech"; +static const int MAX_DATA_LENGTH_BYTES = 256; +static const uint8_t ASCII_LF = 0x0A; + +PylontechComponent::PylontechComponent() {} + +void PylontechComponent::dump_config() { + this->check_uart_settings(115200, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); + ESP_LOGCONFIG(TAG, "pylontech:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Connection with pylontech failed!"); + } + + for (PylontechListener *listener : this->listeners_) { + listener->dump_config(); + } + + LOG_UPDATE_INTERVAL(this); +} + +void PylontechComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up pylontech..."); + while (this->available() != 0) { + this->read(); + } +} + +void PylontechComponent::update() { this->write_str("pwr\n"); } + +void PylontechComponent::loop() { + if (this->available() > 0) { + // pylontech sends a lot of data very suddenly + // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow + uint8_t data; + int recv = 0; + while (this->available() > 0) { + if (this->read_byte(&data)) { + buffer_[buffer_index_write_] += (char) data; + recv++; + if (buffer_[buffer_index_write_].back() == static_cast(ASCII_LF) || + buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { + // complete line received + buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; + } + } + } + ESP_LOGV(TAG, "received %d bytes", recv); + } else { + // only process one line per call of loop() to not block esphome for too long + if (buffer_index_read_ != buffer_index_write_) { + this->process_line_(buffer_[buffer_index_read_]); + buffer_[buffer_index_read_].clear(); + buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS; + } + } +} + +void PylontechComponent::process_line_(std::string &buffer) { + ESP_LOGV(TAG, "Read from serial: %s", buffer.substr(0, buffer.size() - 2).c_str()); + // clang-format off + // example line to parse: + // Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St MosTempr M.T.St + // 1 50548 8910 25000 24200 25000 3368 3371 Charge Normal Normal Normal 97% 2021-06-30 20:49:45 Normal Normal 22700 Normal + // clang-format on + + PylontechListener::LineContents l{}; + char mostempr_s[6]; + const int parsed = sscanf( // NOLINT + buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %5s %*s", // NOLINT + &l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT + l.curr_st, l.temp_st, &l.coulomb, mostempr_s); // NOLINT + + if (l.bat_num <= 0) { + ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str()); + return; + } + if (parsed != 14) { + ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str()); + return; + } + auto mostempr_parsed = parse_number(mostempr_s); + if (mostempr_parsed.has_value()) { + l.mostempr = mostempr_parsed.value(); + } else { + l.mostempr = -300; + ESP_LOGW(TAG, "bat_num %d: received no mostempr", l.bat_num); + } + + for (PylontechListener *listener : this->listeners_) { + listener->on_line_read(&l); + } +} + +float PylontechComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pylontech/pylontech.h b/esphome/components/pylontech/pylontech.h new file mode 100644 index 000000000000..3282cb4d9fb6 --- /dev/null +++ b/esphome/components/pylontech/pylontech.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace pylontech { + +static const uint8_t NUM_BUFFERS = 20; +static const uint8_t TEXT_SENSOR_MAX_LEN = 8; + +class PylontechListener { + public: + struct LineContents { + int bat_num = 0, volt, curr, tempr, tlow, thigh, vlow, vhigh, coulomb, mostempr; + char base_st[TEXT_SENSOR_MAX_LEN], volt_st[TEXT_SENSOR_MAX_LEN], curr_st[TEXT_SENSOR_MAX_LEN], + temp_st[TEXT_SENSOR_MAX_LEN]; + }; + + virtual void on_line_read(LineContents *line); + virtual void dump_config(); +}; + +class PylontechComponent : public PollingComponent, public uart::UARTDevice { + public: + PylontechComponent(); + + /// Schedule data readings. + void update() override; + /// Read data once available + void loop() override; + /// Setup the sensor and test for a connection. + void setup() override; + void dump_config() override; + + float get_setup_priority() const override; + + void register_listener(PylontechListener *listener) { this->listeners_.push_back(listener); } + + protected: + void process_line_(std::string &buffer); + + // ring buffer + std::string buffer_[NUM_BUFFERS]; + int buffer_index_write_ = 0; + int buffer_index_read_ = 0; + + std::vector listeners_{}; +}; + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pylontech/sensor/__init__.py b/esphome/components/pylontech/sensor/__init__.py new file mode 100644 index 000000000000..a1477c627f4e --- /dev/null +++ b/esphome/components/pylontech/sensor/__init__.py @@ -0,0 +1,97 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_VOLTAGE, + CONF_CURRENT, + CONF_TEMPERATURE, + UNIT_VOLT, + UNIT_AMPERE, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_BATTERY, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_ID, +) + +from .. import ( + CONF_PYLONTECH_ID, + PYLONTECH_COMPONENT_SCHEMA, + CONF_BATTERY, + pylontech_ns, +) + +PylontechSensor = pylontech_ns.class_("PylontechSensor", cg.Component) + +CONF_COULOMB = "coulomb" +CONF_TEMPERATURE_LOW = "temperature_low" +CONF_TEMPERATURE_HIGH = "temperature_high" +CONF_VOLTAGE_LOW = "voltage_low" +CONF_VOLTAGE_HIGH = "voltage_high" +CONF_MOS_TEMPERATURE = "mos_temperature" + +TYPES: dict[str, cv.Schema] = { + CONF_VOLTAGE: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_CURRENT: sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + ), + CONF_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + CONF_TEMPERATURE_LOW: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + CONF_TEMPERATURE_HIGH: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + CONF_VOLTAGE_LOW: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_VOLTAGE_HIGH: sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + ), + CONF_COULOMB: sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + ), + CONF_MOS_TEMPERATURE: sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), +} + +CONFIG_SCHEMA = PYLONTECH_COMPONENT_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(PylontechSensor)} +).extend({cv.Optional(marker): schema for marker, schema in TYPES.items()}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PYLONTECH_ID]) + bat = cg.new_Pvariable(config[CONF_ID], config[CONF_BATTERY]) + + for marker in TYPES: + if marker_config := config.get(marker): + sens = await sensor.new_sensor(marker_config) + cg.add(getattr(bat, f"set_{marker}_sensor")(sens)) + + cg.add(paren.register_listener(bat)) diff --git a/esphome/components/pylontech/sensor/pylontech_sensor.cpp b/esphome/components/pylontech/sensor/pylontech_sensor.cpp new file mode 100644 index 000000000000..5b5db0731e44 --- /dev/null +++ b/esphome/components/pylontech/sensor/pylontech_sensor.cpp @@ -0,0 +1,60 @@ +#include "pylontech_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pylontech { + +static const char *const TAG = "pylontech.sensor"; + +PylontechSensor::PylontechSensor(int8_t bat_num) { this->bat_num_ = bat_num; } + +void PylontechSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Pylontech Sensor:"); + ESP_LOGCONFIG(TAG, " Battery %d", this->bat_num_); + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Temperature low", this->temperature_low_sensor_); + LOG_SENSOR(" ", "Temperature high", this->temperature_high_sensor_); + LOG_SENSOR(" ", "Voltage low", this->voltage_low_sensor_); + LOG_SENSOR(" ", "Voltage high", this->voltage_high_sensor_); + LOG_SENSOR(" ", "Coulomb", this->coulomb_sensor_); + LOG_SENSOR(" ", "MOS Temperature", this->mos_temperature_sensor_); +} + +void PylontechSensor::on_line_read(PylontechListener::LineContents *line) { + if (this->bat_num_ != line->bat_num) { + return; + } + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(((float) line->volt) / 1000.0f); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(((float) line->curr) / 1000.0f); + } + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(((float) line->tempr) / 1000.0f); + } + if (this->temperature_low_sensor_ != nullptr) { + this->temperature_low_sensor_->publish_state(((float) line->tlow) / 1000.0f); + } + if (this->temperature_high_sensor_ != nullptr) { + this->temperature_high_sensor_->publish_state(((float) line->thigh) / 1000.0f); + } + if (this->voltage_low_sensor_ != nullptr) { + this->voltage_low_sensor_->publish_state(((float) line->vlow) / 1000.0f); + } + if (this->voltage_high_sensor_ != nullptr) { + this->voltage_high_sensor_->publish_state(((float) line->vhigh) / 1000.0f); + } + if (this->coulomb_sensor_ != nullptr) { + this->coulomb_sensor_->publish_state(line->coulomb); + } + if (this->mos_temperature_sensor_ != nullptr) { + this->mos_temperature_sensor_->publish_state(((float) line->mostempr) / 1000.0f); + } +} + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pylontech/sensor/pylontech_sensor.h b/esphome/components/pylontech/sensor/pylontech_sensor.h new file mode 100644 index 000000000000..8986adc26cd3 --- /dev/null +++ b/esphome/components/pylontech/sensor/pylontech_sensor.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../pylontech.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace pylontech { + +class PylontechSensor : public PylontechListener, public Component { + public: + PylontechSensor(int8_t bat_num); + void dump_config() override; + + SUB_SENSOR(voltage) + SUB_SENSOR(current) + SUB_SENSOR(temperature) + SUB_SENSOR(temperature_low) + SUB_SENSOR(temperature_high) + SUB_SENSOR(voltage_low) + SUB_SENSOR(voltage_high) + + SUB_SENSOR(coulomb) + SUB_SENSOR(mos_temperature) + + void on_line_read(LineContents *line) override; + + protected: + int8_t bat_num_; +}; + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pylontech/text_sensor/__init__.py b/esphome/components/pylontech/text_sensor/__init__.py new file mode 100644 index 000000000000..d6ccc678f82c --- /dev/null +++ b/esphome/components/pylontech/text_sensor/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ID + +from .. import ( + CONF_PYLONTECH_ID, + PYLONTECH_COMPONENT_SCHEMA, + CONF_BATTERY, + pylontech_ns, +) + +PylontechTextSensor = pylontech_ns.class_("PylontechTextSensor", cg.Component) + +CONF_BASE_STATE = "base_state" +CONF_VOLTAGE_STATE = "voltage_state" +CONF_CURRENT_STATE = "current_state" +CONF_TEMPERATURE_STATE = "temperature_state" + +MARKERS: list[str] = [ + CONF_BASE_STATE, + CONF_VOLTAGE_STATE, + CONF_CURRENT_STATE, + CONF_TEMPERATURE_STATE, +] + +CONFIG_SCHEMA = PYLONTECH_COMPONENT_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(PylontechTextSensor)} +).extend({cv.Optional(marker): text_sensor.text_sensor_schema() for marker in MARKERS}) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PYLONTECH_ID]) + bat = cg.new_Pvariable(config[CONF_ID], config[CONF_BATTERY]) + + for marker in MARKERS: + if marker_config := config.get(marker): + var = await text_sensor.new_text_sensor(marker_config) + cg.add(getattr(bat, f"set_{marker}_text_sensor")(var)) + + cg.add(paren.register_listener(bat)) diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp new file mode 100644 index 000000000000..9e894bc5706e --- /dev/null +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp @@ -0,0 +1,40 @@ +#include "pylontech_text_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pylontech { + +static const char *const TAG = "pylontech.textsensor"; + +PylontechTextSensor::PylontechTextSensor(int8_t bat_num) { this->bat_num_ = bat_num; } + +void PylontechTextSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Pylontech Text Sensor:"); + ESP_LOGCONFIG(TAG, " Battery %d", this->bat_num_); + LOG_TEXT_SENSOR(" ", "Base state", this->base_state_text_sensor_); + LOG_TEXT_SENSOR(" ", "Voltage state", this->voltage_state_text_sensor_); + LOG_TEXT_SENSOR(" ", "Current state", this->current_state_text_sensor_); + LOG_TEXT_SENSOR(" ", "Temperature state", this->temperature_state_text_sensor_); +} + +void PylontechTextSensor::on_line_read(PylontechListener::LineContents *line) { + if (this->bat_num_ != line->bat_num) { + return; + } + if (this->base_state_text_sensor_ != nullptr) { + this->base_state_text_sensor_->publish_state(std::string(line->base_st)); + } + if (this->voltage_state_text_sensor_ != nullptr) { + this->voltage_state_text_sensor_->publish_state(std::string(line->volt_st)); + } + if (this->current_state_text_sensor_ != nullptr) { + this->current_state_text_sensor_->publish_state(std::string(line->curr_st)); + } + if (this->temperature_state_text_sensor_ != nullptr) { + this->temperature_state_text_sensor_->publish_state(std::string(line->temp_st)); + } +} + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.h b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.h new file mode 100644 index 000000000000..a685512ed55f --- /dev/null +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../pylontech.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace pylontech { + +class PylontechTextSensor : public PylontechListener, public Component { + public: + PylontechTextSensor(int8_t bat_num); + void dump_config() override; + + SUB_TEXT_SENSOR(base_state) + SUB_TEXT_SENSOR(voltage_state) + SUB_TEXT_SENSOR(current_state) + SUB_TEXT_SENSOR(temperature_state) + + void on_line_read(LineContents *line) override; + + protected: + int8_t bat_num_; +}; + +} // namespace pylontech +} // namespace esphome diff --git a/esphome/components/pzem004t/pzem004t.cpp b/esphome/components/pzem004t/pzem004t.cpp index e5418765bdf2..35b66b03f271 100644 --- a/esphome/components/pzem004t/pzem004t.cpp +++ b/esphome/components/pzem004t/pzem004t.cpp @@ -1,5 +1,6 @@ #include "pzem004t.h" #include "esphome/core/log.h" +#include namespace esphome { namespace pzem004t { @@ -75,7 +76,7 @@ void PZEM004T::loop() { uint32_t energy = (uint32_t(resp[1]) << 16) | (uint32_t(resp[2]) << 8) | (uint32_t(resp[3])); if (this->energy_sensor_ != nullptr) this->energy_sensor_->publish_state(energy); - ESP_LOGD(TAG, "Got Energy %u Wh", energy); + ESP_LOGD(TAG, "Got Energy %" PRIu32 " Wh", energy); this->write_state_(DONE); break; } diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index f03b6af1912f..49a67d4e0960 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -1,4 +1,5 @@ #include "qmc5883l.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include @@ -59,6 +60,10 @@ void QMC5883LComponent::setup() { this->mark_failed(); return; } + + if (this->get_update_interval() < App.get_loop_interval()) { + high_freq_.start(); + } } void QMC5883LComponent::dump_config() { ESP_LOGCONFIG(TAG, "QMC5883L:"); @@ -72,12 +77,15 @@ void QMC5883LComponent::dump_config() { LOG_SENSOR(" ", "Y Axis", this->y_sensor_); LOG_SENSOR(" ", "Z Axis", this->z_sensor_); LOG_SENSOR(" ", "Heading", this->heading_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } void QMC5883LComponent::update() { uint8_t status = false; this->read_byte(QMC5883L_REGISTER_STATUS, &status); + // Always request X,Y,Z regardless if there are sensors for them + // to avoid https://github.com/esphome/issues/issues/5731 uint16_t raw_x, raw_y, raw_z; if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || !this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || @@ -104,7 +112,19 @@ void QMC5883LComponent::update() { const float z = int16_t(raw_z) * mg_per_bit * 0.1f; float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; - ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° status=%u", x, y, z, heading, status); + + float temp = NAN; + if (this->temperature_sensor_ != nullptr) { + uint16_t raw_temp; + if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { + this->status_set_warning(); + return; + } + temp = int16_t(raw_temp) * 0.01f; + } + + ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° temperature=%0.01f°C status=%u", x, y, z, heading, + temp, status); if (this->x_sensor_ != nullptr) this->x_sensor_->publish_state(x); @@ -114,6 +134,8 @@ void QMC5883LComponent::update() { this->z_sensor_->publish_state(z); if (this->heading_sensor_ != nullptr) this->heading_sensor_->publish_state(heading); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temp); } bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index 15ef435ce57d..dd2008d45312 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -40,6 +40,7 @@ class QMC5883LComponent : 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_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } protected: QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; @@ -49,11 +50,13 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *y_sensor_{nullptr}; sensor::Sensor *z_sensor_{nullptr}; sensor::Sensor *heading_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, } error_code_; bool read_byte_16_(uint8_t a_register, uint16_t *data); + HighFrequencyLoopRequester high_freq_; }; } // namespace qmc5883l diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index cce4d93843aa..341c0c3f8a32 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -3,12 +3,19 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ADDRESS, + CONF_FIELD_STRENGTH_X, + CONF_FIELD_STRENGTH_Y, + CONF_FIELD_STRENGTH_Z, + CONF_HEADING, + CONF_TEMPERATURE, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, + DEVICE_CLASS_TEMPERATURE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, + UNIT_CELSIUS, UNIT_DEGREES, ICON_SCREEN_ROTATION, CONF_UPDATE_INTERVAL, @@ -18,11 +25,6 @@ qmc5883l_ns = cg.esphome_ns.namespace("qmc5883l") -CONF_FIELD_STRENGTH_X = "field_strength_x" -CONF_FIELD_STRENGTH_Y = "field_strength_y" -CONF_FIELD_STRENGTH_Z = "field_strength_z" -CONF_HEADING = "heading" - QMC5883LComponent = qmc5883l_ns.class_( "QMC5883LComponent", cg.PollingComponent, i2c.I2CDevice ) @@ -79,6 +81,12 @@ def validate_enum_bound(value): icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, ) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, +) CONFIG_SCHEMA = ( cv.Schema( @@ -95,6 +103,7 @@ def validate_enum_bound(value): 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_TEMPERATURE): temperature_schema, } ) .extend(cv.polling_component_schema("60s")) @@ -131,3 +140,6 @@ 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_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp index aecf7628dc2e..b60e60a4b0a5 100644 --- a/esphome/components/qr_code/qr_code.cpp +++ b/esphome/components/qr_code/qr_code.cpp @@ -51,5 +51,17 @@ void QrCode::draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, } } } + +uint8_t QrCode::get_size() { + if (this->needs_update_) { + this->generate_qr_code(); + this->needs_update_ = false; + } + + uint8_t size = qrcodegen_getSize(this->qr_); + + return size; +} + } // namespace qr_code } // namespace esphome diff --git a/esphome/components/qr_code/qr_code.h b/esphome/components/qr_code/qr_code.h index d88e0aa09af1..ab4c587b6de2 100644 --- a/esphome/components/qr_code/qr_code.h +++ b/esphome/components/qr_code/qr_code.h @@ -24,6 +24,8 @@ class QrCode : public Component { void generate_qr_code(); + uint8_t get_size(); + protected: std::string value_; qrcodegen_Ecc ecc_; diff --git a/esphome/components/qspi_amoled/__init__.py b/esphome/components/qspi_amoled/__init__.py new file mode 100644 index 000000000000..c58ce8a01e84 --- /dev/null +++ b/esphome/components/qspi_amoled/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/qspi_amoled/display.py b/esphome/components/qspi_amoled/display.py new file mode 100644 index 000000000000..84bf9553cbb3 --- /dev/null +++ b/esphome/components/qspi_amoled/display.py @@ -0,0 +1,131 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import ( + spi, + display, +) +from esphome.const import ( + CONF_RESET_PIN, + CONF_ID, + CONF_DIMENSIONS, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_ENABLE_PIN, + CONF_MODEL, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_SWAP_XY, + CONF_COLOR_ORDER, + CONF_TRANSFORM, +) + +DEPENDENCIES = ["spi"] + +qspi_amoled_ns = cg.esphome_ns.namespace("qspi_amoled") +QSPI_AMOLED = qspi_amoled_ns.class_( + "QspiAmoLed", display.Display, display.DisplayBuffer, cg.Component, spi.SPIDevice +) +ColorOrder = display.display_ns.enum("ColorMode") +Model = qspi_amoled_ns.enum("Model") + +MODELS = {"RM690B0": Model.RM690B0, "RM67162": Model.RM67162} + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QSPI_AMOLED), + cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + } + ), + cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( + COLOR_ORDERS, upper=True + ), + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( + 0, 0xFF, min_included=True, max_included=True + ), + } + ).extend( + spi.spi_device_schema( + cs_pin_required=False, + default_mode="MODE0", + default_data_rate=10e6, + quad=True, + ) + ) + ), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + await spi.register_spi_device(var, config) + + cg.add(var.set_color_mode(config[CONF_COLOR_ORDER])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) + cg.add(var.set_model(config[CONF_MODEL])) + if enable_pin := config.get(CONF_ENABLE_PIN): + enable = await cg.gpio_pin_expression(enable_pin) + cg.add(var.set_enable_pin(enable)) + + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if transform := config.get(CONF_TRANSFORM): + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) + + if CONF_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/qspi_amoled/qspi_amoled.cpp b/esphome/components/qspi_amoled/qspi_amoled.cpp new file mode 100644 index 000000000000..697989e8613c --- /dev/null +++ b/esphome/components/qspi_amoled/qspi_amoled.cpp @@ -0,0 +1,165 @@ +#ifdef USE_ESP_IDF +#include "qspi_amoled.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace qspi_amoled { + +void QspiAmoLed::setup() { + esph_log_config(TAG, "Setting up QSPI_AMOLED"); + this->spi_setup(); + if (this->enable_pin_ != nullptr) { + this->enable_pin_->setup(); + this->enable_pin_->digital_write(true); + } + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(5); + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + } + this->set_timeout(120, [this] { this->write_command_(SLEEP_OUT); }); + this->set_timeout(240, [this] { this->write_init_sequence_(); }); +} + +void QspiAmoLed::update() { + this->do_update_(); + int w = this->x_high_ - this->x_low_ + 1; + int h = this->y_high_ - this->y_low_ + 1; + this->draw_pixels_at(this->x_low_, this->y_low_, w, h, this->buffer_, this->color_mode_, display::COLOR_BITNESS_565, + true, this->x_low_, this->y_low_, this->get_width_internal() - w - this->x_low_); + // invalidate watermarks + this->x_low_ = this->width_; + this->y_low_ = this->height_; + this->x_high_ = 0; + this->y_high_ = 0; +} + +void QspiAmoLed::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + return; + } + if (this->buffer_ == nullptr) + this->init_internal_(this->width_ * this->height_ * 2); + if (this->is_failed()) + return; + uint32_t pos = (y * this->width_) + x; + uint16_t new_color; + bool updated = false; + pos = pos * 2; + new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB); + if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) { + this->buffer_[pos] = (uint8_t) (new_color >> 8); + updated = true; + } + pos = pos + 1; + new_color = new_color & 0xFF; + + if (this->buffer_[pos] != new_color) { + this->buffer_[pos] = new_color; + updated = true; + } + if (updated) { + // low and high watermark may speed up drawing from buffer + if (x < this->x_low_) + this->x_low_ = x; + if (y < this->y_low_) + this->y_low_ = y; + if (x > this->x_high_) + this->x_high_ = x; + if (y > this->y_high_) + this->y_high_ = y; + } +} + +void QspiAmoLed::reset_params_(bool ready) { + if (!ready && !this->is_ready()) + return; + this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); + // custom x/y transform and color order + uint8_t mad = this->color_mode_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB; + if (this->swap_xy_) + mad |= MADCTL_MV; + if (this->mirror_x_) + mad |= MADCTL_MX; + if (this->mirror_y_) + mad |= MADCTL_MY; + this->write_command_(MADCTL_CMD, &mad, 1); + this->write_command_(BRIGHTNESS, &this->brightness_, 1); +} + +void QspiAmoLed::write_init_sequence_() { + if (this->model_ == RM690B0) { + this->write_command_(PAGESEL, 0x20); + this->write_command_(MIPI, 0x0A); + this->write_command_(WRAM, 0x80); + this->write_command_(SWIRE1, 0x51); + this->write_command_(SWIRE2, 0x2E); + this->write_command_(PAGESEL, 0x00); + this->write_command_(0xC2, 0x00); + delay(10); + this->write_command_(TEON, 0x00); + } + this->write_command_(PIXFMT, 0x55); + this->write_command_(BRIGHTNESS, 0); + this->write_command_(DISPLAY_ON); + this->reset_params_(true); + this->setup_complete_ = true; + esph_log_config(TAG, "QSPI_AMOLED setup complete"); +} + +void QspiAmoLed::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { + uint8_t buf[4]; + x1 += this->offset_x_; + x2 += this->offset_x_; + y1 += this->offset_y_; + y2 += this->offset_y_; + put16_be(buf, x1); + put16_be(buf + 2, x2); + this->write_command_(CASET, buf, sizeof buf); + put16_be(buf, y1); + put16_be(buf + 2, y2); + this->write_command_(RASET, buf, sizeof buf); +} + +void QspiAmoLed::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) { + if (!this->setup_complete_ || this->is_failed()) + return; + if (w <= 0 || h <= 0) + return; + if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ || + big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); + this->enable(); + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); + } else { + this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4); + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); + } + } + this->disable(); +} + +void QspiAmoLed::dump_config() { + ESP_LOGCONFIG("", "QSPI AMOLED"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + 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_amoled +} // namespace esphome +#endif diff --git a/esphome/components/qspi_amoled/qspi_amoled.h b/esphome/components/qspi_amoled/qspi_amoled.h new file mode 100644 index 000000000000..28d243f54859 --- /dev/null +++ b/esphome/components/qspi_amoled/qspi_amoled.h @@ -0,0 +1,165 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +#ifdef USE_ESP_IDF +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display_color_utils.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace qspi_amoled { + +constexpr static const char *const TAG = "display.qspi_amoled"; +static const uint8_t SW_RESET_CMD = 0x01; +static const uint8_t SLEEP_OUT = 0x11; +static const uint8_t INVERT_OFF = 0x20; +static const uint8_t INVERT_ON = 0x21; +static const uint8_t ALL_ON = 0x23; +static const uint8_t WRAM = 0x24; +static const uint8_t MIPI = 0x26; +static const uint8_t DISPLAY_ON = 0x29; +static const uint8_t RASET = 0x2B; +static const uint8_t CASET = 0x2A; +static const uint8_t WDATA = 0x2C; +static const uint8_t TEON = 0x35; +static const uint8_t MADCTL_CMD = 0x36; +static const uint8_t PIXFMT = 0x3A; +static const uint8_t BRIGHTNESS = 0x51; +static const uint8_t SWIRE1 = 0x5A; +static const uint8_t SWIRE2 = 0x5B; +static const uint8_t PAGESEL = 0xFE; + +static const uint8_t MADCTL_MY = 0x80; ///< Bit 7 Bottom to top +static const uint8_t MADCTL_MX = 0x40; ///< Bit 6 Right to left +static const uint8_t MADCTL_MV = 0x20; ///< Bit 5 Reverse Mode +static const uint8_t MADCTL_RGB = 0x00; ///< Bit 3 Red-Green-Blue pixel order +static const uint8_t MADCTL_BGR = 0x08; ///< Bit 3 Blue-Green-Red pixel order + +// store a 16 bit value in a buffer, big endian. +static inline void put16_be(uint8_t *buf, uint16_t value) { + buf[0] = value >> 8; + buf[1] = value; +} + +enum Model { + RM690B0, + RM67162, +}; + +class QspiAmoLed : public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_model(Model model) { this->model_ = model; } + void update() override; + void setup() override; + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_enable_pin(GPIOPin *enable_pin) { this->enable_pin_ = enable_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_invert_colors(bool invert_colors) { + this->invert_colors_ = invert_colors; + this->reset_params_(); + } + void set_mirror_x(bool mirror_x) { + this->mirror_x_ = mirror_x; + this->reset_params_(); + } + void set_mirror_y(bool mirror_y) { + this->mirror_y_ = mirror_y; + this->reset_params_(); + } + void set_swap_xy(bool swap_xy) { + this->swap_xy_ = swap_xy; + this->reset_params_(); + } + void set_brightness(uint8_t brightness) { + this->brightness_ = brightness; + this->reset_params_(); + } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void dump_config() override; + + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + bool can_proceed() override { return this->setup_complete_; } + + protected: + void draw_absolute_pixel_internal(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; + /** + * the RM67162 in quad SPI mode seems to work like this (not in the datasheet, this is deduced from the + * sample code.) + * + * Immediately after enabling /CS send 4 bytes in single-dataline SPI mode: + * 0: either 0x2 or 0x32. The first indicates that any subsequent data bytes after the initial 4 will be + * sent in 1-dataline SPI. The second indicates quad mode. + * 1: 0x00 + * 2: The command (register address) byte. + * 3: 0x00 + * + * This is followed by zero or more data bytes in either 1-wire or 4-wire mode, depending on the first byte. + * At the conclusion of the write, de-assert /CS. + * + * @param cmd + * @param bytes + * @param len + */ + void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { + this->enable(); + this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); + this->disable(); + } + + void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); } + void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); } + void reset_params_(bool ready = false); + void write_init_sequence_(); + void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + + GPIOPin *reset_pin_{nullptr}; + GPIOPin *enable_pin_{nullptr}; + uint16_t x_low_{0}; + uint16_t y_low_{0}; + uint16_t x_high_{0}; + uint16_t y_high_{0}; + bool setup_complete_{}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + bool swap_xy_{}; + bool mirror_x_{}; + bool mirror_y_{}; + uint8_t brightness_{0xD0}; + Model model_{RM690B0}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace qspi_amoled +} // namespace esphome +#endif diff --git a/esphome/components/qwiic_pir/__init__.py b/esphome/components/qwiic_pir/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/qwiic_pir/binary_sensor.py b/esphome/components/qwiic_pir/binary_sensor.py new file mode 100644 index 000000000000..360f8b506aa4 --- /dev/null +++ b/esphome/components/qwiic_pir/binary_sensor.py @@ -0,0 +1,67 @@ +from esphome import core +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, binary_sensor +from esphome.const import ( + CONF_DEBOUNCE, + DEVICE_CLASS_MOTION, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@kahrendt"] + +qwiic_pir_ns = cg.esphome_ns.namespace("qwiic_pir") + +DebounceMode = qwiic_pir_ns.enum("DebounceMode") +DEBOUNCE_MODE_OPTIONS = { + "RAW": DebounceMode.RAW_DEBOUNCE_MODE, + "NATIVE": DebounceMode.NATIVE_DEBOUNCE_MODE, + "HYBRID": DebounceMode.HYBRID_DEBOUNCE_MODE, +} + +CONF_DEBOUNCE_MODE = "debounce_mode" + +QwiicPIRComponent = qwiic_pir_ns.class_( + "QwiicPIRComponent", cg.Component, i2c.I2CDevice, binary_sensor.BinarySensor +) + + +def validate_no_debounce_unless_native(config): + if CONF_DEBOUNCE in config: + if config[CONF_DEBOUNCE_MODE] != "NATIVE": + raise cv.Invalid("debounce can only be set if debounce_mode is NATIVE") + return config + + +CONFIG_SCHEMA = cv.All( + binary_sensor.binary_sensor_schema( + QwiicPIRComponent, + device_class=DEVICE_CLASS_MOTION, + ) + .extend( + { + cv.Optional(CONF_DEBOUNCE): cv.All( + cv.time_period, + cv.Range(max=core.TimePeriod(milliseconds=65535)), + ), + cv.Optional(CONF_DEBOUNCE_MODE, default="HYBRID"): cv.enum( + DEBOUNCE_MODE_OPTIONS, upper=True + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x12)), + validate_no_debounce_unless_native, +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if debounce_time_setting := config.get(CONF_DEBOUNCE): + cg.add(var.set_debounce_time(debounce_time_setting.total_milliseconds)) + else: + cg.add(var.set_debounce_time(1)) # default to 1 ms if not configured + cg.add(var.set_debounce_mode(config[CONF_DEBOUNCE_MODE])) diff --git a/esphome/components/qwiic_pir/qwiic_pir.cpp b/esphome/components/qwiic_pir/qwiic_pir.cpp new file mode 100644 index 000000000000..c267554c4511 --- /dev/null +++ b/esphome/components/qwiic_pir/qwiic_pir.cpp @@ -0,0 +1,137 @@ +#include "qwiic_pir.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace qwiic_pir { + +static const char *const TAG = "qwiic_pir"; + +void QwiicPIRComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up Qwiic PIR..."); + + // Verify I2C communcation by reading and verifying the chip ID + uint8_t chip_id; + + if (!this->read_byte(QWIIC_PIR_CHIP_ID, &chip_id)) { + ESP_LOGE(TAG, "Failed to read the chip's ID"); + + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + + return; + } + + if (chip_id != QWIIC_PIR_DEVICE_ID) { + ESP_LOGE(TAG, "Unknown chip ID, is this a Qwiic PIR?"); + + this->error_code_ = ERROR_WRONG_CHIP_ID; + this->mark_failed(); + + return; + } + + if (!this->write_byte_16(QWIIC_PIR_DEBOUNCE_TIME, this->debounce_time_)) { + ESP_LOGE(TAG, "Failed to configure debounce time."); + + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + + return; + } + + if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) { + // Publish the starting raw state of the PIR sensor + // If NATIVE mode, the binary_sensor state would be unknown until a motion event + if (!this->read_byte(QWIIC_PIR_EVENT_STATUS, &this->event_register_.reg)) { + ESP_LOGE(TAG, "Failed to read initial sensor state."); + + this->error_code_ = ERROR_COMMUNICATION_FAILED; + this->mark_failed(); + + return; + } + + this->publish_state(this->event_register_.raw_reading); + } +} + +void QwiicPIRComponent::loop() { + // Read Event Register + if (!this->read_byte(QWIIC_PIR_EVENT_STATUS, &this->event_register_.reg)) { + ESP_LOGW(TAG, "Failed to communicate with sensor"); + + return; + } + + if (this->debounce_mode_ == HYBRID_DEBOUNCE_MODE) { + // Use a combination of the raw sensor reading and the device's event detection to determine state + // - The device is hardcoded to use a debounce time of 1 ms in this mode + // - Any event, even if it is object_removed, implies motion was active since the last loop, so publish true + // - Use ESPHome's built-in filters for debouncing + this->publish_state(this->event_register_.raw_reading || this->event_register_.event_available); + + if (this->event_register_.event_available) { + this->clear_events_(); + } + } else if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) { + // Uses the device's firmware to debounce the signal + // - Follows the logic of SparkFun's example implementation: + // https://github.com/sparkfun/SparkFun_Qwiic_PIR_Arduino_Library/blob/master/examples/Example2_PrintPIRStatus/Example2_PrintPIRStatus.ino + // (accessed July 2023) + // - Is unreliable at detecting an object being removed, especially at debounce rates even slightly large + if (this->event_register_.event_available) { + // If an object is detected, publish true + if (this->event_register_.object_detected) + this->publish_state(true); + + // If an object has been removed, publish false + if (this->event_register_.object_removed) + this->publish_state(false); + + this->clear_events_(); + } + } else if (this->debounce_mode_ == RAW_DEBOUNCE_MODE) { + // Publishes the raw PIR sensor reading with no further logic + // - May miss a very short motion detection if the ESP's loop time is slow + this->publish_state(this->event_register_.raw_reading); + } +} + +void QwiicPIRComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Qwiic PIR:"); + + if (this->debounce_mode_ == RAW_DEBOUNCE_MODE) { + ESP_LOGCONFIG(TAG, " Debounce Mode: RAW"); + } else if (this->debounce_mode_ == NATIVE_DEBOUNCE_MODE) { + ESP_LOGCONFIG(TAG, " Debounce Mode: NATIVE"); + ESP_LOGCONFIG(TAG, " Debounce Time: %ums", this->debounce_time_); + } else if (this->debounce_mode_ == HYBRID_DEBOUNCE_MODE) { + ESP_LOGCONFIG(TAG, " Debounce Mode: HYBRID"); + } + + switch (this->error_code_) { + case NONE: + break; + case ERROR_COMMUNICATION_FAILED: + ESP_LOGE(TAG, " Communication with Qwiic PIR failed!"); + break; + case ERROR_WRONG_CHIP_ID: + ESP_LOGE(TAG, " Qwiic PIR has wrong chip ID - please verify you are using a Qwiic PIR"); + break; + default: + ESP_LOGE(TAG, " Qwiic PIR error code %d", (int) this->error_code_); + break; + } + + LOG_I2C_DEVICE(this); + LOG_BINARY_SENSOR(" ", "Qwiic PIR Binary Sensor", this); +} + +void QwiicPIRComponent::clear_events_() { + // Clear event status register + if (!this->write_byte(QWIIC_PIR_EVENT_STATUS, 0x00)) + ESP_LOGW(TAG, "Failed to clear events on sensor"); +} + +} // namespace qwiic_pir +} // namespace esphome diff --git a/esphome/components/qwiic_pir/qwiic_pir.h b/esphome/components/qwiic_pir/qwiic_pir.h new file mode 100644 index 000000000000..d58d67734f8e --- /dev/null +++ b/esphome/components/qwiic_pir/qwiic_pir.h @@ -0,0 +1,70 @@ +/* + * Adds support for Qwiic PIR motion sensors that communicate over an I2C bus. + * These sensors use Sharp PIR motion sensors to detect motion. A firmware running on an ATTiny84 translates the digital + * output to I2C communications. + * ATTiny84 firmware: https://github.com/sparkfun/Qwiic_PIR (acccessed July 2023) + * SparkFun's Arduino library: https://github.com/sparkfun/SparkFun_Qwiic_PIR_Arduino_Library (accessed July 2023) + */ + +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace qwiic_pir { + +// Qwiic PIR I2C Register Addresses +enum { + QWIIC_PIR_CHIP_ID = 0x00, + QWIIC_PIR_EVENT_STATUS = 0x03, + QWIIC_PIR_DEBOUNCE_TIME = 0x05, // uint16_t debounce time in milliseconds +}; + +enum DebounceMode { + RAW_DEBOUNCE_MODE, + NATIVE_DEBOUNCE_MODE, + HYBRID_DEBOUNCE_MODE, +}; + +static const uint8_t QWIIC_PIR_DEVICE_ID = 0x72; + +class QwiicPIRComponent : public Component, public i2c::I2CDevice, public binary_sensor::BinarySensor { + public: + void setup() override; + void loop() override; + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_debounce_time(uint16_t debounce_time) { this->debounce_time_ = debounce_time; } + void set_debounce_mode(DebounceMode mode) { this->debounce_mode_ = mode; } + + protected: + uint16_t debounce_time_{}; + + DebounceMode debounce_mode_{}; + + enum ErrorCode { + NONE = 0, + ERROR_COMMUNICATION_FAILED, + ERROR_WRONG_CHIP_ID, + } error_code_{NONE}; + + union { + struct { + bool raw_reading : 1; // raw state of PIR sensor + bool event_available : 1; // a debounced object has been detected or removed + bool object_removed : 1; // a debounced object is no longer detected + bool object_detected : 1; // a debounced object has been detected + bool : 4; + }; + uint8_t reg; + } event_register_ = {.reg = 0}; + + void clear_events_(); +}; + +} // namespace qwiic_pir +} // namespace esphome diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index b10986c9cba3..340322c188b4 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "radon_eye_ble"; bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (not device.get_name().empty()) { - if (device.get_name().rfind("FR:R20:SN", 0) == 0) { + if (device.get_name().rfind("FR:R", 0) == 0) { // This is an RD200, I think ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(), device.address_str().c_str()); diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 4e74020e4c6f..e2146dd14ebd 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -397,8 +397,10 @@ RC522::StatusCode RC522::await_transceive_() { back_length_ = 0; ESP_LOGW(TAG, "Communication with the MFRC522 might be down, reset in %d", 10 - error_counter_); // todo: trigger reset? - if (error_counter_++ > 10) + if (error_counter_++ >= 10) { setup(); + error_counter_ = 0; // reset the error counter + } return STATUS_TIMEOUT; } diff --git a/esphome/components/rdm6300/rdm6300.cpp b/esphome/components/rdm6300/rdm6300.cpp index 434b9f572069..bfdd8800798f 100644 --- a/esphome/components/rdm6300/rdm6300.cpp +++ b/esphome/components/rdm6300/rdm6300.cpp @@ -57,7 +57,7 @@ void rdm6300::RDM6300Component::loop() { trig->process(result); if (report) { - ESP_LOGD(TAG, "Found new tag with ID %u", result); + ESP_LOGD(TAG, "Found new tag with ID %" PRIu32, result); } } } diff --git a/esphome/components/rdm6300/rdm6300.h b/esphome/components/rdm6300/rdm6300.h index 0aeabef2bca1..1a1a0c0cd68e 100644 --- a/esphome/components/rdm6300/rdm6300.h +++ b/esphome/components/rdm6300/rdm6300.h @@ -5,6 +5,7 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/uart/uart.h" +#include #include namespace esphome { diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index e2d96c9472c0..8a1d50d1c6bd 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -3,6 +3,7 @@ from esphome import automation from esphome.components import binary_sensor from esphome.const import ( + CONF_COMMAND_REPEATS, CONF_DATA, CONF_TRIGGER_ID, CONF_NBITS, @@ -31,6 +32,10 @@ CONF_MAGNITUDE, CONF_WAND_ID, CONF_LEVEL, + CONF_DELTA, + CONF_ID, + CONF_BUTTON, + CONF_CHECK, ) from esphome.core import coroutine from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -52,8 +57,9 @@ "RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener ) RemoteTransmitterDumper = ns.class_("RemoteTransmitterDumper") +RemoteTransmittable = ns.class_("RemoteTransmittable") RemoteTransmitterActionBase = ns.class_( - "RemoteTransmitterActionBase", automation.Action + "RemoteTransmitterActionBase", RemoteTransmittable, automation.Action ) RemoteReceiverBase = ns.class_("RemoteReceiverBase") RemoteTransmitterBase = ns.class_("RemoteTransmitterBase") @@ -68,11 +74,30 @@ def templatize(value): return cv.Schema(ret) +REMOTE_LISTENER_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), + } +) + + +REMOTE_TRANSMITTABLE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), + } +) + + async def register_listener(var, config): receiver = await cg.get_variable(config[CONF_RECEIVER_ID]) cg.add(receiver.register_listener(var)) +async def register_transmittable(var, config): + transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter_)) + + def register_binary_sensor(name, type, schema): return BINARY_SENSOR_REGISTRY.register(name, type, schema) @@ -129,10 +154,9 @@ def validate_repeat(value): BASE_REMOTE_TRANSMITTER_SCHEMA = cv.Schema( { - cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), cv.Optional(CONF_REPEAT): validate_repeat, } -) +).extend(REMOTE_TRANSMITTABLE_SCHEMA) def register_action(name, type_, schema): @@ -143,9 +167,8 @@ def register_action(name, type_, schema): def decorator(func): async def new_func(config, action_id, template_arg, args): - transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID]) var = cg.new_Pvariable(action_id, template_arg) - cg.add(var.set_parent(transmitter)) + await register_transmittable(var, config) if CONF_REPEAT in config: conf = config[CONF_REPEAT] template_ = await cg.templatable(conf[CONF_TIMES], args, cg.uint32) @@ -242,6 +265,55 @@ async def build_dumpers(config): return dumpers +# ByronSX +( + ByronSXData, + ByronSXBinarySensor, + ByronSXTrigger, + ByronSXAction, + ByronSXDumper, +) = declare_protocol("ByronSX") +BYRONSX_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFF)), + cv.Optional(CONF_COMMAND, default=0x10): cv.All( + cv.hex_int, cv.one_of(1, 2, 3, 5, 6, 9, 0xD, 0xE, 0x10, int=True) + ), + } +) + + +@register_binary_sensor("byronsx", ByronSXBinarySensor, BYRONSX_SCHEMA) +def byronsx_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ByronSXData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("byronsx", ByronSXTrigger, ByronSXData) +def byronsx_trigger(var, config): + pass + + +@register_dumper("byronsx", ByronSXDumper) +def byronsx_dumper(var, config): + pass + + +@register_action("byronsx", ByronSXAction, BYRONSX_SCHEMA) +async def byronsx_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + + # CanalSat ( CanalSatData, @@ -372,19 +444,14 @@ def coolix_binary_sensor(var, config): if isinstance(config, dict): cg.add( var.set_data( - cg.StructInitializer( - CoolixData, - ("first", config[CONF_FIRST]), - ("second", config[CONF_SECOND]), + cg.ArrayInitializer( + config[CONF_FIRST], + config[CONF_SECOND], ) ) ) else: - cg.add( - var.set_data( - cg.StructInitializer(CoolixData, ("first", 0), ("second", config)) - ) - ) + cg.add(var.set_data(cg.ArrayInitializer(0, config))) @register_action("coolix", CoolixAction, COOLIX_BASE_SCHEMA) @@ -448,6 +515,57 @@ async def dish_action(var, config, args): cg.add(var.set_command(template_)) +# Dooya +DooyaData, DooyaBinarySensor, DooyaTrigger, DooyaAction, DooyaDumper = declare_protocol( + "Dooya" +) +DOOYA_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.hex_int_range(0, 16777215), + cv.Required(CONF_CHANNEL): cv.hex_int_range(0, 255), + cv.Required(CONF_BUTTON): cv.hex_int_range(0, 15), + cv.Required(CONF_CHECK): cv.hex_int_range(0, 15), + } +) + + +@register_binary_sensor("dooya", DooyaBinarySensor, DOOYA_SCHEMA) +def dooya_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + DooyaData, + ("id", config[CONF_ID]), + ("channel", config[CONF_CHANNEL]), + ("button", config[CONF_BUTTON]), + ("check", config[CONF_CHECK]), + ) + ) + ) + + +@register_trigger("dooya", DooyaTrigger, DooyaData) +def dooya_trigger(var, config): + pass + + +@register_dumper("dooya", DooyaDumper) +def dooya_dumper(var, config): + pass + + +@register_action("dooya", DooyaAction, DOOYA_SCHEMA) +async def dooya_action(var, config, args): + template_ = await cg.templatable(config[CONF_ID], args, cg.uint32) + cg.add(var.set_id(template_)) + template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) + cg.add(var.set_channel(template_)) + template_ = await cg.templatable(config[CONF_BUTTON], args, cg.uint8) + cg.add(var.set_button(template_)) + template_ = await cg.templatable(config[CONF_CHECK], args, cg.uint8) + cg.add(var.set_check(template_)) + + # JVC JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC") JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) @@ -570,12 +688,69 @@ async def magiquest_action(var, config, args): cg.add(var.set_magnitude(template_)) +# Microchip HCS301 KeeLoq OOK +( + KeeloqData, + KeeloqBinarySensor, + KeeloqTrigger, + KeeloqAction, + KeeloqDumper, +) = declare_protocol("Keeloq") +KEELOQ_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFF)), + cv.Required(CONF_CODE): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFFF)), + cv.Optional(CONF_COMMAND, default=0x10): cv.All( + cv.hex_int, + cv.Range(min=0, max=0x10), + ), + cv.Optional(CONF_LEVEL, default=False): cv.boolean, + } +) + + +@register_binary_sensor("keeloq", KeeloqBinarySensor, KEELOQ_SCHEMA) +def Keeloq_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + KeeloqData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("keeloq", KeeloqTrigger, KeeloqData) +def keeloq_trigger(var, config): + pass + + +@register_dumper("keeloq", KeeloqDumper) +def keeloq_dumper(var, config): + pass + + +@register_action("keeloq", KeeloqAction, KEELOQ_SCHEMA) +async def keeloq_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint32) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_CODE], args, cg.uint32) + cg.add(var.set_encrypted(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + template_ = await cg.templatable(config[CONF_LEVEL], args, bool) + cg.add(var.set_vlow(template_)) + + # NEC NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC") NEC_SCHEMA = cv.Schema( { cv.Required(CONF_ADDRESS): cv.hex_uint16_t, cv.Required(CONF_COMMAND): cv.hex_uint16_t, + cv.Optional(CONF_COMMAND_REPEATS, default=1): cv.uint16_t, } ) @@ -588,6 +763,7 @@ def nec_binary_sensor(var, config): NECData, ("address", config[CONF_ADDRESS]), ("command", config[CONF_COMMAND]), + ("command_repeats", config[CONF_COMMAND_REPEATS]), ) ) ) @@ -609,6 +785,8 @@ async def nec_action(var, config, args): cg.add(var.set_address(template_)) template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint16) cg.add(var.set_command(template_)) + template_ = await cg.templatable(config[CONF_COMMAND_REPEATS], args, cg.uint16) + cg.add(var.set_command_repeats(template_)) # Pioneer @@ -669,6 +847,7 @@ async def pioneer_action(var, config, args): PRONTO_SCHEMA = cv.Schema( { cv.Required(CONF_DATA): cv.string, + cv.Optional(CONF_DELTA, default=-1): cv.int_, } ) @@ -680,6 +859,7 @@ def pronto_binary_sensor(var, config): cg.StructInitializer( ProntoData, ("data", config[CONF_DATA]), + ("delta", config[CONF_DELTA]), ) ) ) @@ -701,6 +881,45 @@ async def pronto_action(var, config, args): cg.add(var.set_data(template_)) +# Roomba +( + RoombaData, + RoombaBinarySensor, + RoombaTrigger, + RoombaAction, + RoombaDumper, +) = declare_protocol("Roomba") +ROOMBA_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint8_t}) + + +@register_binary_sensor("roomba", RoombaBinarySensor, ROOMBA_SCHEMA) +def roomba_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + RoombaData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("roomba", RoombaTrigger, RoombaData) +def roomba_trigger(var, config): + pass + + +@register_dumper("roomba", RoombaDumper) +def roomba_dumper(var, config): + pass + + +@register_action("roomba", RoombaAction, ROOMBA_SCHEMA) +async def roomba_action(var, config, args): + template_ = await cg.templatable(config[CONF_DATA], args, cg.uint8) + cg.add(var.set_data(template_)) + + # Sony SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol( "Sony" @@ -969,7 +1188,7 @@ async def rc6_action(var, config, args): def validate_rc_switch_code(value): - if not isinstance(value, (str, str)): + if not isinstance(value, str): raise cv.Invalid("All RCSwitch codes must be in quotes ('')") for c in value: if c not in ("0", "1"): @@ -986,7 +1205,7 @@ def validate_rc_switch_code(value): def validate_rc_switch_raw_code(value): - if not isinstance(value, (str, str)): + if not isinstance(value, str): raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')") for c in value: if c not in ("0", "1", "x"): @@ -1495,7 +1714,7 @@ def nexa_action(var, config, args): @register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA) def midea_binary_sensor(var, config): - cg.add(var.set_code(config[CONF_CODE])) + cg.add(var.set_data(config[CONF_CODE])) @register_trigger("midea", MideaTrigger, MideaData) @@ -1558,3 +1777,139 @@ async def aeha_action(var, config, args): config[CONF_DATA], args, cg.std_vector.template(cg.uint8) ) cg.add(var.set_data(template_)) + + +# Haier +HaierData, HaierBinarySensor, HaierTrigger, HaierAction, HaierDumper = declare_protocol( + "Haier" +) +HaierAction = ns.class_("HaierAction", RemoteTransmitterActionBase) +HAIER_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=13, max=13)), + } +) + + +@register_binary_sensor("haier", HaierBinarySensor, HAIER_SCHEMA) +def haier_binary_sensor(var, config): + cg.add(var.set_code(config[CONF_CODE])) + + +@register_trigger("haier", HaierTrigger, HaierData) +def haier_trigger(var, config): + pass + + +@register_dumper("haier", HaierDumper) +def haier_dumper(var, config): + pass + + +@register_action("haier", HaierAction, HAIER_SCHEMA) +async def haier_action(var, config, args): + vec_ = cg.std_vector.template(cg.uint8) + template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) + cg.add(var.set_code(template_)) + + +# ABBWelcome +( + ABBWelcomeData, + ABBWelcomeBinarySensor, + ABBWelcomeTrigger, + ABBWelcomeAction, + ABBWelcomeDumper, +) = declare_protocol("ABBWelcome") + +CONF_SOURCE_ADDRESS = "source_address" +CONF_DESTINATION_ADDRESS = "destination_address" +CONF_THREE_BYTE_ADDRESS = "three_byte_address" +CONF_MESSAGE_TYPE = "message_type" +CONF_MESSAGE_ID = "message_id" +CONF_RETRANSMISSION = "retransmission" + +ABB_WELCOME_SCHEMA = cv.Schema( + { + cv.Required(CONF_SOURCE_ADDRESS): cv.hex_uint32_t, + cv.Required(CONF_DESTINATION_ADDRESS): cv.hex_uint32_t, + cv.Optional(CONF_RETRANSMISSION, default=False): cv.boolean, + cv.Optional(CONF_THREE_BYTE_ADDRESS, default=False): cv.boolean, + cv.Required(CONF_MESSAGE_TYPE): cv.Any(cv.hex_uint8_t, cv.uint8_t), + cv.Optional(CONF_MESSAGE_ID): cv.Any(cv.hex_uint8_t, cv.uint8_t), + cv.Optional(CONF_DATA): cv.All( + [cv.Any(cv.hex_uint8_t, cv.uint8_t)], + cv.Length(min=0, max=7), + ), + } +) + + +@register_binary_sensor("abbwelcome", ABBWelcomeBinarySensor, ABB_WELCOME_SCHEMA) +def abbwelcome_binary_sensor(var, config): + cg.add(var.set_three_byte_address(config[CONF_THREE_BYTE_ADDRESS])) + cg.add(var.set_source_address(config[CONF_SOURCE_ADDRESS])) + cg.add(var.set_destination_address(config[CONF_DESTINATION_ADDRESS])) + cg.add(var.set_retransmission(config[CONF_RETRANSMISSION])) + cg.add(var.set_message_type(config[CONF_MESSAGE_TYPE])) + cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config)) + if CONF_MESSAGE_ID in config: + cg.add(var.set_message_id(config[CONF_MESSAGE_ID])) + if CONF_DATA in config: + cg.add(var.set_data(config[CONF_DATA])) + cg.add(var.finalize()) + + +@register_trigger("abbwelcome", ABBWelcomeTrigger, ABBWelcomeData) +def abbwelcome_trigger(var, config): + pass + + +@register_dumper("abbwelcome", ABBWelcomeDumper) +def abbwelcome_dumper(var, config): + pass + + +@register_action("abbwelcome", ABBWelcomeAction, ABB_WELCOME_SCHEMA) +async def abbwelcome_action(var, config, args): + cg.add( + var.set_three_byte_address( + await cg.templatable(config[CONF_THREE_BYTE_ADDRESS], args, cg.bool_) + ) + ) + cg.add( + var.set_source_address( + await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint16) + ) + ) + cg.add( + var.set_destination_address( + await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint16) + ) + ) + cg.add( + var.set_retransmission( + await cg.templatable(config[CONF_RETRANSMISSION], args, cg.bool_) + ) + ) + cg.add( + var.set_message_type( + await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8) + ) + ) + cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config)) + if CONF_MESSAGE_ID in config: + cg.add( + var.set_message_id( + await cg.templatable(config[CONF_MESSAGE_ID], args, cg.uint8) + ) + ) + if CONF_DATA in config: + data_ = config[CONF_DATA] + if cg.is_template(data_): + template_ = await cg.templatable( + data_, args, cg.std_vector.template(cg.uint8) + ) + cg.add(var.set_data_template(template_)) + else: + cg.add(var.set_data_static(data_)) diff --git a/esphome/components/remote_base/abbwelcome_protocol.cpp b/esphome/components/remote_base/abbwelcome_protocol.cpp new file mode 100644 index 000000000000..88f928901bc8 --- /dev/null +++ b/esphome/components/remote_base/abbwelcome_protocol.cpp @@ -0,0 +1,123 @@ +#include "abbwelcome_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.abbwelcome"; + +static const uint32_t BIT_ONE_SPACE_US = 102; +static const uint32_t BIT_ZERO_MARK_US = 32; // 18-44 +static const uint32_t BIT_ZERO_SPACE_US = BIT_ONE_SPACE_US - BIT_ZERO_MARK_US; +static const uint16_t BYTE_SPACE_US = 210; + +uint8_t ABBWelcomeData::calc_cs_() const { + uint8_t checksum = 0; + for (uint8_t i = 0; i < this->size() - 1; i++) { + uint16_t temp = checksum ^ (this->data_[i]); + temp = temp ^ (uint16_t) (((uint32_t) temp << 0x11) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x12) >> 0x10) ^ + (uint16_t) (((uint32_t) temp << 0x13) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x14) >> 0x10) ^ + (uint16_t) (((uint32_t) temp << 0x15) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x16) >> 0x10) ^ + (uint16_t) (((uint32_t) temp << 0x17) >> 0x10); + checksum = (temp & 0xfe) ^ ((temp >> 8) & 1); + } + return ~checksum; +} + +void ABBWelcomeProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t data) const { + // space = bus high, mark = activate bus pulldown + dst->mark(BIT_ZERO_MARK_US); + uint32_t next_space = BIT_ZERO_SPACE_US; + for (uint8_t mask = 1 << 7; mask; mask >>= 1) { + if (data & mask) { + next_space += BIT_ONE_SPACE_US; + } else { + dst->space(next_space); + dst->mark(BIT_ZERO_MARK_US); + next_space = BIT_ZERO_SPACE_US; + } + } + next_space += BYTE_SPACE_US; + dst->space(next_space); +} + +void ABBWelcomeProtocol::encode(RemoteTransmitData *dst, const ABBWelcomeData &src) { + dst->set_carrier_frequency(0); + uint32_t reserve_count = 0; + for (size_t i = 0; i < src.size(); i++) { + reserve_count += 2 * (9 - (src[i] & 1) - ((src[i] >> 1) & 1) - ((src[i] >> 2) & 1) - ((src[i] >> 3) & 1) - + ((src[i] >> 4) & 1) - ((src[i] >> 5) & 1) - ((src[i] >> 6) & 1) - ((src[i] >> 7) & 1)); + } + 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()); +} + +bool ABBWelcomeProtocol::decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data) { + if (!src.expect_mark(BIT_ZERO_MARK_US)) + return false; + uint32_t next_space = BIT_ZERO_SPACE_US; + for (uint8_t mask = 1 << 7; mask; mask >>= 1) { + // if (!src.peek_space_at_least(next_space, 0)) + // return false; + if (src.expect_space(next_space)) { + if (!src.expect_mark(BIT_ZERO_MARK_US)) + return false; + next_space = BIT_ZERO_SPACE_US; + } else { + data |= mask; + next_space += BIT_ONE_SPACE_US; + } + } + next_space += BYTE_SPACE_US; + // if (!src.peek_space_at_least(next_space, 0)) + // return false; + done = !(src.expect_space(next_space)); + return true; +} + +optional ABBWelcomeProtocol::decode(RemoteReceiveData src) { + if (src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US) && + src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) && + src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) && + src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) && + src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US + BYTE_SPACE_US) && + src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + 8 * BIT_ONE_SPACE_US + BYTE_SPACE_US)) { + ESP_LOGVV(TAG, "Received Header: 0x55FF"); + ABBWelcomeData out; + out[0] = 0x55; + out[1] = 0xff; + bool done = false; + uint8_t length = 10; + uint8_t received_bytes = 2; + 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()); + return {}; + } + if (received_bytes == 2) { + length += std::min(static_cast(data & DATA_LENGTH_MASK), MAX_DATA_LENGTH); + if (data & 0x40) { + length += 2; + } + } + ESP_LOGVV(TAG, "Received Byte: 0x%02X", data); + out[received_bytes] = data; + } + if (out.is_valid()) { + ESP_LOGI(TAG, "Received: %s", out.to_string().c_str()); + return out; + } + ESP_LOGW(TAG, "Received malformed packet: %s", out.to_string(received_bytes).c_str()); + } + return {}; +} + +void ABBWelcomeProtocol::dump(const ABBWelcomeData &data) { + ESP_LOGD(TAG, "Received ABBWelcome: %s", data.to_string().c_str()); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h new file mode 100644 index 000000000000..04939939264d --- /dev/null +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -0,0 +1,251 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "remote_base.h" +#include +#include +#include + +namespace esphome { +namespace remote_base { + +static const uint8_t MAX_DATA_LENGTH = 15; +static const uint8_t DATA_LENGTH_MASK = 0x3f; + +/* +Message Format: + 2 bytes: Sync (0x55FF) + 1 bit: Retransmission flag (High means retransmission) + 1 bit: Address length flag (Low means 2 bytes, High means 3 bytes) + 2 bits: Unknown + 4 bits: Data length (in bytes) + 1 bit: Reply flag (High means this is a reply to a previous message with the same message type) + 7 bits: Message type + 2-3 bytes: Destination address + 2-3 bytes: Source address + 1 byte: Message ID (randomized, does not change for retransmissions) + 0-? bytes: Data + 1 byte: Checksum +*/ + +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; + } + // Make from initializer_list + ABBWelcomeData(std::initializer_list data) { + std::fill(std::begin(this->data_), std::end(this->data_), 0); + 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); + std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); + } + // Default copy constructor + ABBWelcomeData(const ABBWelcomeData &) = default; + + bool auto_message_id{false}; + + uint8_t *data() { return this->data_.data(); } + const uint8_t *data() const { return this->data_.data(); } + uint8_t size() const { + return std::min(static_cast(6 + (2 * this->get_address_length()) + (this->data_[2] & DATA_LENGTH_MASK)), + static_cast(this->data_.size())); + } + bool is_valid() const { + return this->data_[0] == 0x55 && this->data_[1] == 0xff && + ((this->data_[2] & DATA_LENGTH_MASK) <= MAX_DATA_LENGTH) && + (this->data_[this->size() - 1] == this->calc_cs_()); + } + void set_retransmission(bool retransmission) { + if (retransmission) { + this->data_[2] |= 0x80; + } else { + this->data_[2] &= 0x7f; + } + } + bool get_retransmission() const { return this->data_[2] & 0x80; } + // set_three_byte_address must be called before set_source_address, set_destination_address, set_message_id and + // set_data! + void set_three_byte_address(bool three_byte_address) { + if (three_byte_address) { + this->data_[2] |= 0x40; + } else { + this->data_[2] &= 0xbf; + } + } + uint8_t get_three_byte_address() const { return (this->data_[2] & 0x40); } + uint8_t get_address_length() const { return this->get_three_byte_address() ? 3 : 2; } + void set_message_type(uint8_t message_type) { this->data_[3] = message_type; } + uint8_t get_message_type() const { return this->data_[3]; } + void set_destination_address(uint32_t address) { + if (this->get_address_length() == 2) { + this->data_[4] = (address >> 8) & 0xff; + this->data_[5] = address & 0xff; + } else { + this->data_[4] = (address >> 16) & 0xff; + this->data_[5] = (address >> 8) & 0xff; + this->data_[6] = address & 0xff; + } + } + uint32_t get_destination_address() const { + if (this->get_address_length() == 2) { + return (this->data_[4] << 8) + this->data_[5]; + } + return (this->data_[4] << 16) + (this->data_[5] << 8) + this->data_[6]; + } + void set_source_address(uint32_t address) { + if (this->get_address_length() == 2) { + this->data_[6] = (address >> 8) & 0xff; + this->data_[7] = address & 0xff; + } else { + this->data_[7] = (address >> 16) & 0xff; + this->data_[8] = (address >> 8) & 0xff; + this->data_[9] = address & 0xff; + } + } + uint32_t get_source_address() const { + if (this->get_address_length() == 2) { + return (this->data_[6] << 8) + this->data_[7]; + } + return (this->data_[7] << 16) + (this->data_[8] << 8) + this->data_[9]; + } + void set_message_id(uint8_t message_id) { this->data_[4 + 2 * this->get_address_length()] = message_id; } + uint8_t get_message_id() const { return this->data_[4 + 2 * this->get_address_length()]; } + void set_data(std::vector data) { + uint8_t size = std::min(MAX_DATA_LENGTH, static_cast(data.size())); + this->data_[2] &= (0xff ^ DATA_LENGTH_MASK); + this->data_[2] |= (size & DATA_LENGTH_MASK); + if (size) + std::copy_n(data.begin(), size, this->data_.begin() + 5 + 2 * this->get_address_length()); + } + std::vector get_data() const { + std::vector data(this->data_.begin() + 5 + 2 * this->get_address_length(), + this->data_.begin() + 5 + 2 * this->get_address_length() + this->get_data_size()); + return data; + } + uint8_t get_data_size() const { + return std::min(MAX_DATA_LENGTH, static_cast(this->data_[2] & DATA_LENGTH_MASK)); + } + void finalize() { + if (this->auto_message_id && !this->get_retransmission() && !(this->data_[3] & 0x80)) { + this->set_message_id(static_cast(random_uint32())); + } + this->data_[0] = 0x55; + 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() ? "[%06X %s %06X] Type: %02X" : "[%04X %s %04X] 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; + } + bool operator==(const ABBWelcomeData &rhs) const { + if (std::equal(this->data_.begin(), this->data_.begin() + this->size(), rhs.data_.begin())) + return true; + return (this->auto_message_id || rhs.auto_message_id) && this->is_valid() && rhs.is_valid() && + (this->get_message_type() == rhs.get_message_type()) && + (this->get_source_address() == rhs.get_source_address()) && + (this->get_destination_address() == rhs.get_destination_address()) && (this->get_data() == rhs.get_data()); + } + uint8_t &operator[](size_t idx) { return this->data_[idx]; } + const uint8_t &operator[](size_t idx) const { return this->data_[idx]; } + + protected: + std::array data_; + // Calculate checksum + uint8_t calc_cs_() const; +}; + +class ABBWelcomeProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const ABBWelcomeData &src) override; + optional decode(RemoteReceiveData src) override; + void dump(const ABBWelcomeData &data) override; + + protected: + void encode_byte_(RemoteTransmitData *dst, uint8_t data) const; + bool decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data); +}; + +class ABBWelcomeBinarySensor : public RemoteReceiverBinarySensorBase { + public: + bool matches(RemoteReceiveData src) override { + auto data = ABBWelcomeProtocol().decode(src); + return data.has_value() && data.value() == this->data_; + } + void set_source_address(const uint32_t source_address) { this->data_.set_source_address(source_address); } + void set_destination_address(const uint32_t destination_address) { + this->data_.set_destination_address(destination_address); + } + void set_retransmission(const bool retransmission) { this->data_.set_retransmission(retransmission); } + void set_three_byte_address(const bool three_byte_address) { this->data_.set_three_byte_address(three_byte_address); } + void set_message_type(const uint8_t message_type) { this->data_.set_message_type(message_type); } + void set_message_id(const uint8_t message_id) { this->data_.set_message_id(message_id); } + void set_auto_message_id(const bool auto_message_id) { this->data_.auto_message_id = auto_message_id; } + void set_data(const std::vector &data) { this->data_.set_data(data); } + void finalize() { this->data_.finalize(); } + + protected: + ABBWelcomeData data_; +}; + +using ABBWelcomeTrigger = RemoteReceiverTrigger; +using ABBWelcomeDumper = RemoteReceiverDumper; + +template class ABBWelcomeAction : public RemoteTransmitterActionBase { + TEMPLATABLE_VALUE(uint32_t, source_address) + TEMPLATABLE_VALUE(uint32_t, destination_address) + TEMPLATABLE_VALUE(bool, retransmission) + TEMPLATABLE_VALUE(bool, three_byte_address) + 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 encode(RemoteTransmitData *dst, Ts... x) override { + ABBWelcomeData data; + data.set_three_byte_address(this->three_byte_address_.value(x...)); + data.set_source_address(this->source_address_.value(x...)); + data.set_destination_address(this->destination_address_.value(x...)); + data.set_retransmission(this->retransmission_.value(x...)); + 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...)); + } else { + data.set_data(this->data_static_); + } + data.finalize(); + ABBWelcomeProtocol().encode(dst, data); + } + + protected: + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; + bool has_data_func_{false}; +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/byronsx_protocol.cpp b/esphome/components/remote_base/byronsx_protocol.cpp new file mode 100644 index 000000000000..3096283b3013 --- /dev/null +++ b/esphome/components/remote_base/byronsx_protocol.cpp @@ -0,0 +1,134 @@ +#include "byronsx_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.byronsx"; + +static const uint32_t BIT_TIME_US = 333; +static const uint8_t NBITS_ADDRESS = 8; +static const uint8_t NBITS_COMMAND = 4; +static const uint8_t NBITS_START_BIT = 1; +static const uint8_t NBITS_DATA = NBITS_ADDRESS + NBITS_COMMAND /*+ NBITS_COMMAND*/; + +/* +ByronSX Protocol +Each transmitted packet appears to consist of thirteen bits of PWM encoded +data. Each bit period of aprox 1ms consists of a transmitter OFF period +followed by a transmitter ON period. The 'on' and 'off' periods are either +short (approx 332us) or long (664us). + +A short 'off' followed by a long 'on' represents a '1' bit. +A long 'off' followed by a short 'on' represents a '0' bit. + +A the beginning of each packet is and initial 'off' period of approx 5.6ms +followed by a short 'on'. + +The data payload consists of twelve bits which appear to be an eight bit +address floowied by a 4 bit chime number. + +SAAAAAAAACCCC + +Whese: +S = the initial short start pulse +A = The eight address bits +C - The four chime bits + +-------------------- + +I have also used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) +to capture these packets, eg: + +20;19;Byron;ID=004f;SWITCH=02;CMD=ON;CHIME=02; + +This module transmits and interprets packets in the same way as RFLink. + +marshn + +*/ + +void ByronSXProtocol::encode(RemoteTransmitData *dst, const ByronSXData &data) { + uint32_t out_data = 0x0; + + ESP_LOGD(TAG, "Send ByronSX: address=%04x command=%03x", data.address, data.command); + + out_data = data.address; + out_data <<= NBITS_COMMAND; + out_data |= data.command; + + ESP_LOGV(TAG, "Send ByronSX: out_data %03x", out_data); + + // Initial Mark start bit + dst->mark(1 * BIT_TIME_US); + + for (uint32_t mask = 1UL << (NBITS_DATA - 1); mask != 0; mask >>= 1) { + if (out_data & mask) { + dst->space(2 * BIT_TIME_US); + dst->mark(1 * BIT_TIME_US); + } else { + dst->space(1 * BIT_TIME_US); + dst->mark(2 * BIT_TIME_US); + } + } + // final space at end of packet + dst->space(17 * BIT_TIME_US); +} + +optional ByronSXProtocol::decode(RemoteReceiveData src) { + ByronSXData out{ + .address = 0, + .command = 0, + }; + + if (src.size() != (NBITS_DATA + NBITS_START_BIT) * 2) { + return {}; + } + + // Skip start bit + if (!src.expect_mark(BIT_TIME_US)) { + return {}; + } + + ESP_LOGVV(TAG, "%3d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0), + src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8), + src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15), + src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + + ESP_LOGVV(TAG, " %d %d %d %d %d %d", src.peek(20), src.peek(21), src.peek(22), src.peek(23), src.peek(24), + src.peek(25)); + + // Read data bits + uint32_t out_data = 0; + int8_t bit = NBITS_DATA; + while (--bit >= 0) { + if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US)) { + out_data |= 1 << bit; + } else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) { + out_data |= 0 << bit; + } else { + ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08x", bit, out_data); + return {}; + } + ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08x", bit, out_data); + } + + // last bit followed by a long space + if (!src.peek_space_at_least(BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode ByronSX: Fail 4 "); + return {}; + } + + out.command = (uint8_t) (out_data & 0xF); + out_data >>= NBITS_COMMAND; + out.address = (uint16_t) (out_data & 0xFF); + + return out; +} + +void ByronSXProtocol::dump(const ByronSXData &data) { + ESP_LOGD(TAG, "Received ByronSX: address=0x%08X, command=0x%02x", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/byronsx_protocol.h b/esphome/components/remote_base/byronsx_protocol.h new file mode 100644 index 000000000000..5d23237ab115 --- /dev/null +++ b/esphome/components/remote_base/byronsx_protocol.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ByronSXData { + uint8_t address; + uint8_t command; + + bool operator==(const ByronSXData &rhs) const { + // Treat 0x10 as a special, wildcard command/chime + // This allows us to match on just the address if wanted. + if (address != rhs.address) { + return false; + } + return (rhs.command == 0x10 || command == rhs.command); + } +}; + +class ByronSXProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const ByronSXData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ByronSXData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(ByronSX) + +template class ByronSXAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint8_t, address) + TEMPLATABLE_VALUE(uint8_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ByronSXData data{}; + data.address = this->address_.value(x...); + data.command = this->command_.value(x...); + ByronSXProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp index 295fccb762d9..21a9f598b7a9 100644 --- a/esphome/components/remote_base/coolix_protocol.cpp +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -101,11 +101,11 @@ optional CoolixProtocol::decode(RemoteReceiveData data) { void CoolixProtocol::dump(const CoolixData &data) { if (data.is_strict()) { - ESP_LOGI(TAG, "Received Coolix: 0x%06X", data.first); + ESP_LOGI(TAG, "Received Coolix: 0x%06" PRIX32, data.first); } else if (data.has_second()) { - ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second); + ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06" PRIX32 ", 0x%06" PRIX32 "]", data.first, data.second); } else { - ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06X]", data.first); + ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06" PRIX32 "]", data.first); } } diff --git a/esphome/components/remote_base/coolix_protocol.h b/esphome/components/remote_base/coolix_protocol.h index 50ac83920095..b66415ff70e8 100644 --- a/esphome/components/remote_base/coolix_protocol.h +++ b/esphome/components/remote_base/coolix_protocol.h @@ -4,6 +4,8 @@ #include "esphome/core/helpers.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/dooya_protocol.cpp b/esphome/components/remote_base/dooya_protocol.cpp new file mode 100644 index 000000000000..d979bca8c58b --- /dev/null +++ b/esphome/components/remote_base/dooya_protocol.cpp @@ -0,0 +1,120 @@ +#include "dooya_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.dooya"; + +static const uint32_t HEADER_HIGH_US = 5000; +static const uint32_t HEADER_LOW_US = 1500; +static const uint32_t BIT_ZERO_HIGH_US = 750; +static const uint32_t BIT_ZERO_LOW_US = 350; +static const uint32_t BIT_ONE_HIGH_US = 350; +static const uint32_t BIT_ONE_LOW_US = 750; + +void DooyaProtocol::encode(RemoteTransmitData *dst, const DooyaData &data) { + dst->set_carrier_frequency(0); + dst->reserve(2 + 40 * 2u); + + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + + for (uint32_t mask = 1UL << (23); mask != 0; mask >>= 1) { + if (data.id & mask) { + dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US); + } + } + + for (uint32_t mask = 1UL << (7); mask != 0; mask >>= 1) { + if (data.channel & mask) { + dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US); + } + } + + for (uint32_t mask = 1UL << (3); mask != 0; mask >>= 1) { + if (data.button & mask) { + dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US); + } + } + + for (uint32_t mask = 1UL << (3); mask != 0; mask >>= 1) { + if (data.check & mask) { + dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US); + } + } +} +optional DooyaProtocol::decode(RemoteReceiveData src) { + DooyaData out{ + .id = 0, + .channel = 0, + .button = 0, + .check = 0, + }; + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + for (uint8_t i = 0; i < 24; i++) { + if (src.expect_item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US)) { + out.id = (out.id << 1) | 1; + } else if (src.expect_item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US)) { + out.id = (out.id << 1) | 0; + } else { + return {}; + } + } + + for (uint8_t i = 0; i < 8; i++) { + if (src.expect_item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US)) { + out.channel = (out.channel << 1) | 1; + } else if (src.expect_item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US)) { + out.channel = (out.channel << 1) | 0; + } else { + return {}; + } + } + + for (uint8_t i = 0; i < 4; i++) { + if (src.expect_item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US)) { + out.button = (out.button << 1) | 1; + } else if (src.expect_item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US)) { + out.button = (out.button << 1) | 0; + } else { + return {}; + } + } + + for (uint8_t i = 0; i < 3; i++) { + if (src.expect_item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US)) { + out.check = (out.check << 1) | 1; + } else if (src.expect_item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US)) { + out.check = (out.check << 1) | 0; + } else { + return {}; + } + } + // Last bit is not received properly but can be decoded + if (src.expect_mark(BIT_ONE_HIGH_US)) { + out.check = (out.check << 1) | 1; + } else if (src.expect_mark(BIT_ZERO_HIGH_US)) { + out.check = (out.check << 1) | 0; + } else { + return {}; + } + + return out; +} +void DooyaProtocol::dump(const DooyaData &data) { + ESP_LOGI(TAG, "Received Dooya: id=0x%08" PRIX32 ", channel=%d, button=%d, check=%d", data.id, data.channel, + data.button, data.check); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/dooya_protocol.h b/esphome/components/remote_base/dooya_protocol.h new file mode 100644 index 000000000000..9d17ad5d5ee0 --- /dev/null +++ b/esphome/components/remote_base/dooya_protocol.h @@ -0,0 +1,49 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +#include + +namespace esphome { +namespace remote_base { + +struct DooyaData { + uint32_t id; + uint8_t channel; + uint8_t button; + uint8_t check; + + bool operator==(const DooyaData &rhs) const { + return id == rhs.id && channel == rhs.channel && button == rhs.button && check == rhs.check; + } +}; + +class DooyaProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const DooyaData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const DooyaData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Dooya) + +template class DooyaAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint32_t, id) + TEMPLATABLE_VALUE(uint8_t, channel) + TEMPLATABLE_VALUE(uint8_t, button) + TEMPLATABLE_VALUE(uint8_t, check) + + void encode(RemoteTransmitData *dst, Ts... x) override { + DooyaData data{}; + data.id = this->id_.value(x...); + data.channel = this->channel_.value(x...); + data.button = this->button_.value(x...); + data.check = this->check_.value(x...); + DooyaProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index 56a3dec1e049..acfb7a0f1681 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -13,7 +13,8 @@ static const uint8_t NBITS_SYNC = 4; static const uint8_t NBITS_ADDRESS = 16; static const uint8_t NBITS_CHANNEL = 5; static const uint8_t NBITS_COMMAND = 7; -static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; +static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; +static const uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2); static const uint8_t CMD_ON = 0x41; static const uint8_t CMD_OFF = 0x02; @@ -114,9 +115,9 @@ void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) { out_data <<= NBITS_CHANNEL; out_data |= data.channel; - ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data); + ESP_LOGV(TAG, "Send Drayton: out_data %08" PRIx32, out_data); - for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { + for (uint32_t mask = 1UL << (NDATABITS - 1); mask != 0; mask >>= 1) { if (out_data & mask) { dst->mark(BIT_TIME_US); dst->space(BIT_TIME_US); @@ -134,75 +135,96 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { .command = 0, }; - if (src.size() < 45) { - return {}; - } - - ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), - src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), - src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), - src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + while (src.size() - src.get_index() >= MIN_RX_SRC) { + ESP_LOGVV(TAG, + "Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 "", + src.size() - src.get_index(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), + src.peek(5), src.peek(6), src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), + src.peek(13), src.peek(14), src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + + // If first preamble item is a space, skip it + if (src.peek_space_at_least(1)) { + src.advance(1); + } - // If first preamble item is a space, skip it - if (src.peek_space_at_least(1)) { - src.advance(1); - } + // Look for sync pulse, after. If sucessful index points to space of sync symbol + while (src.size() - src.get_index() >= MIN_RX_SRC) { + ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(), + src.peek(1)); + if (src.peek_mark(2 * BIT_TIME_US) && + (src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) { + src.advance(1); + ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index()); + break; + } else { + src.advance(2); + } + } - // Look for sync pulse, after. If sucessful index points to space of sync symbol - for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) { - ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1)); - if (src.peek_mark(2 * BIT_TIME_US, preamble) && - (src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) { - src.advance(preamble + 1); + // No point continuing if not enough samples remaining to complete a packet + if (src.size() - src.get_index() < NDATABITS) { + ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %" PRIu32, src.get_index()); break; } - } - - // Read data. Index points to space of sync symbol - // Extract first bit - // Checks next bit to leave index pointing correctly - uint32_t out_data = 0; - uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1; - if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { - out_data |= 0 << bit; - } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && - (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { - out_data |= 1 << bit; - } else { - ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index()); - return {}; - } - // Before/after each bit is read the index points to the transition at the start of the bit period or, - // if there is no transition at the start of the bit period, then the transition in the middle of - // the previous bit period. - while (--bit >= 1) { - ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); - if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && - (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + // Read data. Index points to space of sync symbol + // Extract first bit + // Checks next bit to leave index pointing correctly + uint32_t out_data = 0; + uint8_t bit = NDATABITS - 1; + ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2)); + if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { out_data |= 0 << bit; - } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && + } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { out_data |= 1 << bit; } else { - ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data); - return {}; + ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1)); + continue; } - } - if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { - out_data |= 0; - } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { - out_data |= 1; - } - ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); - out.channel = (uint8_t) (out_data & 0x1F); - out_data >>= NBITS_CHANNEL; - out.command = (uint8_t) (out_data & 0x7F); - out_data >>= NBITS_COMMAND; - out.address = (uint16_t) (out_data & 0xFFFF); + // Before/after each bit is read the index points to the transition at the start of the bit period or, + // if there is no transition at the start of the bit period, then the transition in the middle of + // the previous bit period. + while (--bit >= 1) { + ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data); + if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && + (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + out_data |= 0 << bit; + } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && + (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { + out_data |= 1 << bit; + } else { + break; + } + } + + if (bit > 0) { + ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1)); + continue; + } - return out; + if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { + out_data |= 0; + } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { + out_data |= 1; + } else { + continue; + } + + ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); + + out.channel = (uint8_t) (out_data & 0x1F); + out_data >>= NBITS_CHANNEL; + out.command = (uint8_t) (out_data & 0x7F); + out_data >>= NBITS_COMMAND; + out.address = (uint16_t) (out_data & 0xFFFF); + + return out; + } + return {}; } void DraytonProtocol::dump(const DraytonData &data) { ESP_LOGI(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, diff --git a/esphome/components/remote_base/drayton_protocol.h b/esphome/components/remote_base/drayton_protocol.h index f468e7b57e86..75213b918669 100644 --- a/esphome/components/remote_base/drayton_protocol.h +++ b/esphome/components/remote_base/drayton_protocol.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/haier_protocol.cpp b/esphome/components/remote_base/haier_protocol.cpp new file mode 100644 index 000000000000..ec5cb5775c03 --- /dev/null +++ b/esphome/components/remote_base/haier_protocol.cpp @@ -0,0 +1,84 @@ +#include "haier_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.haier"; + +constexpr uint32_t HEADER_LOW_US = 3100; +constexpr uint32_t HEADER_HIGH_US = 4400; +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; + +void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) { + for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { + if (item & mask) { + dst->space(BIT_ONE_SPACE_US); + } else { + dst->space(BIT_ZERO_SPACE_US); + } + dst->mark(BIT_MARK_US); + } +} + +void HaierProtocol::encode(RemoteTransmitData *dst, const HaierData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(5 + ((data.data.size() + 1) * 2)); + dst->mark(HEADER_LOW_US); + dst->space(HEADER_LOW_US); + dst->mark(HEADER_LOW_US); + dst->space(HEADER_HIGH_US); + dst->mark(BIT_MARK_US); + uint8_t checksum = 0; + for (uint8_t item : data.data) { + this->encode_byte_(dst, item); + checksum += item; + } + this->encode_byte_(dst, checksum); +} + +optional HaierProtocol::decode(RemoteReceiveData src) { + if (!src.expect_item(HEADER_LOW_US, HEADER_LOW_US) || !src.expect_item(HEADER_LOW_US, HEADER_HIGH_US)) { + return {}; + } + if (!src.expect_mark(BIT_MARK_US)) { + return {}; + } + size_t size = src.size() - src.get_index() - 1; + if (size < HAIER_IR_PACKET_BIT_SIZE * 2) + return {}; + size = HAIER_IR_PACKET_BIT_SIZE * 2; + uint8_t checksum = 0; + HaierData out; + while (size > 0) { + uint8_t data = 0; + for (uint8_t mask = 0x80; mask != 0; mask >>= 1) { + if (src.expect_space(BIT_ONE_SPACE_US)) { + data |= mask; + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { + return {}; + } + if (!src.expect_mark(BIT_MARK_US)) { + return {}; + } + size -= 2; + } + if (size > 0) { + checksum += data; + out.data.push_back(data); + } else if (checksum != data) { + return {}; + } + } + return out; +} + +void HaierProtocol::dump(const HaierData &data) { + ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str()); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/haier_protocol.h b/esphome/components/remote_base/haier_protocol.h new file mode 100644 index 000000000000..7a4ee640e8f7 --- /dev/null +++ b/esphome/components/remote_base/haier_protocol.h @@ -0,0 +1,39 @@ +#pragma once + +#include "remote_base.h" +#include + +namespace esphome { +namespace remote_base { + +struct HaierData { + std::vector data; + + bool operator==(const HaierData &rhs) const { return data == rhs.data; } +}; + +class HaierProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const HaierData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const HaierData &data) override; + + protected: + void encode_byte_(RemoteTransmitData *dst, uint8_t item); +}; + +DECLARE_REMOTE_PROTOCOL(Haier) + +template class HaierAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(std::vector, code) + + void encode(RemoteTransmitData *dst, Ts... x) override { + HaierData data{}; + data.data = this->code_.value(x...); + HaierProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/jvc_protocol.cpp b/esphome/components/remote_base/jvc_protocol.cpp index 3d34cc614e29..ca423b61e697 100644 --- a/esphome/components/remote_base/jvc_protocol.cpp +++ b/esphome/components/remote_base/jvc_protocol.cpp @@ -46,7 +46,7 @@ optional JVCProtocol::decode(RemoteReceiveData src) { } return out; } -void JVCProtocol::dump(const JVCData &data) { ESP_LOGI(TAG, "Received JVC: data=0x%04X", data.data); } +void JVCProtocol::dump(const JVCData &data) { ESP_LOGI(TAG, "Received JVC: data=0x%04" PRIX32, data.data); } } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/jvc_protocol.h b/esphome/components/remote_base/jvc_protocol.h index fc40a6a874fb..a17e593ad255 100644 --- a/esphome/components/remote_base/jvc_protocol.h +++ b/esphome/components/remote_base/jvc_protocol.h @@ -2,6 +2,8 @@ #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/keeloq_protocol.cpp b/esphome/components/remote_base/keeloq_protocol.cpp new file mode 100644 index 000000000000..09d9ea4f5385 --- /dev/null +++ b/esphome/components/remote_base/keeloq_protocol.cpp @@ -0,0 +1,191 @@ +#include "keeloq_protocol.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.keeloq"; + +static const uint32_t BIT_TIME_US = 380; +static const uint8_t NBITS_PREAMBLE = 12; +static const uint8_t NBITS_REPEAT = 1; +static const uint8_t NBITS_VLOW = 1; +static const uint8_t NBITS_SERIAL = 28; +static const uint8_t NBITS_BUTTONS = 4; +static const uint8_t NBITS_DISC = 12; +static const uint8_t NBITS_SYNC_CNT = 16; + +static const uint8_t NBITS_FIXED_DATA = NBITS_REPEAT + NBITS_VLOW + NBITS_BUTTONS + NBITS_SERIAL; +static const uint8_t NBITS_ENCRYPTED_DATA = NBITS_BUTTONS + NBITS_DISC + NBITS_SYNC_CNT; +static const uint8_t NBITS_DATA = NBITS_FIXED_DATA + NBITS_ENCRYPTED_DATA; + +/* +KeeLoq Protocol + +Coded using information from datasheet for Microchip HCS301 KeeLow Code Hopping Encoder + +Encoder - Hopping code is generated at random. + +Decoder - Hopping code is ignored and not checked when received. Serial number of +transmitter and nutton command is decoded. + +*/ + +void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) { + uint32_t out_data = 0x0; + + ESP_LOGD(TAG, "Send Keeloq: address=%07" PRIx32 " command=%03x encrypted=%08" PRIx32, data.address, data.command, + data.encrypted); + ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA); + + // Preamble = '01' x 12 + for (uint8_t cnt = NBITS_PREAMBLE; cnt; cnt--) { + dst->space(BIT_TIME_US); + dst->mark(BIT_TIME_US); + } + + // Header = 10 bit space + dst->space(10 * BIT_TIME_US); + + // Encrypted field + out_data = data.encrypted; + + ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data); + + for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) { + if (out_data & mask) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + } + + // first 32 bits of fixed portion + out_data = (data.command & 0x0f); + out_data <<= NBITS_SERIAL; + out_data |= data.address; + ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data); + + for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) { + if (out_data & mask) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + } + + // low battery flag + if (data.vlow) { + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + } else { + dst->mark(2 * BIT_TIME_US); + dst->space(1 * BIT_TIME_US); + } + + // repeat flag - always sent as a '1' + dst->mark(1 * BIT_TIME_US); + dst->space(2 * BIT_TIME_US); + + // Guard time at end of packet + dst->space(39 * BIT_TIME_US); +} + +optional KeeloqProtocol::decode(RemoteReceiveData src) { + KeeloqData out{ + .encrypted = 0, + .address = 0, + .command = 0, + .repeat = false, + .vlow = false, + + }; + + if (src.size() != (NBITS_PREAMBLE + NBITS_DATA) * 2) { + return {}; + } + + ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0), + src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8), + src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15), + src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + + // Check preamble bits + int8_t bit = NBITS_PREAMBLE - 1; + while (--bit >= 0) { + if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + return {}; + } + } + if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + return {}; + } + + // Read encrypted bits + uint32_t out_data = 0; + for (bit = 0; bit < NBITS_ENCRYPTED_DATA; bit++) { + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out_data |= 0 << bit; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out_data |= 1 << bit; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek()); + return {}; + } + } + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data); + out.encrypted = out_data; + + // Read Serial Number and Button Status + out_data = 0; + for (bit = 0; bit < NBITS_SERIAL + NBITS_BUTTONS; bit++) { + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out_data |= 0 << bit; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out_data |= 1 << bit; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek()); + return {}; + } + } + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data); + out.command = (out_data >> 28) & 0xf; + out.address = out_data & 0xfffffff; + + // Read Vlow bit + if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) { + out.vlow = false; + } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { + out.vlow = true; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek()); + return {}; + } + + // Read Repeat bit + if (src.expect_mark(2 * BIT_TIME_US) && src.peek_space_at_least(BIT_TIME_US)) { + out.repeat = false; + } else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) { + out.repeat = true; + } else { + ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek()); + return {}; + } + + return out; +} + +void KeeloqProtocol::dump(const KeeloqData &data) { + ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/keeloq_protocol.h b/esphome/components/remote_base/keeloq_protocol.h new file mode 100644 index 000000000000..47125c151be6 --- /dev/null +++ b/esphome/components/remote_base/keeloq_protocol.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct KeeloqData { + uint32_t encrypted; // 32 bit encrypted field + uint32_t address; // 28 bit serial number + uint8_t command; // Button Status S2-S1-S0-S3 + bool repeat; // Repeated command bit + bool vlow; // Battery status bit + + bool operator==(const KeeloqData &rhs) const { + // Treat 0x10 as a special, wildcard button press + // This allows us to match on just the address if wanted. + if (address != rhs.address) { + return false; + } + return (rhs.command == 0x10 || command == rhs.command); + } +}; + +class KeeloqProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const KeeloqData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const KeeloqData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Keeloq) + +template class KeeloqAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint32_t, address) + TEMPLATABLE_VALUE(uint32_t, encrypted) + TEMPLATABLE_VALUE(uint8_t, command) + TEMPLATABLE_VALUE(bool, vlow) + + void encode(RemoteTransmitData *dst, Ts... x) override { + KeeloqData data{}; + data.address = this->address_.value(x...); + data.encrypted = this->encrypted_.value(x...); + data.command = this->command_.value(x...); + data.vlow = this->vlow_.value(x...); + KeeloqProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/lg_protocol.cpp b/esphome/components/remote_base/lg_protocol.cpp index d7d3a5ac7db5..d25c59d2b129 100644 --- a/esphome/components/remote_base/lg_protocol.cpp +++ b/esphome/components/remote_base/lg_protocol.cpp @@ -51,7 +51,7 @@ optional LGProtocol::decode(RemoteReceiveData src) { return out; } void LGProtocol::dump(const LGData &data) { - ESP_LOGI(TAG, "Received LG: data=0x%08X, nbits=%d", data.data, data.nbits); + ESP_LOGI(TAG, "Received LG: data=0x%08" PRIX32 ", nbits=%d", data.data, data.nbits); } } // namespace remote_base diff --git a/esphome/components/remote_base/lg_protocol.h b/esphome/components/remote_base/lg_protocol.h index 6267560443d8..e0039d033da2 100644 --- a/esphome/components/remote_base/lg_protocol.h +++ b/esphome/components/remote_base/lg_protocol.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/magiquest_protocol.cpp b/esphome/components/remote_base/magiquest_protocol.cpp index 76024b1eafc2..1cec58a55f6f 100644 --- a/esphome/components/remote_base/magiquest_protocol.cpp +++ b/esphome/components/remote_base/magiquest_protocol.cpp @@ -76,7 +76,7 @@ optional MagiQuestProtocol::decode(RemoteReceiveData src) { return data; } void MagiQuestProtocol::dump(const MagiQuestData &data) { - ESP_LOGI(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude); + ESP_LOGI(TAG, "Received MagiQuest: wand_id=0x%08" PRIX32 ", magnitude=0x%04X", data.wand_id, data.magnitude); } } // namespace remote_base diff --git a/esphome/components/remote_base/magiquest_protocol.h b/esphome/components/remote_base/magiquest_protocol.h index 909be346d01c..a531a9aee182 100644 --- a/esphome/components/remote_base/magiquest_protocol.h +++ b/esphome/components/remote_base/magiquest_protocol.h @@ -2,6 +2,8 @@ #include "remote_base.h" +#include + /* Based on protocol analysis from * https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html */ diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 6925686b34eb..94fb6f3d94e3 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -67,20 +67,7 @@ class MideaProtocol : public RemoteProtocol { void dump(const MideaData &data) override; }; -class MideaBinarySensor : public RemoteReceiverBinarySensorBase { - public: - bool matches(RemoteReceiveData src) override { - auto data = MideaProtocol().decode(src); - return data.has_value() && data.value() == this->data_; - } - void set_code(const std::vector &code) { this->data_ = code; } - - protected: - MideaData data_; -}; - -using MideaTrigger = RemoteReceiverTrigger; -using MideaDumper = RemoteReceiverDumper; +DECLARE_REMOTE_PROTOCOL(Midea) template class MideaAction : public RemoteTransmitterActionBase { TEMPLATABLE_VALUE(std::vector, code) diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index d5c68784eed0..6ea9a8583c79 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -13,10 +13,14 @@ static const uint32_t BIT_ONE_LOW_US = 1690; static const uint32_t BIT_ZERO_LOW_US = 560; void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { - dst->reserve(68); + ESP_LOGD(TAG, "Sending NEC: address=0x%04X, command=0x%04X command_repeats=%d", data.address, data.command, + data.command_repeats); + + dst->reserve(2 + 32 + 32 * data.command_repeats + 2); dst->set_carrier_frequency(38000); dst->item(HEADER_HIGH_US, HEADER_LOW_US); + for (uint16_t mask = 1; mask; mask <<= 1) { if (data.address & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); @@ -25,11 +29,13 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { } } - for (uint16_t mask = 1; mask; mask <<= 1) { - if (data.command & mask) { - dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - } else { - dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + for (uint16_t repeats = 0; repeats < data.command_repeats; repeats++) { + for (uint16_t mask = 1; mask; mask <<= 1) { + if (data.command & mask) { + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } } @@ -39,6 +45,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { NECData data{ .address = 0, .command = 0, + .command_repeats = 1, }; if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) return {}; @@ -63,11 +70,32 @@ optional NECProtocol::decode(RemoteReceiveData src) { } } + while (src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US) || src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + uint16_t command = 0; + for (uint16_t mask = 1; mask; mask <<= 1) { + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + command |= mask; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + command &= ~mask; + } else { + return {}; + } + } + + // Make sure the extra/repeated data matches original command + if (command != data.command) { + return {}; + } + + data.command_repeats += 1; + } + src.expect_mark(BIT_HIGH_US); return data; } void NECProtocol::dump(const NECData &data) { - ESP_LOGI(TAG, "Received NEC: address=0x%04X, command=0x%04X", data.address, data.command); + ESP_LOGI(TAG, "Received NEC: address=0x%04X, command=0x%04X command_repeats=%d", data.address, data.command, + data.command_repeats); } } // namespace remote_base diff --git a/esphome/components/remote_base/nec_protocol.h b/esphome/components/remote_base/nec_protocol.h index 593a3efe1773..71e1bccba8f7 100644 --- a/esphome/components/remote_base/nec_protocol.h +++ b/esphome/components/remote_base/nec_protocol.h @@ -8,6 +8,7 @@ namespace remote_base { struct NECData { uint16_t address; uint16_t command; + uint16_t command_repeats; bool operator==(const NECData &rhs) const { return address == rhs.address && command == rhs.command; } }; @@ -25,11 +26,13 @@ template class NECAction : public RemoteTransmitterActionBaseaddress_.value(x...); data.command = this->command_.value(x...); + data.command_repeats = this->command_repeats_.value(x...); NECProtocol().encode(dst, data); } }; diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp index f4e7d14187ee..862165714e98 100644 --- a/esphome/components/remote_base/nexa_protocol.cpp +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -232,7 +232,7 @@ optional NexaProtocol::decode(RemoteReceiveData src) { } void NexaProtocol::dump(const NexaData &data) { - ESP_LOGI(TAG, "Received NEXA: device=0x%04X group=%d state=%d channel=%d level=%d", data.device, data.group, + ESP_LOGI(TAG, "Received NEXA: device=0x%04" PRIX32 " group=%d state=%d channel=%d level=%d", data.device, data.group, data.state, data.channel, data.level); } diff --git a/esphome/components/remote_base/nexa_protocol.h b/esphome/components/remote_base/nexa_protocol.h index f1ce38078054..4d9443ce0e85 100644 --- a/esphome/components/remote_base/nexa_protocol.h +++ b/esphome/components/remote_base/nexa_protocol.h @@ -2,6 +2,8 @@ #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/panasonic_protocol.cpp b/esphome/components/remote_base/panasonic_protocol.cpp index 460ca3b164c4..d6cf1a160db7 100644 --- a/esphome/components/remote_base/panasonic_protocol.cpp +++ b/esphome/components/remote_base/panasonic_protocol.cpp @@ -67,7 +67,7 @@ optional PanasonicProtocol::decode(RemoteReceiveData src) { return out; } void PanasonicProtocol::dump(const PanasonicData &data) { - ESP_LOGI(TAG, "Received Panasonic: address=0x%04X, command=0x%08X", data.address, data.command); + ESP_LOGI(TAG, "Received Panasonic: address=0x%04X, command=0x%08" PRIX32, data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/panasonic_protocol.h b/esphome/components/remote_base/panasonic_protocol.h index eae97a8a14fd..c81366138a6c 100644 --- a/esphome/components/remote_base/panasonic_protocol.h +++ b/esphome/components/remote_base/panasonic_protocol.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 4b6977e1a2aa..625af7623580 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -49,13 +49,13 @@ bool ProntoData::operator==(const ProntoData &rhs) const { for (std::vector::size_type i = 0; i < data1.size() - 1; ++i) { int diff = data2[i] - data1[i]; diff *= diff; - if (diff > 9) + if (rhs.delta == -1 && diff > 9) return false; total_diff += diff; } - return total_diff <= data1.size() * 3; + return total_diff <= (rhs.delta == -1 ? data1.size() * 3 : rhs.delta); } // DO NOT EXPORT from this file @@ -222,21 +222,23 @@ optional ProntoProtocol::decode(RemoteReceiveData src) { prontodata += compensate_and_dump_sequence_(data, timebase); out.data = prontodata; + out.delta = -1; return out; } void ProntoProtocol::dump(const ProntoData &data) { - std::string first, rest; - if (data.data.size() < 230) { - first = data.data; - } else { - first = data.data.substr(0, 229); - rest = data.data.substr(230); - } - ESP_LOGI(TAG, "Received Pronto: data=%s", first.c_str()); - if (!rest.empty()) { - ESP_LOGI(TAG, "%s", rest.c_str()); + 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; + } } } diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h index 8b2163af12e5..e600834d1ab0 100644 --- a/esphome/components/remote_base/pronto_protocol.h +++ b/esphome/components/remote_base/pronto_protocol.h @@ -12,6 +12,7 @@ std::vector encode_pronto(const std::string &str); struct ProntoData { std::string data; + int delta; bool operator==(const ProntoData &rhs) const; }; @@ -40,10 +41,12 @@ DECLARE_REMOTE_PROTOCOL(Pronto) template class ProntoAction : public RemoteTransmitterActionBase { public: TEMPLATABLE_VALUE(std::string, data) + TEMPLATABLE_VALUE(int, delta) void encode(RemoteTransmitData *dst, Ts... x) override { ProntoData data{}; data.data = this->data_.value(x...); + data.delta = this->delta_.value(x...); ProntoProtocol().encode(dst, data); } }; diff --git a/esphome/components/remote_base/raw_protocol.cpp b/esphome/components/remote_base/raw_protocol.cpp index 9304aa3e3da4..bdeb935dc4c2 100644 --- a/esphome/components/remote_base/raw_protocol.cpp +++ b/esphome/components/remote_base/raw_protocol.cpp @@ -17,9 +17,9 @@ bool RawDumper::dump(RemoteReceiveData src) { int written; if (i + 1 < src.size() - 1) { - written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value); + written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value); } else { - written = snprintf(buffer + buffer_offset, remaining_length, "%d", value); + written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value); } if (written < 0 || written >= int(remaining_length)) { @@ -29,9 +29,9 @@ bool RawDumper::dump(RemoteReceiveData src) { buffer_offset = 0; written = sprintf(buffer, " "); if (i + 1 < src.size()) { - written += sprintf(buffer + written, "%d, ", value); + written += sprintf(buffer + written, "%" PRId32 ", ", value); } else { - written += sprintf(buffer + written, "%d", value); + written += sprintf(buffer + written, "%" PRId32, value); } } diff --git a/esphome/components/remote_base/raw_protocol.h b/esphome/components/remote_base/raw_protocol.h index 494903daa8dc..9b671e611f4c 100644 --- a/esphome/components/remote_base/raw_protocol.h +++ b/esphome/components/remote_base/raw_protocol.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include #include namespace esphome { diff --git a/esphome/components/remote_base/rc_switch_protocol.h b/esphome/components/remote_base/rc_switch_protocol.h index fc465dbd5d8d..96cbbd1467c7 100644 --- a/esphome/components/remote_base/rc_switch_protocol.h +++ b/esphome/components/remote_base/rc_switch_protocol.h @@ -15,6 +15,8 @@ struct RCSwitchData { class RCSwitchBase { public: + using ProtocolData = RCSwitchData; + RCSwitchBase() = default; RCSwitchBase(uint32_t sync_high, uint32_t sync_low, uint32_t zero_high, uint32_t zero_low, uint32_t one_high, uint32_t one_low, bool inverted); @@ -213,7 +215,7 @@ class RCSwitchDumper : public RemoteReceiverDumperBase { bool dump(RemoteReceiveData src) override; }; -using RCSwitchTrigger = RemoteReceiverTrigger; +using RCSwitchTrigger = RemoteReceiverTrigger; } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 7fe5e47ee71f..fdfd0b43cc19 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -1,6 +1,8 @@ #include "remote_base.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace remote_base { @@ -13,8 +15,11 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + mem_block_num); } +RemoteRMTChannel::RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num) + : channel_(channel), mem_block_num_(mem_block_num) {} + void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { - if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) { + if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) { this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_); } @@ -103,18 +108,18 @@ void RemoteReceiverBase::register_dumper(RemoteReceiverDumperBase *dumper) { void RemoteReceiverBase::call_listeners_() { for (auto *listener : this->listeners_) - listener->on_receive(RemoteReceiveData(this->temp_, this->tolerance_)); + listener->on_receive(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_)); } void RemoteReceiverBase::call_dumpers_() { bool success = false; for (auto *dumper : this->dumpers_) { - if (dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_))) + if (dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_))) success = true; } if (!success) { for (auto *dumper : this->secondary_dumpers_) - dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_)); + dumper->dump(RemoteReceiveData(this->temp_, this->tolerance_, this->tolerance_mode_)); } } @@ -125,7 +130,7 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { const auto &vec = this->temp_.get_data(); char buffer[256]; uint32_t buffer_offset = 0; - buffer_offset += sprintf(buffer, "Sending times=%u wait=%ums: ", send_times, send_wait); + buffer_offset += sprintf(buffer, "Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait); for (size_t i = 0; i < vec.size(); i++) { const int32_t value = vec[i]; @@ -133,9 +138,9 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { int written; if (i + 1 < vec.size()) { - written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value); + written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value); } else { - written = snprintf(buffer + buffer_offset, remaining_length, "%d", value); + written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value); } if (written < 0 || written >= int(remaining_length)) { @@ -145,9 +150,9 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { buffer_offset = 0; written = sprintf(buffer, " "); if (i + 1 < vec.size()) { - written += sprintf(buffer + written, "%d, ", value); + written += sprintf(buffer + written, "%" PRId32 ", ", value); } else { - written += sprintf(buffer + written, "%d", value); + written += sprintf(buffer + written, "%" PRId32, value); } } diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index a4560076550e..c31127735aa0 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -3,10 +3,10 @@ #pragma once +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" -#include "esphome/core/automation.h" -#include "esphome/components/binary_sensor/binary_sensor.h" #ifdef USE_ESP32 #include @@ -15,6 +15,11 @@ namespace esphome { namespace remote_base { +enum ToleranceMode : uint8_t { + TOLERANCE_MODE_PERCENTAGE = 0, + TOLERANCE_MODE_TIME = 1, +}; + using RawTimings = std::vector; class RemoteTransmitData { @@ -42,8 +47,8 @@ class RemoteTransmitData { class RemoteReceiveData { public: - explicit RemoteReceiveData(const RawTimings &data, uint8_t tolerance) - : data_(data), index_(0), tolerance_(tolerance) {} + explicit RemoteReceiveData(const RawTimings &data, uint32_t tolerance, ToleranceMode tolerance_mode) + : data_(data), index_(0), tolerance_(tolerance), tolerance_mode_(tolerance_mode) {} const RawTimings &get_raw_data() const { return this->data_; } uint32_t get_index() const { return index_; } @@ -65,13 +70,35 @@ class RemoteReceiveData { void advance(uint32_t amount = 1) { this->index_ += amount; } void reset() { this->index_ = 0; } + void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) { + this->tolerance_ = tolerance; + this->tolerance_mode_ = tolerance_mode; + } + uint32_t get_tolerance() { return tolerance_; } + ToleranceMode get_tolerance_mode() { return this->tolerance_mode_; } + protected: - int32_t lower_bound_(uint32_t length) const { return int32_t(100 - this->tolerance_) * length / 100U; } - int32_t upper_bound_(uint32_t length) const { return int32_t(100 + this->tolerance_) * length / 100U; } + int32_t lower_bound_(uint32_t length) const { + if (this->tolerance_mode_ == TOLERANCE_MODE_TIME) { + return int32_t(length - this->tolerance_); + } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) { + return int32_t(100 - this->tolerance_) * length / 100U; + } + return 0; + } + int32_t upper_bound_(uint32_t length) const { + if (this->tolerance_mode_ == TOLERANCE_MODE_TIME) { + return int32_t(length + this->tolerance_); + } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) { + return int32_t(100 + this->tolerance_) * length / 100U; + } + return 0; + } const RawTimings &data_; uint32_t index_; - uint8_t tolerance_; + uint32_t tolerance_; + ToleranceMode tolerance_mode_; }; class RemoteComponentBase { @@ -86,6 +113,7 @@ class RemoteComponentBase { class RemoteRMTChannel { public: explicit RemoteRMTChannel(uint8_t mem_block_num = 1); + explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1); void config_rmt(rmt_config_t &rmt); void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } @@ -127,6 +155,14 @@ class RemoteTransmitterBase : public RemoteComponentBase { this->temp_.reset(); return TransmitCall(this); } + template + void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) { + auto call = this->transmit(); + Protocol().encode(call.get_data(), data); + call.set_send_times(send_times); + call.set_send_wait(send_wait); + call.perform(); + } protected: void send_(uint32_t send_times, uint32_t send_wait); @@ -153,7 +189,10 @@ class RemoteReceiverBase : public RemoteComponentBase { RemoteReceiverBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); } void register_dumper(RemoteReceiverDumperBase *dumper); - void set_tolerance(uint8_t tolerance) { tolerance_ = tolerance; } + void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) { + this->tolerance_ = tolerance; + this->tolerance_mode_ = tolerance_mode; + } protected: void call_listeners_(); @@ -167,7 +206,8 @@ class RemoteReceiverBase : public RemoteComponentBase { std::vector dumpers_; std::vector secondary_dumpers_; RawTimings temp_; - uint8_t tolerance_; + uint32_t tolerance_{25}; + ToleranceMode tolerance_mode_{TOLERANCE_MODE_PERCENTAGE}; }; class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensorInitiallyOff, @@ -184,12 +224,13 @@ class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensorInitial template class RemoteProtocol { public: - virtual void encode(RemoteTransmitData *dst, const T &data) = 0; - virtual optional decode(RemoteReceiveData src) = 0; - virtual void dump(const T &data) = 0; + using ProtocolData = T; + virtual void encode(RemoteTransmitData *dst, const ProtocolData &data) = 0; + virtual optional decode(RemoteReceiveData src) = 0; + virtual void dump(const ProtocolData &data) = 0; }; -template class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase { +template class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase { public: RemoteReceiverBinarySensor() : RemoteReceiverBinarySensorBase() {} @@ -201,13 +242,14 @@ template class RemoteReceiverBinarySensor : public Remot } public: - void set_data(D data) { data_ = data; } + void set_data(typename T::ProtocolData data) { data_ = data; } protected: - D data_; + typename T::ProtocolData data_; }; -template class RemoteReceiverTrigger : public Trigger, public RemoteReceiverListener { +template +class RemoteReceiverTrigger : public Trigger, public RemoteReceiverListener { protected: bool on_receive(RemoteReceiveData src) override { auto proto = T(); @@ -220,28 +262,36 @@ template class RemoteReceiverTrigger : public Trigger } }; -template class RemoteTransmitterActionBase : public Action { +class RemoteTransmittable { public: - void set_parent(RemoteTransmitterBase *parent) { this->parent_ = parent; } + RemoteTransmittable() {} + RemoteTransmittable(RemoteTransmitterBase *transmitter) : transmitter_(transmitter) {} + void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; } - TEMPLATABLE_VALUE(uint32_t, send_times); - TEMPLATABLE_VALUE(uint32_t, send_wait); + protected: + template + void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) { + this->transmitter_->transmit(data, send_times, send_wait); + } + RemoteTransmitterBase *transmitter_; +}; +template class RemoteTransmitterActionBase : public RemoteTransmittable, public Action { + TEMPLATABLE_VALUE(uint32_t, send_times) + TEMPLATABLE_VALUE(uint32_t, send_wait) + + protected: void play(Ts... x) override { - auto call = this->parent_->transmit(); + auto call = this->transmitter_->transmit(); this->encode(call.get_data(), x...); call.set_send_times(this->send_times_.value_or(x..., 1)); call.set_send_wait(this->send_wait_.value_or(x..., 0)); call.perform(); } - - protected: virtual void encode(RemoteTransmitData *dst, Ts... x) = 0; - - RemoteTransmitterBase *parent_{}; }; -template class RemoteReceiverDumper : public RemoteReceiverDumperBase { +template class RemoteReceiverDumper : public RemoteReceiverDumperBase { public: bool dump(RemoteReceiveData src) override { auto proto = T(); @@ -254,9 +304,9 @@ template class RemoteReceiverDumper : public RemoteRecei }; #define DECLARE_REMOTE_PROTOCOL_(prefix) \ - using prefix##BinarySensor = RemoteReceiverBinarySensor; \ - using prefix##Trigger = RemoteReceiverTrigger; \ - using prefix##Dumper = RemoteReceiverDumper; + using prefix##BinarySensor = RemoteReceiverBinarySensor; \ + using prefix##Trigger = RemoteReceiverTrigger; \ + using prefix##Dumper = RemoteReceiverDumper; #define DECLARE_REMOTE_PROTOCOL(prefix) DECLARE_REMOTE_PROTOCOL_(prefix) } // namespace remote_base diff --git a/esphome/components/remote_base/roomba_protocol.cpp b/esphome/components/remote_base/roomba_protocol.cpp new file mode 100644 index 000000000000..2d2dde114af1 --- /dev/null +++ b/esphome/components/remote_base/roomba_protocol.cpp @@ -0,0 +1,56 @@ +#include "roomba_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.roomba"; + +static const uint8_t NBITS = 8; +static const uint32_t BIT_ONE_HIGH_US = 3000; +static const uint32_t BIT_ONE_LOW_US = 1000; +static const uint32_t BIT_ZERO_HIGH_US = BIT_ONE_LOW_US; +static const uint32_t BIT_ZERO_LOW_US = BIT_ONE_HIGH_US; + +void RoombaProtocol::encode(RemoteTransmitData *dst, const RoombaData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(NBITS * 2u); + + for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { + if (data.data & mask) { + dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US); + } + } +} +optional RoombaProtocol::decode(RemoteReceiveData src) { + RoombaData out{.data = 0}; + + for (uint8_t i = 0; i < (NBITS - 1); i++) { + out.data <<= 1UL; + if (src.expect_item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US)) { + out.data |= 1UL; + } else if (src.expect_item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US)) { + out.data |= 0UL; + } else { + return {}; + } + } + + // not possible to measure space on last bit, check only mark + out.data <<= 1UL; + if (src.expect_mark(BIT_ONE_HIGH_US)) { + out.data |= 1UL; + } else if (src.expect_mark(BIT_ZERO_HIGH_US)) { + out.data |= 0UL; + } else { + return {}; + } + + return out; +} +void RoombaProtocol::dump(const RoombaData &data) { ESP_LOGD(TAG, "Received Roomba: data=0x%02X", data.data); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/roomba_protocol.h b/esphome/components/remote_base/roomba_protocol.h new file mode 100644 index 000000000000..f94cb7df1b1a --- /dev/null +++ b/esphome/components/remote_base/roomba_protocol.h @@ -0,0 +1,35 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct RoombaData { + uint8_t data; + + bool operator==(const RoombaData &rhs) const { return data == rhs.data; } +}; + +class RoombaProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const RoombaData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const RoombaData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Roomba) + +template class RoombaAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint8_t, data) + + void encode(RemoteTransmitData *dst, Ts... x) override { + RoombaData data{}; + data.data = this->data_.value(x...); + RoombaProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/samsung36_protocol.cpp b/esphome/components/remote_base/samsung36_protocol.cpp index 239698667009..bd3eee584986 100644 --- a/esphome/components/remote_base/samsung36_protocol.cpp +++ b/esphome/components/remote_base/samsung36_protocol.cpp @@ -96,7 +96,7 @@ optional Samsung36Protocol::decode(RemoteReceiveData src) { return out; } void Samsung36Protocol::dump(const Samsung36Data &data) { - ESP_LOGI(TAG, "Received Samsung36: address=0x%04X, command=0x%08X", data.address, data.command); + ESP_LOGI(TAG, "Received Samsung36: address=0x%04X, command=0x%08" PRIX32, data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/samsung36_protocol.h b/esphome/components/remote_base/samsung36_protocol.h index 4ba6226edd19..aa7fd21609fb 100644 --- a/esphome/components/remote_base/samsung36_protocol.h +++ b/esphome/components/remote_base/samsung36_protocol.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index bcd8e4c8cf35..69f2b49c42e3 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -62,7 +62,7 @@ optional SonyProtocol::decode(RemoteReceiveData src) { return out; } void SonyProtocol::dump(const SonyData &data) { - ESP_LOGI(TAG, "Received Sony: data=0x%08X, nbits=%d", data.data, data.nbits); + ESP_LOGI(TAG, "Received Sony: data=0x%08" PRIX32 ", nbits=%d", data.data, data.nbits); } } // namespace remote_base diff --git a/esphome/components/remote_base/sony_protocol.h b/esphome/components/remote_base/sony_protocol.h index aecc8ab91cd3..d9e4f37d53ff 100644 --- a/esphome/components/remote_base/sony_protocol.h +++ b/esphome/components/remote_base/sony_protocol.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "remote_base.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 5737957adbdf..6fe20153f41d 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.components import remote_base +from esphome.components import remote_base, esp32_rmt from esphome.const import ( CONF_BUFFER_SIZE, CONF_DUMP, @@ -10,16 +10,69 @@ CONF_IDLE, CONF_PIN, CONF_TOLERANCE, + CONF_TYPE, CONF_MEMORY_BLOCKS, + CONF_RMT_CHANNEL, + CONF_VALUE, ) from esphome.core import CORE, TimePeriod +CONF_CLOCK_DIVIDER = "clock_divider" + AUTO_LOAD = ["remote_base"] remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") +remote_base_ns = cg.esphome_ns.namespace("remote_base") + +ToleranceMode = remote_base_ns.enum("ToleranceMode") + +TYPE_PERCENTAGE = "percentage" +TYPE_TIME = "time" + +TOLERANCE_MODE = { + TYPE_PERCENTAGE: ToleranceMode.TOLERANCE_MODE_PERCENTAGE, + TYPE_TIME: ToleranceMode.TOLERANCE_MODE_TIME, +} + +TOLERANCE_SCHEMA = cv.typed_schema( + { + TYPE_PERCENTAGE: cv.Schema( + {cv.Required(CONF_VALUE): cv.All(cv.percentage_int, cv.uint32_t)} + ), + TYPE_TIME: cv.Schema( + { + cv.Required(CONF_VALUE): cv.All( + cv.positive_time_period_microseconds, + cv.Range(max=TimePeriod(microseconds=4294967295)), + ) + } + ), + }, + lower=True, + enum=TOLERANCE_MODE, +) + RemoteReceiverComponent = remote_receiver_ns.class_( "RemoteReceiverComponent", remote_base.RemoteReceiverBase, cg.Component ) + +def validate_tolerance(value): + if isinstance(value, dict): + return TOLERANCE_SCHEMA(value) + + if "%" in str(value): + type_ = TYPE_PERCENTAGE + else: + type_ = TYPE_TIME + + return TOLERANCE_SCHEMA( + { + CONF_VALUE: value, + CONF_TYPE: type_, + } + ) + + MULTI_CONF = True CONFIG_SCHEMA = remote_base.validate_triggers( cv.Schema( @@ -27,9 +80,7 @@ cv.GenerateID(): cv.declare_id(RemoteReceiverComponent), cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_DUMP, default=[]): remote_base.validate_dumpers, - cv.Optional(CONF_TOLERANCE, default=25): cv.All( - cv.percentage_int, cv.Range(min=0) - ), + cv.Optional(CONF_TOLERANCE, default="25%"): validate_tolerance, cv.SplitDefault( CONF_BUFFER_SIZE, esp32="10000b", @@ -39,12 +90,17 @@ ): cv.validate_bytes, cv.Optional(CONF_FILTER, default="50us"): cv.All( cv.positive_time_period_microseconds, - cv.Range(max=TimePeriod(microseconds=255)), + cv.Range(max=TimePeriod(microseconds=4294967295)), + ), + cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32=80): cv.All( + cv.only_on_esp32, cv.Range(min=1, max=255) + ), + cv.Optional(CONF_IDLE, default="10ms"): cv.All( + cv.positive_time_period_microseconds, + cv.Range(max=TimePeriod(microseconds=4294967295)), ), - cv.Optional( - CONF_IDLE, default="10ms" - ): cv.positive_time_period_microseconds, cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), + cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False), } ).extend(cv.COMPONENT_SCHEMA) ) @@ -53,7 +109,13 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) if CORE.is_esp32: - var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) + if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: + var = cg.new_Pvariable( + config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] + ) + else: + var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) + cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) else: var = cg.new_Pvariable(config[CONF_ID], pin) @@ -66,7 +128,11 @@ async def to_code(config): cg.add(var.register_listener(trigger)) await cg.register_component(var, config) - cg.add(var.set_tolerance(config[CONF_TOLERANCE])) + cg.add( + var.set_tolerance( + config[CONF_TOLERANCE][CONF_VALUE], config[CONF_TOLERANCE][CONF_TYPE] + ) + ) cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) cg.add(var.set_filter_us(config[CONF_FILTER])) cg.add(var.set_idle_us(config[CONF_IDLE])) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 82c66e3cd01a..773f8cf636be 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -1,7 +1,9 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/remote_base/remote_base.h" +#include "esphome/core/component.h" + +#include namespace esphome { namespace remote_receiver { @@ -20,7 +22,7 @@ struct RemoteReceiverComponentStore { uint32_t buffer_read_at{0}; bool overflow{false}; uint32_t buffer_size{1000}; - uint8_t filter_us{10}; + uint32_t filter_us{10}; ISRInternalGPIOPin pin; }; #endif @@ -36,6 +38,9 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, #ifdef USE_ESP32 RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} + + RemoteReceiverComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1) + : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {} #else RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {} #endif @@ -45,7 +50,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, float get_setup_priority() const override { return setup_priority::DATA; } void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; } - void set_filter_us(uint8_t filter_us) { this->filter_us_ = filter_us; } + void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; } void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } protected: @@ -53,6 +58,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, void decode_rmt_(rmt_item32_t *item, size_t len); RingbufHandle_t ringbuf_; esp_err_t error_code_{ESP_OK}; + std::string error_string_{""}; #endif #if defined(USE_ESP8266) || defined(USE_LIBRETINY) @@ -61,7 +67,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, #endif uint32_t buffer_size_{}; - uint8_t filter_us_{10}; + uint32_t filter_us_{10}; uint32_t idle_us_{10000}; }; diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 5a7fb3c98551..91295871e24f 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -20,13 +20,16 @@ void RemoteReceiverComponent::setup() { rmt.rx_config.filter_en = false; } else { rmt.rx_config.filter_en = true; - rmt.rx_config.filter_ticks_thresh = this->from_microseconds_(this->filter_us_); + rmt.rx_config.filter_ticks_thresh = static_cast( + std::min(this->from_microseconds_(this->filter_us_) * this->clock_divider_, (uint32_t) 255)); } - rmt.rx_config.idle_threshold = this->from_microseconds_(this->idle_us_); + rmt.rx_config.idle_threshold = + static_cast(std::min(this->from_microseconds_(this->idle_us_), (uint32_t) 65535)); esp_err_t error = rmt_config(&rmt); if (error != ESP_OK) { this->error_code_ = error; + this->error_string_ = "in rmt_config"; this->mark_failed(); return; } @@ -34,18 +37,25 @@ void RemoteReceiverComponent::setup() { error = rmt_driver_install(this->channel_, this->buffer_size_, 0); if (error != ESP_OK) { this->error_code_ = error; + if (error == ESP_ERR_INVALID_STATE) { + this->error_string_ = str_sprintf("RMT channel %i is already in use by another component", this->channel_); + } else { + this->error_string_ = "in rmt_driver_install"; + } this->mark_failed(); return; } error = rmt_get_ringbuf_handle(this->channel_, &this->ringbuf_); if (error != ESP_OK) { this->error_code_ = error; + this->error_string_ = "in rmt_get_ringbuf_handle"; this->mark_failed(); return; } error = rmt_rx_start(this->channel_, true); if (error != ESP_OK) { this->error_code_ = error; + this->error_string_ = "in rmt_rx_start"; this->mark_failed(); return; } @@ -60,11 +70,13 @@ void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " RMT memory blocks: %d", this->mem_block_num_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); - ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_); - ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %u us", this->filter_us_); - ESP_LOGCONFIG(TAG, " Signal is done after %u us of no changes", this->idle_us_); + ESP_LOGCONFIG(TAG, " Tolerance: %" PRIu32 "%s", this->tolerance_, + (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); + ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %" PRIu32 " us", this->filter_us_); + ESP_LOGCONFIG(TAG, " Signal is done after %" PRIu32 " us of no changes", this->idle_us_); if (this->is_failed()) { - ESP_LOGE(TAG, "Configuring RMT driver failed: %s", esp_err_to_name(this->error_code_)); + ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), + this->error_string_.c_str()); } } @@ -88,18 +100,23 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { this->temp_.clear(); int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; size_t item_count = len / sizeof(rmt_item32_t); + uint32_t filter_ticks = this->from_microseconds_(this->filter_us_); ESP_LOGVV(TAG, "START:"); for (size_t i = 0; i < item_count; i++) { if (item[i].level0) { - ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%zu A: ON %" PRIu32 "us (%u ticks)", i, this->to_microseconds_(item[i].duration0), + item[i].duration0); } else { - ESP_LOGVV(TAG, "%u A: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); + ESP_LOGVV(TAG, "%zu A: OFF %" PRIu32 "us (%u ticks)", i, this->to_microseconds_(item[i].duration0), + item[i].duration0); } if (item[i].level1) { - ESP_LOGVV(TAG, "%u B: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%zu B: ON %" PRIu32 "us (%u ticks)", i, this->to_microseconds_(item[i].duration1), + item[i].duration1); } else { - ESP_LOGVV(TAG, "%u B: OFF %uus (%u ticks)", i, this->to_microseconds_(item[i].duration1), item[i].duration1); + ESP_LOGVV(TAG, "%zu B: OFF %" PRIu32 "us (%u ticks)", i, this->to_microseconds_(item[i].duration1), + item[i].duration1); } } ESP_LOGVV(TAG, "\n"); @@ -108,7 +125,7 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { for (size_t i = 0; i < item_count; i++) { if (item[i].duration0 == 0u) { // Do nothing - } else if (bool(item[i].level0) == prev_level) { + } else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) { prev_length += item[i].duration0; } else { if (prev_length > 0) { @@ -124,7 +141,7 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { if (item[i].duration1 == 0u) { // Do nothing - } else if (bool(item[i].level1) == prev_level) { + } else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) { prev_length += item[i].duration1; } else { if (prev_length > 0) { diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index 74f244746a04..461b1169e39d 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -64,7 +64,8 @@ void RemoteReceiverComponent::dump_config() { "invert the signal using 'inverted: True' in the pin schema!"); } ESP_LOGCONFIG(TAG, " Buffer Size: %u", this->buffer_size_); - ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_); + ESP_LOGCONFIG(TAG, " Tolerance: %u%s", this->tolerance_, + (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %u us", this->filter_us_); ESP_LOGCONFIG(TAG, " Signal is done after %u us of no changes", this->idle_us_); } diff --git a/esphome/components/remote_receiver/remote_receiver_libretiny.cpp b/esphome/components/remote_receiver/remote_receiver_libretiny.cpp new file mode 100644 index 000000000000..bfc29b421122 --- /dev/null +++ b/esphome/components/remote_receiver/remote_receiver_libretiny.cpp @@ -0,0 +1,123 @@ +#include "remote_receiver.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace remote_receiver { + +static const char *const TAG = "remote_receiver.libretiny"; + +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; + + // 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; +} + +void RemoteReceiverComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); + 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->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); +} +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", this->buffer_size_); + ESP_LOGCONFIG(TAG, " Tolerance: %u%s", this->tolerance_, + (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); + ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %u us", this->filter_us_); + ESP_LOGCONFIG(TAG, " Signal is done after %u us of no changes", this->idle_us_); +} + +void RemoteReceiverComponent::loop() { + 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; + } + + 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; + } + + 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); + + this->call_listeners_dumpers_(); +} + +} // namespace remote_receiver +} // namespace esphome + +#endif diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index e09e4c7f558e..d203ff3417f2 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.components import remote_base -from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN +from esphome.components import remote_base, esp32_rmt +from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, CONF_RMT_CHANNEL AUTO_LOAD = ["remote_base"] remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") @@ -18,13 +18,17 @@ cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( cv.percentage_int, cv.Range(min=1, max=100) ), + cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), } ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) - var = cg.new_Pvariable(config[CONF_ID], pin) + if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: + var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) + else: + var = cg.new_Pvariable(config[CONF_ID], pin) await cg.register_component(var, config) cg.add(var.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT])) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 686a6ec09b03..b897fa8fab73 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -1,7 +1,7 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/components/remote_base/remote_base.h" +#include "esphome/core/component.h" #include @@ -16,8 +16,15 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif { public: - explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} +#ifdef USE_ESP32 + RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) + : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} + RemoteTransmitterComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1) + : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {} +#else + explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} +#endif void setup() override; void dump_config() override; @@ -46,6 +53,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, bool initialized_{false}; std::vector rmt_temp_; esp_err_t error_code_{ESP_OK}; + std::string error_string_{""}; bool inverted_{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 c3d4d42e4f7f..eea35019ffa1 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -23,7 +23,8 @@ void RemoteTransmitterComponent::dump_config() { } if (this->is_failed()) { - ESP_LOGE(TAG, "Configuring RMT driver failed: %s", esp_err_to_name(this->error_code_)); + ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), + this->error_string_.c_str()); } } @@ -56,6 +57,7 @@ void RemoteTransmitterComponent::configure_rmt_() { esp_err_t error = rmt_config(&c); if (error != ESP_OK) { this->error_code_ = error; + this->error_string_ = "in rmt_config"; this->mark_failed(); return; } @@ -64,6 +66,11 @@ void RemoteTransmitterComponent::configure_rmt_() { error = rmt_driver_install(this->channel_, 0, 0); if (error != ESP_OK) { this->error_code_ = error; + if (error == ESP_ERR_INVALID_STATE) { + this->error_string_ = str_sprintf("RMT channel %i is already in use by another component", this->channel_); + } else { + this->error_string_ = "in rmt_driver_install"; + } this->mark_failed(); return; } diff --git a/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp b/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp new file mode 100644 index 000000000000..78bb28048280 --- /dev/null +++ b/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp @@ -0,0 +1,104 @@ +#include "remote_transmitter.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace remote_transmitter { + +static const char *const TAG = "remote_transmitter"; + +void RemoteTransmitterComponent::setup() { + this->pin_->setup(); + this->pin_->digital_write(false); +} + +void RemoteTransmitterComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Remote Transmitter..."); + ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_); + LOG_PIN(" Pin: ", this->pin_); +} + +void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, + uint32_t *off_time_period) { + if (carrier_frequency == 0) { + *on_time_period = 0; + *off_time_period = 0; + return; + } + uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq) + period = std::max(uint32_t(1), period); + *on_time_period = (period * this->carrier_duty_percent_) / 100; + *off_time_period = period - *on_time_period; +} + +void RemoteTransmitterComponent::await_target_time_() { + const uint32_t current_time = micros(); + if (this->target_time_ == 0) { + this->target_time_ = current_time; + } else { + while (this->target_time_ > micros()) { + // busy loop that ensures micros is constantly called + } + } +} + +void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { + this->await_target_time_(); + this->pin_->digital_write(true); + + const uint32_t target = this->target_time_ + usec; + if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) { + while (true) { // Modulate with carrier frequency + this->target_time_ += on_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(false); + + this->target_time_ += off_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(true); + } + } + this->target_time_ = target; +} + +void RemoteTransmitterComponent::space_(uint32_t usec) { + this->await_target_time_(); + this->pin_->digital_write(false); + this->target_time_ += usec; +} + +void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { + ESP_LOGD(TAG, "Sending remote code..."); + uint32_t on_time, off_time; + this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); + this->target_time_ = 0; + for (uint32_t i = 0; i < send_times; i++) { + InterruptLock lock; + for (int32_t item : this->temp_.get_data()) { + if (item > 0) { + const auto length = uint32_t(item); + this->mark_(on_time, off_time, length); + } else { + const auto length = uint32_t(-item); + this->space_(length); + } + App.feed_wdt(); + } + this->await_target_time_(); // wait for duration of last pulse + this->pin_->digital_write(false); + + if (i + 1 < send_times) + this->target_time_ += send_wait; + } +} + +} // namespace remote_transmitter +} // namespace esphome + +#endif diff --git a/esphome/components/resistance/resistance_sensor.h b/esphome/components/resistance/resistance_sensor.h index b57f90b59c86..8fa1f8b57092 100644 --- a/esphome/components/resistance/resistance_sensor.h +++ b/esphome/components/resistance/resistance_sensor.h @@ -1,7 +1,8 @@ #pragma once -#include "esphome/core/component.h" +#include "esphome/components/resistance_sampler/resistance_sampler.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" namespace esphome { namespace resistance { @@ -11,7 +12,7 @@ enum ResistanceConfiguration { DOWNSTREAM, }; -class ResistanceSensor : public Component, public sensor::Sensor { +class ResistanceSensor : public Component, public sensor::Sensor, resistance_sampler::ResistanceSampler { public: void set_sensor(Sensor *sensor) { sensor_ = sensor; } void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; } diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index 55e7ddfc8141..ce4459fc6de5 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -1,17 +1,24 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor +from esphome.components import sensor, resistance_sampler from esphome.const import ( + CONF_REFERENCE_VOLTAGE, CONF_SENSOR, STATE_CLASS_MEASUREMENT, UNIT_OHM, ICON_FLASH, ) +AUTO_LOAD = ["resistance_sampler"] + resistance_ns = cg.esphome_ns.namespace("resistance") -ResistanceSensor = resistance_ns.class_("ResistanceSensor", cg.Component, sensor.Sensor) +ResistanceSensor = resistance_ns.class_( + "ResistanceSensor", + cg.Component, + sensor.Sensor, + resistance_sampler.ResistanceSampler, +) -CONF_REFERENCE_VOLTAGE = "reference_voltage" CONF_CONFIGURATION = "configuration" CONF_RESISTOR = "resistor" diff --git a/esphome/components/resistance_sampler/__init__.py b/esphome/components/resistance_sampler/__init__.py new file mode 100644 index 000000000000..d2032848aa79 --- /dev/null +++ b/esphome/components/resistance_sampler/__init__.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +resistance_sampler_ns = cg.esphome_ns.namespace("resistance_sampler") +ResistanceSampler = resistance_sampler_ns.class_("ResistanceSampler") + +CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/resistance_sampler/resistance_sampler.h b/esphome/components/resistance_sampler/resistance_sampler.h new file mode 100644 index 000000000000..9e300bebcc54 --- /dev/null +++ b/esphome/components/resistance_sampler/resistance_sampler.h @@ -0,0 +1,10 @@ +#pragma once + +namespace esphome { +namespace resistance_sampler { + +/// Abstract interface to mark components that provide resistance values. +class ResistanceSampler {}; + +} // namespace resistance_sampler +} // namespace esphome diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index c34b3d2dc463..3b3e00a416fb 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -1,5 +1,6 @@ #include "rf_bridge.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -53,8 +54,10 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { ESP_LOGD(TAG, "Learning success"); } - ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, - data.high, data.code); + ESP_LOGI(TAG, + "Received RFBridge Code: sync=0x%04" PRIX16 " low=0x%04" PRIX16 " high=0x%04" PRIX16 + " code=0x%06" PRIX32, + data.sync, data.low, data.high, data.code); this->data_callback_.call(data); break; } @@ -144,8 +147,8 @@ void RFBridgeComponent::loop() { } void RFBridgeComponent::send_code(RFBridgeData data) { - ESP_LOGD(TAG, "Sending code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, data.high, - data.code); + ESP_LOGD(TAG, "Sending code: sync=0x%04" PRIX16 " low=0x%04" PRIX16 " high=0x%04" PRIX16 " code=0x%06" PRIX32, + data.sync, data.low, data.high, data.code); this->write(RF_CODE_START); this->write(RF_CODE_RFOUT); this->write((data.sync >> 8) & 0xFF); diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 7440214b1c2a..a3631ffe2758 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -226,6 +226,7 @@ void RotaryEncoderSensor::loop() { } this->store_.last_read = counter; this->publish_state(counter); + this->listeners_.call(counter); this->publish_initial_value_ = false; } } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index deba3d952d9d..e88ee9152af8 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -92,6 +92,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { this->on_anticlockwise_callback_.add(std::move(callback)); } + void register_listener(std::function listener) { this->listeners_.add(std::move(listener)); } + protected: InternalGPIOPin *pin_a_; InternalGPIOPin *pin_b_; @@ -102,8 +104,9 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { RotaryEncoderSensorStore store_{}; - CallbackManager on_clockwise_callback_; - CallbackManager on_anticlockwise_callback_; + CallbackManager on_clockwise_callback_{}; + CallbackManager on_anticlockwise_callback_{}; + CallbackManager listeners_{}; }; template class RotaryEncoderSetValueAction : public Action { diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index dafafc531c0d..ace455add79f 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -14,6 +14,8 @@ KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_RP2040, + CONF_PLATFORM_VERSION, ) from esphome.core import CORE, coroutine_with_priority, EsphomeError from esphome.helpers import mkdir_p, write_file, copy_file_if_changed @@ -30,7 +32,7 @@ def set_core_data(config): CORE.data[KEY_RP2040] = {} - CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040" + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_RP2040 CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( config[CONF_FRAMEWORK][CONF_VERSION] @@ -42,6 +44,17 @@ def set_core_data(config): return config +def get_download_types(storage_json): + return [ + { + "title": "UF2 format", + "description": "For copying to RP2040 over USB.", + "file": "firmware.uf2", + "download": f"{storage_json.name}.uf2", + }, + ] + + def _format_framework_arduino_version(ver: cv.Version) -> str: # The most recent releases have not been uploaded to platformio so grabbing them directly from # the GitHub release is one path forward for now. @@ -62,19 +75,19 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/earlephilhower/arduino-pico/releases # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 3, 0) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 7, 2) # The platformio/raspberrypi version to use for arduino frameworks # - https://github.com/platformio/platform-raspberrypi/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi -ARDUINO_PLATFORM_VERSION = cv.Version(1, 9, 0) +ARDUINO_PLATFORM_VERSION = cv.Version(1, 12, 0) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(3, 3, 0), "https://github.com/earlephilhower/arduino-pico"), - "latest": (cv.Version(3, 3, 0), None), + "dev": (cv.Version(3, 4, 0), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(3, 4, 0), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } @@ -113,8 +126,6 @@ def _parse_platform_version(value): return value -CONF_PLATFORM_VERSION = "platform_version" - ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { @@ -141,6 +152,9 @@ def _parse_platform_version(value): async def to_code(config): cg.add(rp2040_ns.setup_preferences()) + # Allow LDF to properly discover dependency including those in preprocessor + # conditionals + cg.add_platformio_option("lib_ldf_mode", "chain+") cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_RP2040") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) diff --git a/esphome/components/rp2040/gpio.py b/esphome/components/rp2040/gpio.py index 4823a6d22aeb..6ba0975a2c1c 100644 --- a/esphome/components/rp2040/gpio.py +++ b/esphome/components/rp2040/gpio.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( - CONF_ANALOG, CONF_ID, CONF_INPUT, CONF_INVERTED, @@ -11,6 +10,7 @@ CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, + CONF_ANALOG, ) from esphome.core import CORE from esphome import pins @@ -78,22 +78,10 @@ def validate_supports(value): RP2040_PIN_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(RP2040GPIOPin), - cv.Required(CONF_NUMBER): validate_gpio_pin, - cv.Optional(CONF_MODE, default={}): cv.Schema( - { - cv.Optional(CONF_ANALOG, default=False): cv.boolean, - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, - } - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } + pins.gpio_base_schema( + RP2040GPIOPin, + validate_gpio_pin, + modes=pins.GPIO_STANDARD_MODES + (CONF_ANALOG,), ), validate_supports, ) diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.cpp b/esphome/components/rp2040_pio_led_strip/led_strip.cpp index ce1836306ff2..c04419a9bfe1 100644 --- a/esphome/components/rp2040_pio_led_strip/led_strip.cpp +++ b/esphome/components/rp2040_pio_led_strip/led_strip.cpp @@ -70,9 +70,10 @@ void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) { // assemble bits in buffer to 32 bit words with ex for GBR: 0bGGGGGGGGRRRRRRRRBBBBBBBB00000000 for (int i = 0; i < this->num_leds_; i++) { - uint8_t c1 = this->buf_[(i * 3) + 0]; - uint8_t c2 = this->buf_[(i * 3) + 1]; - uint8_t c3 = this->buf_[(i * 3) + 2]; + uint8_t multiplier = this->is_rgbw_ ? 4 : 3; + uint8_t c1 = this->buf_[(i * multiplier) + 0]; + uint8_t c2 = this->buf_[(i * multiplier) + 1]; + uint8_t c3 = this->buf_[(i * multiplier) + 2]; uint8_t w = this->is_rgbw_ ? this->buf_[(i * 4) + 3] : 0; uint32_t color = encode_uint32(c1, c2, c3, w); pio_sm_put_blocking(this->pio_, this->sm_, color); diff --git a/esphome/components/rpi_dpi_rgb/__init__.py b/esphome/components/rpi_dpi_rgb/__init__.py new file mode 100644 index 000000000000..c58ce8a01e84 --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py new file mode 100644 index 000000000000..969b9db78e96 --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -0,0 +1,197 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_HSYNC_PIN, + CONF_RESET_PIN, + CONF_DATA_PINS, + CONF_ID, + CONF_IGNORE_STRAPPING_WARNING, + CONF_DIMENSIONS, + CONF_VSYNC_PIN, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_COLOR_ORDER, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_NUMBER, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, +) +from esphome.components.esp32 import ( + only_on_variant, + const, +) + +DEPENDENCIES = ["esp32"] + +CONF_DE_PIN = "de_pin" +CONF_PCLK_PIN = "pclk_pin" + +CONF_HSYNC_FRONT_PORCH = "hsync_front_porch" +CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width" +CONF_HSYNC_BACK_PORCH = "hsync_back_porch" +CONF_VSYNC_FRONT_PORCH = "vsync_front_porch" +CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width" +CONF_VSYNC_BACK_PORCH = "vsync_back_porch" +CONF_PCLK_FREQUENCY = "pclk_frequency" +CONF_PCLK_INVERTED = "pclk_inverted" + +rpi_dpi_rgb_ns = cg.esphome_ns.namespace("rpi_dpi_rgb") +RPI_DPI_RGB = rpi_dpi_rgb_ns.class_("RpiDpiRgb", display.Display, cg.Component) +ColorOrder = display.display_ns.enum("ColorMode") + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + + +def data_pin_validate(value): + """ + It is safe to use strapping pins as RGB output data bits, as they are outputs only, + and not initialised until after boot. + """ + if not isinstance(value, dict): + try: + return DATA_PIN_SCHEMA( + {CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True} + ) + except cv.Invalid: + pass + return DATA_PIN_SCHEMA(value) + + +def data_pin_set(length): + return cv.All( + [data_pin_validate], + cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"), + ) + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RPI_DPI_RGB), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All( + cv.frequency, cv.Range(min=4e6, max=30e6) + ), + cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean, + cv.Required(CONF_DATA_PINS): cv.Any( + data_pin_set(16), + cv.Schema( + { + cv.Required(CONF_RED): data_pin_set(5), + cv.Required(CONF_GREEN): data_pin_set(6), + cv.Required(CONF_BLUE): data_pin_set(5), + } + ), + ), + cv.Optional(CONF_COLOR_ORDER): cv.one_of( + *COLOR_ORDERS.keys(), upper=True + ), + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_, + cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_, + } + ) + ), + only_on_variant(supported=[const.VARIANT_ESP32S3]), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + + cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) + cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) + cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH])) + cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) + cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) + 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] + 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) + # swap bytes to match big-endian format + dpins = dpins[8:16] + dpins[0:8] + else: + dpins = config[CONF_DATA_PINS] + for pin in dpins: + data_pin = await cg.gpio_pin_expression(pin) + cg.add(var.add_data_pin(data_pin, index)) + index += 1 + + 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_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + + pin = await cg.gpio_pin_expression(config[CONF_DE_PIN]) + cg.add(var.set_de_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN]) + cg.add(var.set_pclk_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN]) + cg.add(var.set_hsync_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN]) + cg.add(var.set_vsync_pin(pin)) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp new file mode 100644 index 000000000000..2ffdb3272a20 --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -0,0 +1,116 @@ +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "rpi_dpi_rgb.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rpi_dpi_rgb { + +void RpiDpiRgb::setup() { + esph_log_config(TAG, "Setting up RPI_DPI_RGB"); + esp_lcd_rgb_panel_config_t config{}; + config.flags.fb_in_psram = 1; + config.timings.h_res = this->width_; + config.timings.v_res = this->height_; + config.timings.hsync_pulse_width = this->hsync_pulse_width_; + config.timings.hsync_back_porch = this->hsync_back_porch_; + config.timings.hsync_front_porch = this->hsync_front_porch_; + config.timings.vsync_pulse_width = this->vsync_pulse_width_; + config.timings.vsync_back_porch = this->vsync_back_porch_; + config.timings.vsync_front_porch = this->vsync_front_porch_; + config.timings.flags.pclk_active_neg = this->pclk_inverted_; + config.timings.pclk_hz = this->pclk_frequency_; + config.clk_src = LCD_CLK_SRC_PLL160M; + config.sram_trans_align = 64; + config.psram_trans_align = 64; + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) { + config.data_gpio_nums[i] = this->data_pins_[i]->get_pin(); + } + config.data_width = data_pin_count; + config.disp_gpio_num = -1; + config.hsync_gpio_num = this->hsync_pin_->get_pin(); + config.vsync_gpio_num = this->vsync_pin_->get_pin(); + config.de_gpio_num = this->de_pin_->get_pin(); + config.pclk_gpio_num = this->pclk_pin_->get_pin(); + esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); + if (err != ESP_OK) { + esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); + esph_log_config(TAG, "RPI_DPI_RGB setup complete"); +} + +void RpiDpiRgb::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) { + if (w <= 0 || h <= 0) + return; + // if color mapping is required, pass the buck. + // note that endianness is not considered here - it is assumed to match! + if (bitness != display::COLOR_BITNESS_565) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + x_start += this->offset_x_; + y_start += this->offset_y_; + esp_err_t err; + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr); + } else { + // draw line by line + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, + ptr + ((y + y_offset) * stride + x_offset) * 2); + if (err != ESP_OK) + break; + } + } + if (err != ESP_OK) + esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); +} + +void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { + if (!this->get_clipping().inside(x, y)) + return; // NOLINT + + switch (this->rotation_) { + case display::DISPLAY_ROTATION_0_DEGREES: + break; + case display::DISPLAY_ROTATION_90_DEGREES: + std::swap(x, y); + x = this->width_ - x - 1; + break; + case display::DISPLAY_ROTATION_180_DEGREES: + x = this->width_ - x - 1; + y = this->height_ - y - 1; + break; + case display::DISPLAY_ROTATION_270_DEGREES: + std::swap(x, y); + y = this->height_ - y - 1; + break; + } + auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color)); + + this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true, + 0, 0, 0); + App.feed_wdt(); +} + +void RpiDpiRgb::dump_config() { + ESP_LOGCONFIG("", "RPI_DPI_RGB LCD"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + LOG_PIN(" DE Pin: ", this->de_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) + ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str()); +} + +} // namespace rpi_dpi_rgb +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h new file mode 100644 index 000000000000..0319b46391cf --- /dev/null +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -0,0 +1,92 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +// only applicable on ESP32-S3 +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "esphome/core/component.h" +#include "esphome/core/gpio.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/display/display.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace rpi_dpi_rgb { + +constexpr static const char *const TAG = "rpi_dpi_rgb"; + +class RpiDpiRgb : public display::Display { + public: + void update() override { this->do_update_(); } + void setup() 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; + void draw_pixel_at(int x, int y, Color color) override; + + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; }; + void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; } + void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; } + void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; } + void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; } + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } + void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } + void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } + void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } + void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } + void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } + void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } + void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + void dump_config() override; + + protected: + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + InternalGPIOPin *de_pin_{nullptr}; + InternalGPIOPin *pclk_pin_{nullptr}; + InternalGPIOPin *hsync_pin_{nullptr}; + InternalGPIOPin *vsync_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + InternalGPIOPin *data_pins_[16] = {}; + uint16_t hsync_front_porch_ = 8; + uint16_t hsync_pulse_width_ = 4; + uint16_t hsync_back_porch_ = 8; + uint16_t vsync_front_porch_ = 8; + uint16_t vsync_pulse_width_ = 4; + uint16_t vsync_back_porch_ = 8; + uint32_t pclk_frequency_ = 16 * 1000 * 1000; + bool pclk_inverted_{true}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace rpi_dpi_rgb +} // namespace esphome +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/rtl87xx/boards.py b/esphome/components/rtl87xx/boards.py index 6c29467f6e5d..e737767a56e9 100644 --- a/esphome/components/rtl87xx/boards.py +++ b/esphome/components/rtl87xx/boards.py @@ -36,6 +36,10 @@ "name": "T103_V1.0", "family": FAMILY_RTL8710B, }, + "t112-v1.1": { + "name": "T112_V1.1", + "family": FAMILY_RTL8710B, + }, "wr1": { "name": "WR1 Wi-Fi Module", "family": FAMILY_RTL8710B, @@ -125,7 +129,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -136,9 +139,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -180,11 +181,9 @@ "SERIAL2_RTS": 20, "SERIAL2_RX": 15, "SERIAL2_TX": 16, - "CS0": 15, "CTS1": 4, "CTS2": 19, "MISO0": 20, - "MOSI0": 19, "PA00": 0, "PA0": 0, "PA01": 1, @@ -203,23 +202,15 @@ "PA18": 18, "PA19": 19, "PA20": 20, - "PWM0": 0, "PWM1": 1, - "PWM2": 14, - "PWM3": 3, - "PWM4": 16, "PWM5": 17, "PWM6": 18, - "PWM7": 13, "RTS2": 20, "RX0": 13, - "RX1": 0, "RX2": 15, - "SCK0": 3, "SCL0": 19, "SDA0": 3, "TX0": 14, - "TX1": 1, "TX2": 16, "D0": 17, "D1": 18, @@ -294,7 +285,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -305,9 +295,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -390,7 +378,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -401,9 +388,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -485,7 +470,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -496,9 +480,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -560,7 +542,6 @@ "CTS0": 10, "CTS1": 4, "CTS2": 19, - "MISO0": 20, "MOSI0": 19, "PA00": 0, "PA0": 0, @@ -591,23 +572,13 @@ "PA20": 20, "PA23": 23, "PWM0": 20, - "PWM1": 12, - "PWM2": 14, - "PWM3": 15, - "PWM4": 16, "PWM5": 17, "PWM6": 18, "PWM7": 23, "RTS0": 9, "RTS2": 20, - "RX0": 13, - "RX1": 2, "RX2": 15, "SCK0": 16, - "SCL0": 19, - "SDA0": 20, - "TX0": 14, - "TX1": 3, "TX2": 16, "D0": 0, "D1": 1, @@ -652,7 +623,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -720,7 +690,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -731,9 +700,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -751,6 +718,75 @@ "A0": 19, "A1": 41, }, + "t112-v1.1": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 19, + "D2": 15, + "D3": 14, + "D4": 0, + "D5": 5, + "D6": 18, + "D7": 12, + "D8": 23, + "D9": 22, + "D10": 30, + "A0": 19, + }, "wr1": { "SPI0_CS": 19, "SPI0_MISO": 22, @@ -793,7 +829,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM4": 29, @@ -803,9 +838,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -863,7 +896,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM3": 12, "PWM4": 29, @@ -873,9 +905,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -915,7 +945,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -969,7 +998,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 14, "PWM1": 15, "PWM3": 12, "PWM4": 29, @@ -979,7 +1007,6 @@ "SCK1": 18, "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1083,7 +1110,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1094,9 +1120,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1157,7 +1181,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1168,9 +1191,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1231,7 +1252,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1242,9 +1262,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 29, "SCL1": 18, - "SDA0": 30, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1305,7 +1323,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, @@ -1316,9 +1333,7 @@ "RX2": 29, "SCK0": 18, "SCK1": 18, - "SCL0": 22, "SCL1": 18, - "SDA0": 19, "SDA1": 23, "TX0": 23, "TX2": 30, @@ -1359,7 +1374,6 @@ "PA23": 23, "PA29": 29, "PA30": 30, - "PWM0": 23, "PWM1": 15, "PWM2": 0, "PWM3": 12, diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index e9453896ac3f..10f13134088b 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -4,7 +4,16 @@ import esphome.final_validate as fv from esphome import automation from esphome.components.output import FloatOutput -from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID +from esphome.components.speaker import Speaker + +from esphome.const import ( + CONF_ID, + CONF_OUTPUT, + CONF_PLATFORM, + CONF_TRIGGER_ID, + CONF_SPEAKER, + CONF_GAIN, +) _LOGGER = logging.getLogger(__name__) @@ -24,17 +33,24 @@ MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(CONF_ID): cv.declare_id(Rtttl), - cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput), - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FinishedPlaybackTrigger), - } - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(Rtttl), + cv.Optional(CONF_OUTPUT): cv.use_id(FloatOutput), + cv.Optional(CONF_SPEAKER): cv.use_id(Speaker), + cv.Optional(CONF_GAIN, default="0.6"): cv.percentage, + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FinishedPlaybackTrigger + ), + } + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_OUTPUT, CONF_SPEAKER), +) def validate_parent_output_config(value): @@ -63,9 +79,9 @@ def validate_parent_output_config(value): FINAL_VALIDATE_SCHEMA = cv.Schema( { - cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema( + cv.Optional(CONF_OUTPUT): fv.id_declaration_match_schema( validate_parent_output_config - ) + ), }, extra=cv.ALLOW_EXTRA, ) @@ -75,8 +91,16 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - out = await cg.get_variable(config[CONF_OUTPUT]) - cg.add(var.set_output(out)) + if CONF_OUTPUT in config: + out = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(out)) + cg.add_define("USE_OUTPUT") + + if CONF_SPEAKER in config: + out = await cg.get_variable(config[CONF_SPEAKER]) + cg.add(var.set_speaker(out)) + + cg.add(var.set_gain(config[CONF_GAIN])) for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 6274e69ba34c..0bdf65b7bdae 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -1,4 +1,5 @@ #include "rtttl.h" +#include #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -15,104 +16,185 @@ static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951}; +static const uint16_t I2S_SPEED = 1000; + +#undef HALF_PI +static const double HALF_PI = 1.5707963267948966192313216916398; + +inline double deg2rad(double degrees) { + static const double PI_ON_180 = 4.0 * atan(1.0) / 180.0; + return degrees * PI_ON_180; +} + void Rtttl::dump_config() { ESP_LOGCONFIG(TAG, "Rtttl"); } void Rtttl::play(std::string rtttl) { - rtttl_ = std::move(rtttl); + this->rtttl_ = std::move(rtttl); + + this->default_duration_ = 4; + this->default_octave_ = 6; + this->note_duration_ = 0; - default_duration_ = 4; - default_octave_ = 6; int bpm = 63; uint8_t num; // Get name - position_ = rtttl_.find(':'); + this->position_ = rtttl_.find(':'); // it's somewhat documented to be up to 10 characters but let's be a bit flexible here - if (position_ == std::string::npos || position_ > 15) { + if (this->position_ == std::string::npos || this->position_ > 15) { ESP_LOGE(TAG, "Missing ':' when looking for name."); return; } - auto name = this->rtttl_.substr(0, position_); + auto name = this->rtttl_.substr(0, this->position_); ESP_LOGD(TAG, "Playing song %s", name.c_str()); // get default duration - position_ = this->rtttl_.find("d=", position_); - if (position_ == std::string::npos) { + this->position_ = this->rtttl_.find("d=", this->position_); + if (this->position_ == std::string::npos) { ESP_LOGE(TAG, "Missing 'd='"); return; } - position_ += 2; + this->position_ += 2; num = this->get_integer_(); if (num > 0) - default_duration_ = num; + this->default_duration_ = num; // get default octave - position_ = rtttl_.find("o=", position_); - if (position_ == std::string::npos) { + this->position_ = this->rtttl_.find("o=", this->position_); + if (this->position_ == std::string::npos) { ESP_LOGE(TAG, "Missing 'o="); return; } - position_ += 2; + this->position_ += 2; num = get_integer_(); if (num >= 3 && num <= 7) - default_octave_ = num; + this->default_octave_ = num; // get BPM - position_ = rtttl_.find("b=", position_); - if (position_ == std::string::npos) { + this->position_ = this->rtttl_.find("b=", this->position_); + if (this->position_ == std::string::npos) { ESP_LOGE(TAG, "Missing b="); return; } - position_ += 2; + this->position_ += 2; num = get_integer_(); if (num != 0) bpm = num; - position_ = rtttl_.find(':', position_); - if (position_ == std::string::npos) { + this->position_ = this->rtttl_.find(':', this->position_); + if (this->position_ == std::string::npos) { ESP_LOGE(TAG, "Missing second ':'"); return; } - position_++; + this->position_++; // BPM usually expresses the number of quarter notes per minute - wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds) + this->wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds) + + this->output_freq_ = 0; + this->last_note_ = millis(); + this->note_duration_ = 1; + +#ifdef USE_SPEAKER + this->samples_sent_ = 0; + this->samples_count_ = 0; +#endif +} - output_freq_ = 0; - last_note_ = millis(); - note_duration_ = 1; +void Rtttl::stop() { + this->note_duration_ = 0; +#ifdef USE_OUTPUT + if (this->output_ != nullptr) { + this->output_->set_level(0.0); + } +#endif +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + if (this->speaker_->is_running()) { + this->speaker_->stop(); + } + } +#endif } void Rtttl::loop() { - if (note_duration_ == 0 || millis() - last_note_ < note_duration_) + if (this->note_duration_ == 0) return; - if (!rtttl_[position_]) { - output_->set_level(0.0); +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + if (this->samples_sent_ != this->samples_count_) { + SpeakerSample sample[SAMPLE_BUFFER_SIZE + 1]; + int x = 0; + double rem = 0.0; + + while (true) { + // Try and send out the remainder of the existing note, one per loop() + + if (this->samples_per_wave_ != 0 && this->samples_sent_ >= this->samples_gap_) { // Play note// + rem = ((this->samples_sent_ << 10) % this->samples_per_wave_) * (360.0 / this->samples_per_wave_); + + int16_t val = (49152 * this->gain_) * sin(deg2rad(rem)); + + sample[x].left = val; + sample[x].right = val; + + } else { + sample[x].left = 0; + sample[x].right = 0; + } + + if (x >= SAMPLE_BUFFER_SIZE || this->samples_sent_ >= this->samples_count_) { + break; + } + this->samples_sent_++; + x++; + } + if (x > 0) { + int send = this->speaker_->play((uint8_t *) (&sample), x * 4); + if (send != x * 4) { + this->samples_sent_ -= (x - (send / 4)); + } + return; + } + } + } +#endif +#ifdef USE_OUTPUT + if (this->output_ != nullptr && millis() - this->last_note_ < this->note_duration_) + return; +#endif + if (!this->rtttl_[position_]) { + this->note_duration_ = 0; +#ifdef USE_OUTPUT + if (this->output_ != nullptr) { + this->output_->set_level(0.0); + } +#endif ESP_LOGD(TAG, "Playback finished"); this->on_finished_playback_callback_.call(); - note_duration_ = 0; return; } // align to note: most rtttl's out there does not add and space after the ',' separator but just in case... - while (rtttl_[position_] == ',' || rtttl_[position_] == ' ') - position_++; + while (this->rtttl_[this->position_] == ',' || this->rtttl_[this->position_] == ' ') + this->position_++; // first, get note duration, if available uint8_t num = this->get_integer_(); if (num) { - note_duration_ = wholenote_ / num; + this->note_duration_ = this->wholenote_ / num; } else { - note_duration_ = wholenote_ / default_duration_; // we will need to check if we are a dotted note after + this->note_duration_ = + this->wholenote_ / this->default_duration_; // we will need to check if we are a dotted note after } uint8_t note; - switch (rtttl_[position_]) { + switch (this->rtttl_[this->position_]) { case 'c': note = 1; break; @@ -138,51 +220,86 @@ void Rtttl::loop() { default: note = 0; } - position_++; + this->position_++; // now, get optional '#' sharp - if (rtttl_[position_] == '#') { + if (this->rtttl_[this->position_] == '#') { note++; - position_++; + this->position_++; } // now, get optional '.' dotted note - if (rtttl_[position_] == '.') { - note_duration_ += note_duration_ / 2; - position_++; + if (this->rtttl_[this->position_] == '.') { + this->note_duration_ += this->note_duration_ / 2; + this->position_++; } // now, get scale uint8_t scale = get_integer_(); if (scale == 0) - scale = default_octave_; + scale = this->default_octave_; + bool need_note_gap = false; // Now play the note if (note) { auto note_index = (scale - 4) * 12 + note; if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { ESP_LOGE(TAG, "Note out of valid range"); + this->note_duration_ = 0; return; } auto freq = NOTES[note_index]; + need_note_gap = freq == this->output_freq_; + + // Add small silence gap between same note + this->output_freq_ = freq; - if (freq == output_freq_) { - // Add small silence gap between same note - output_->set_level(0.0); + ESP_LOGVV(TAG, "playing note: %d for %dms", note, this->note_duration_); + } else { + ESP_LOGVV(TAG, "waiting: %dms", this->note_duration_); + this->output_freq_ = 0; + } + +#ifdef USE_OUTPUT + if (this->output_ != nullptr) { + if (need_note_gap) { + this->output_->set_level(0.0); delay(DOUBLE_NOTE_GAP_MS); - note_duration_ -= DOUBLE_NOTE_GAP_MS; + this->note_duration_ -= DOUBLE_NOTE_GAP_MS; + } + if (this->output_freq_ != 0) { + this->output_->update_frequency(this->output_freq_); + this->output_->set_level(this->gain_); + } else { + this->output_->set_level(0.0); + } + } +#endif +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + this->samples_sent_ = 0; + this->samples_gap_ = 0; + this->samples_per_wave_ = 0; + this->samples_count_ = (this->sample_rate_ * this->note_duration_) / 1600; //(ms); + if (need_note_gap) { + this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / 1600; //(ms); } - output_freq_ = freq; + if (this->output_freq_ != 0) { + this->samples_per_wave_ = (this->sample_rate_ << 10) / this->output_freq_; - ESP_LOGVV(TAG, "playing note: %d for %dms", note, note_duration_); - output_->update_frequency(freq); - output_->set_level(0.5); - } else { - ESP_LOGVV(TAG, "waiting: %dms", note_duration_); - output_->set_level(0.0); + // make sure there is enough samples to add a full last sinus. + uint16_t division = ((this->samples_count_ << 10) / this->samples_per_wave_) + 1; + uint16_t x = this->samples_count_; + this->samples_count_ = (division * this->samples_per_wave_); + ESP_LOGD(TAG, "play time old: %d div: %d new: %d %d", x, division, this->samples_count_, this->samples_per_wave_); + this->samples_count_ = this->samples_count_ >> 10; + } + // Convert from frequency in Hz to high and low samples in fixed point } +#endif - last_note_ = millis(); + this->last_note_ = millis(); } + } // namespace rtttl } // namespace esphome diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index ec6fe7f98fd9..bf089ce98096 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -1,23 +1,48 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" + +#ifdef USE_OUTPUT #include "esphome/components/output/float_output.h" +#endif + +#ifdef USE_SPEAKER +#include "esphome/components/speaker/speaker.h" +#endif namespace esphome { namespace rtttl { +#ifdef USE_SPEAKER +static const size_t SAMPLE_BUFFER_SIZE = 512; + +struct SpeakerSample { + int16_t left{0}; + int16_t right{0}; +}; +#endif + class Rtttl : public Component { public: - void set_output(output::FloatOutput *output) { output_ = output; } - void play(std::string rtttl); - void stop() { - note_duration_ = 0; - output_->set_level(0.0); +#ifdef USE_OUTPUT + void set_output(output::FloatOutput *output) { this->output_ = output; } +#endif +#ifdef USE_SPEAKER + void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } +#endif + void set_gain(float gain) { + if (gain < 0.1f) + gain = 0.1f; + if (gain > 1.0f) + gain = 1.0f; + this->gain_ = gain; } + void play(std::string rtttl); + void stop(); void dump_config() override; - bool is_playing() { return note_duration_ != 0; } + bool is_playing() { return this->note_duration_ != 0; } void loop() override; void add_on_finished_playback_callback(std::function callback) { @@ -27,14 +52,14 @@ class Rtttl : public Component { protected: inline uint8_t get_integer_() { uint8_t ret = 0; - while (isdigit(rtttl_[position_])) { - ret = (ret * 10) + (rtttl_[position_++] - '0'); + while (isdigit(this->rtttl_[this->position_])) { + ret = (ret * 10) + (this->rtttl_[this->position_++] - '0'); } return ret; } - std::string rtttl_; - size_t position_; + std::string rtttl_{""}; + size_t position_{0}; uint16_t wholenote_; uint16_t default_duration_; uint16_t default_octave_; @@ -42,7 +67,23 @@ class Rtttl : public Component { uint16_t note_duration_; uint32_t output_freq_; + float gain_{0.6f}; + +#ifdef USE_OUTPUT output::FloatOutput *output_; +#endif + + void play_output_(); + +#ifdef USE_SPEAKER + speaker::Speaker *speaker_{nullptr}; + int sample_rate_{16000}; + int samples_per_wave_{0}; + int samples_sent_{0}; + int samples_count_{0}; + int samples_gap_{0}; + +#endif CallbackManager on_finished_playback_callback_; }; diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index f72b43fd373f..a900c51a58c3 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -8,6 +8,7 @@ CONF_HUMIDITY, CONF_TEMPERATURE, CONF_CO2, + CONF_TEMPERATURE_OFFSET, CONF_UPDATE_INTERVAL, CONF_VALUE, DEVICE_CLASS_CARBON_DIOXIDE, @@ -36,7 +37,6 @@ CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" -CONF_TEMPERATURE_OFFSET = "temperature_offset" CONFIG_SCHEMA = ( diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 4c94d4257f5d..13027b6f880a 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -10,6 +10,7 @@ CONF_CO2, CONF_HUMIDITY, CONF_TEMPERATURE, + CONF_TEMPERATURE_OFFSET, CONF_VALUE, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, @@ -52,7 +53,6 @@ CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_MEASUREMENT_MODE = "measurement_mode" -CONF_TEMPERATURE_OFFSET = "temperature_offset" CONFIG_SCHEMA = ( diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index 78b23e7b5ef5..483357f85b15 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -2,7 +2,7 @@ import esphome.config_validation as cv from esphome import automation from esphome.automation import maybe_simple_id -from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS +from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS, CONF_RESTART from esphome.core import CORE, EsphomeError CODEOWNERS = ["@esphome/core"] @@ -19,7 +19,6 @@ CONF_SCRIPT = "script" CONF_SINGLE = "single" -CONF_RESTART = "restart" CONF_QUEUED = "queued" CONF_PARALLEL = "parallel" CONF_MAX_RUNS = "max_runs" diff --git a/esphome/components/seeed_mr24hpc1/__init__.py b/esphome/components/seeed_mr24hpc1/__init__.py new file mode 100644 index 000000000000..52b971e7e46c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/__init__.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +DEPENDENCIES = ["uart"] +# is the code owner of the relevant code base +CODEOWNERS = ["@limengdu"] +# The current component or platform can be configured or defined multiple times in the same configuration file. +MULTI_CONF = True + +# This line of code creates a new namespace called mr24hpc1_ns. +# This namespace will be used as a prefix for all classes, functions and variables associated with the mr24hpc1_ns component, ensuring that they do not conflict with the names of other components. +mr24hpc1_ns = cg.esphome_ns.namespace("seeed_mr24hpc1") +# This MR24HPC1Component class will be a periodically polled UART device +MR24HPC1Component = mr24hpc1_ns.class_( + "MR24HPC1Component", cg.Component, uart.UARTDevice +) + +CONF_MR24HPC1_ID = "mr24hpc1_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR24HPC1Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +# A verification mode was created to verify the configuration parameters of a UART device named "seeed_mr24hpc1". +# This authentication mode requires that the device must have transmit and receive functionality, a parity mode of "NONE", and a stop bit of one. +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr24hpc1", + require_tx=True, + require_rx=True, + parity="NONE", + stop_bits=1, +) + + +# The async def keyword is used to define a concurrent function. +# Concurrent functions are special functions designed to work with Python's asyncio library to support asynchronous I/O operations. +async def to_code(config): + # This line of code creates a new Pvariable (a Python object representing a C++ variable) with the variable's ID taken from the configuration. + var = cg.new_Pvariable(config[CONF_ID]) + # This line of code registers the newly created Pvariable as a component so that ESPHome can manage it at runtime. + await cg.register_component(var, config) + # This line of code registers the newly created Pvariable as a device. + await uart.register_uart_device(var, config) diff --git a/esphome/components/seeed_mr24hpc1/binary_sensor.py b/esphome/components/seeed_mr24hpc1/binary_sensor.py new file mode 100644 index 000000000000..003db9f4a32a --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/binary_sensor.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_OCCUPANCY, + CONF_HAS_TARGET, +) +from . import CONF_MR24HPC1_ID, MR24HPC1Component + + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor" + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if has_target_config := config.get(CONF_HAS_TARGET): + sens = await binary_sensor.new_binary_sensor(has_target_config) + cg.add(mr24hpc1_component.set_has_target_binary_sensor(sens)) diff --git a/esphome/components/seeed_mr24hpc1/button/__init__.py b/esphome/components/seeed_mr24hpc1/button/__init__.py new file mode 100644 index 000000000000..59372e4100dc --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + CONF_RESTART, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +RestartButton = mr24hpc1_ns.class_("RestartButton", button.Button) +CustomSetEndButton = mr24hpc1_ns.class_("CustomSetEndButton", button.Button) + +CONF_CUSTOM_SET_END = "custom_set_end" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_RESTART): button.button_schema( + RestartButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ), + cv.Optional(CONF_CUSTOM_SET_END): button.button_schema( + CustomSetEndButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:cog", + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if restart_config := config.get(CONF_RESTART): + b = await button.new_button(restart_config) + await cg.register_parented(b, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_restart_button(b)) + if custom_set_end_config := config.get(CONF_CUSTOM_SET_END): + b = await button.new_button(custom_set_end_config) + await cg.register_parented(b, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_custom_set_end_button(b)) diff --git a/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp new file mode 100644 index 000000000000..0ae88892479c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.cpp @@ -0,0 +1,9 @@ +#include "custom_mode_end_button.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomSetEndButton::press_action() { this->parent_->set_custom_end_mode(); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h new file mode 100644 index 000000000000..a1701d8581b9 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/custom_mode_end_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomSetEndButton : public button::Button, public Parented { + public: + CustomSetEndButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/restart_button.cpp b/esphome/components/seeed_mr24hpc1/button/restart_button.cpp new file mode 100644 index 000000000000..6c4a070d1cf0 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/restart_button.cpp @@ -0,0 +1,9 @@ +#include "restart_button.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void RestartButton::press_action() { this->parent_->set_restart(); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/button/restart_button.h b/esphome/components/seeed_mr24hpc1/button/restart_button.h new file mode 100644 index 000000000000..8a2ec2087ca5 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class RestartButton : public button::Button, public Parented { + public: + RestartButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/__init__.py b/esphome/components/seeed_mr24hpc1/number/__init__.py new file mode 100644 index 000000000000..2055fc548c73 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/__init__.py @@ -0,0 +1,132 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_SENSITIVITY, + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +SensitivityNumber = mr24hpc1_ns.class_("SensitivityNumber", number.Number) +CustomModeNumber = mr24hpc1_ns.class_("CustomModeNumber", number.Number) +ExistenceThresholdNumber = mr24hpc1_ns.class_("ExistenceThresholdNumber", number.Number) +MotionThresholdNumber = mr24hpc1_ns.class_("MotionThresholdNumber", number.Number) +MotionTriggerTimeNumber = mr24hpc1_ns.class_("MotionTriggerTimeNumber", number.Number) +MotionToRestTimeNumber = mr24hpc1_ns.class_("MotionToRestTimeNumber", number.Number) +CustomUnmanTimeNumber = mr24hpc1_ns.class_("CustomUnmanTimeNumber", number.Number) + +CONF_CUSTOM_MODE = "custom_mode" +CONF_EXISTENCE_THRESHOLD = "existence_threshold" +CONF_MOTION_THRESHOLD = "motion_threshold" +CONF_MOTION_TRIGGER = "motion_trigger" +CONF_MOTION_TO_REST = "motion_to_rest" +CONF_CUSTOM_UNMAN_TIME = "custom_unman_time" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_SENSITIVITY): number.number_schema( + SensitivityNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:archive-check-outline", + ), + cv.Optional(CONF_CUSTOM_MODE): number.number_schema( + CustomModeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:cog", + ), + cv.Optional(CONF_EXISTENCE_THRESHOLD): number.number_schema( + ExistenceThresholdNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_THRESHOLD): number.number_schema( + MotionThresholdNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_TRIGGER): number.number_schema( + MotionTriggerTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="ms", + ), + cv.Optional(CONF_MOTION_TO_REST): number.number_schema( + MotionToRestTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="ms", + ), + cv.Optional(CONF_CUSTOM_UNMAN_TIME): number.number_schema( + CustomUnmanTimeNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:camera-timer", + unit_of_measurement="s", + ), + } +) + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if sensitivity_config := config.get(CONF_SENSITIVITY): + n = await number.new_number( + sensitivity_config, + min_value=0, + max_value=3, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_sensitivity_number(n)) + if custom_mode_config := config.get(CONF_CUSTOM_MODE): + n = await number.new_number( + custom_mode_config, + min_value=0, + max_value=4, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_custom_mode_number(n)) + if existence_threshold_config := config.get(CONF_EXISTENCE_THRESHOLD): + n = await number.new_number( + existence_threshold_config, + min_value=0, + max_value=250, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_existence_threshold_number(n)) + if motion_threshold_config := config.get(CONF_MOTION_THRESHOLD): + n = await number.new_number( + motion_threshold_config, + min_value=0, + max_value=250, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_threshold_number(n)) + if motion_trigger_config := config.get(CONF_MOTION_TRIGGER): + n = await number.new_number( + motion_trigger_config, + min_value=0, + max_value=150, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_trigger_number(n)) + if motion_to_rest_config := config.get(CONF_MOTION_TO_REST): + n = await number.new_number( + motion_to_rest_config, + min_value=0, + max_value=3000, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_motion_to_rest_number(n)) + if custom_unman_time_config := config.get(CONF_CUSTOM_UNMAN_TIME): + n = await number.new_number( + custom_unman_time_config, + min_value=0, + max_value=3600, + step=1, + ) + await cg.register_parented(n, mr24hpc1_component) + cg.add(mr24hpc1_component.set_custom_unman_time_number(n)) diff --git a/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp new file mode 100644 index 000000000000..0aebd8fb9fe5 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.cpp @@ -0,0 +1,12 @@ +#include "custom_mode_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomModeNumber::control(float value) { + this->publish_state(value); + this->parent_->set_custom_mode(value); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h new file mode 100644 index 000000000000..40ff3f201ace --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_mode_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomModeNumber : public number::Number, public Parented { + public: + CustomModeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp new file mode 100644 index 000000000000..12a8ff69fa52 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.cpp @@ -0,0 +1,9 @@ +#include "custom_unman_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void CustomUnmanTimeNumber::control(float value) { this->parent_->set_custom_unman_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h new file mode 100644 index 000000000000..6b871c4c134e --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/custom_unman_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class CustomUnmanTimeNumber : public number::Number, public Parented { + public: + CustomUnmanTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp new file mode 100644 index 000000000000..58ef56509ef8 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.cpp @@ -0,0 +1,9 @@ +#include "existence_threshold_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void ExistenceThresholdNumber::control(float value) { this->parent_->set_existence_threshold(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h new file mode 100644 index 000000000000..656bad17deba --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/existence_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class ExistenceThresholdNumber : public number::Number, public Parented { + public: + ExistenceThresholdNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp new file mode 100644 index 000000000000..d252b4f01dd3 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.cpp @@ -0,0 +1,9 @@ +#include "motion_threshold_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionThresholdNumber::control(float value) { this->parent_->set_motion_threshold(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h new file mode 100644 index 000000000000..e8ae37b96f03 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionThresholdNumber : public number::Number, public Parented { + public: + MotionThresholdNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp new file mode 100644 index 000000000000..fc7659dc54bb --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.cpp @@ -0,0 +1,9 @@ +#include "motion_trigger_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionTriggerTimeNumber::control(float value) { this->parent_->set_motion_trigger_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h new file mode 100644 index 000000000000..996356e23711 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motion_trigger_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionTriggerTimeNumber : public number::Number, public Parented { + public: + MotionTriggerTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp new file mode 100644 index 000000000000..f598e6686c65 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.cpp @@ -0,0 +1,9 @@ +#include "motiontorest_time_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionToRestTimeNumber::control(float value) { this->parent_->set_motion_to_rest_time(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h new file mode 100644 index 000000000000..559d23fdeb7b --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/motiontorest_time_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionToRestTimeNumber : public number::Number, public Parented { + public: + MotionToRestTimeNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp new file mode 100644 index 000000000000..d903dfd8187d --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.cpp @@ -0,0 +1,9 @@ +#include "sensitivity_number.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void SensitivityNumber::control(float value) { this->parent_->set_sensitivity(value); } + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h new file mode 100644 index 000000000000..fee33521d047 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/number/sensitivity_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class SensitivityNumber : public number::Number, public Parented { + public: + SensitivityNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp new file mode 100644 index 000000000000..1cf9bd300a18 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -0,0 +1,890 @@ +#include "seeed_mr24hpc1.h" + +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +static const char *const TAG = "seeed_mr24hpc1"; + +// 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 MR24HPC1Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR24HPC1:"); +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR(" ", "Heartbeat Text Sensor", this->heartbeat_state_text_sensor_); + LOG_TEXT_SENSOR(" ", "Product Model Text Sensor", this->product_model_text_sensor_); + LOG_TEXT_SENSOR(" ", "Product ID Text Sensor", this->product_id_text_sensor_); + LOG_TEXT_SENSOR(" ", "Hardware Model Text Sensor", this->hardware_model_text_sensor_); + LOG_TEXT_SENSOR(" ", "Firware Verison Text Sensor", this->firware_version_text_sensor_); + LOG_TEXT_SENSOR(" ", "Keep Away Text Sensor", this->keep_away_text_sensor_); + LOG_TEXT_SENSOR(" ", "Motion Status Text Sensor", this->motion_status_text_sensor_); + LOG_TEXT_SENSOR(" ", "Custom Mode End Text Sensor", this->custom_mode_end_text_sensor_); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Has Target Binary Sensor", this->has_target_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Custom Presence Of Detection Sensor", this->custom_presence_of_detection_sensor_); + LOG_SENSOR(" ", "Movement Signs Sensor", this->movement_signs_sensor_); + LOG_SENSOR(" ", "Custom Motion Distance Sensor", this->custom_motion_distance_sensor_); + LOG_SENSOR(" ", "Custom Spatial Static Sensor", this->custom_spatial_static_value_sensor_); + LOG_SENSOR(" ", "Custom Spatial Motion Sensor", this->custom_spatial_motion_value_sensor_); + LOG_SENSOR(" ", "Custom Motion Speed Sensor", this->custom_motion_speed_sensor_); + LOG_SENSOR(" ", "Custom Mode Num Sensor", this->custom_mode_num_sensor_); +#endif +#ifdef USE_SWITCH + LOG_SWITCH(" ", "Underly Open Function Switch", this->underlying_open_function_switch_); +#endif +#ifdef USE_BUTTON + LOG_BUTTON(" ", "Restart Button", this->restart_button_); + LOG_BUTTON(" ", "Custom Set End Button", this->custom_set_end_button_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "Scene Mode Select", this->scene_mode_select_); + LOG_SELECT(" ", "Unman Time Select", this->unman_time_select_); + LOG_SELECT(" ", "Existence Boundary Select", this->existence_boundary_select_); + LOG_SELECT(" ", "Motion Boundary Select", this->motion_boundary_select_); +#endif +#ifdef USE_NUMBER + LOG_NUMBER(" ", "Sensitivity Number", this->sensitivity_number_); + LOG_NUMBER(" ", "Custom Mode Number", this->custom_mode_number_); + LOG_NUMBER(" ", "Existence Threshold Number", this->existence_threshold_number_); + LOG_NUMBER(" ", "Motion Threshold Number", this->motion_threshold_number_); + LOG_NUMBER(" ", "Motion Trigger Time Number", this->motion_trigger_number_); + LOG_NUMBER(" ", "Motion To Rest Time Number", this->motion_to_rest_number_); + LOG_NUMBER(" ", "Custom Unman Time Number", this->custom_unman_time_number_); +#endif +} + +// Initialisation functions +void MR24HPC1Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MR24HPC1..."); + this->check_uart_settings(115200); + + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); // Zero out the custom mode + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(0); + } + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Not in custom mode"); + } + this->set_custom_end_mode(); + this->poll_time_base_func_check_ = true; + this->check_dev_inf_sign_ = true; + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_PRODUCT_MODE; + this->sg_data_len_ = 0; + this->sg_frame_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_INIT; + + memset(this->c_product_mode_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_product_id_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_firmware_version_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->c_hardware_model_, 0, PRODUCT_BUF_MAX_SIZE); + memset(this->sg_frame_prase_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->sg_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + + this->set_interval(8000, [this]() { this->update_(); }); + ESP_LOGCONFIG(TAG, "Set up MR24HPC1 complete"); +} + +// Timed polling of radar data +void MR24HPC1Component::update_() { + this->get_radar_output_information_switch(); // Query the key status every so often + this->poll_time_base_func_check_ = true; // Query the base functionality information at regular intervals +} + +// main loop +void MR24HPC1Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->r24_split_data_frame_(byte); // split data frame + } + + if ((this->s_output_info_switch_flag_ == OUTPUT_SWTICH_OFF) && + (this->sg_start_query_data_ > CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED) && (!this->check_dev_inf_sign_)) { + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_SCENE_MODE; + } else if ((this->s_output_info_switch_flag_ == OUTPUT_SWITCH_ON) && + (this->sg_start_query_data_ < CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY) && (!this->check_dev_inf_sign_)) { + this->sg_start_query_data_ = CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY; + } else if (this->check_dev_inf_sign_ && (this->sg_start_query_data_ > STANDARD_FUNCTION_QUERY_HARDWARE_MODE)) { + // First time power up information polling + this->sg_start_query_data_ = STANDARD_FUNCTION_QUERY_PRODUCT_MODE; + } + + // Polling Functions + if (this->poll_time_base_func_check_) { + switch (this->sg_start_query_data_) { + case STANDARD_FUNCTION_QUERY_PRODUCT_MODE: + this->get_product_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_PRODUCT_ID: + this->get_product_id(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_FIRMWARE_VERSION: + this->get_product_mode(); + this->get_product_id(); + this->get_firmware_version(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HARDWARE_MODE: // Above is the equipment information + this->get_product_mode(); + this->get_product_id(); + this->get_hardware_model(); + this->sg_start_query_data_++; + this->check_dev_inf_sign_ = false; + break; + case STANDARD_FUNCTION_QUERY_SCENE_MODE: + this->get_scene_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_SENSITIVITY: + this->get_sensitivity(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_UNMANNED_TIME: + this->get_unmanned_time(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HUMAN_STATUS: + this->get_human_status(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HUMAN_MOTION_INF: + this->get_human_motion_info(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_BODY_MOVE_PARAMETER: + this->get_body_motion_params(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_KEEPAWAY_STATUS: // The above is the basic functional information + this->get_keep_away(); + this->sg_start_query_data_++; + break; + case STANDARD_QUERY_CUSTOM_MODE: + this->get_custom_mode(); + this->sg_start_query_data_++; + break; + case STANDARD_FUNCTION_QUERY_HEARTBEAT_STATE: + this->get_heartbeat_packet(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY: + this->get_existence_boundary(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_BOUNDARY: + this->get_motion_boundary(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_EXISTENCE_THRESHOLD: + this->get_existence_threshold(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_THRESHOLD: + this->get_motion_threshold(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_TRIGGER_TIME: + this->get_motion_trigger_time(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_MOTION_TO_REST_TIME: + this->get_motion_to_rest_time(); + this->sg_start_query_data_++; + break; + case CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED: + this->get_custom_unman_time(); + this->sg_start_query_data_++; + if (this->s_output_info_switch_flag_ == OUTPUT_SWTICH_OFF) { + this->poll_time_base_func_check_ = false; // Avoiding high-speed polling that can cause the device to jam + } + break; + case UNDERLY_FUNCTION_QUERY_HUMAN_STATUS: + this->get_human_status(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_SPATIAL_STATIC_VALUE: + this->get_spatial_static_value(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_SPATIAL_MOTION_VALUE: + this->get_spatial_motion_value(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_DISTANCE_OF_STATIC_OBJECT: + this->get_distance_of_static_object(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_DISTANCE_OF_MOVING_OBJECT: + this->get_distance_of_moving_object(); + this->sg_start_query_data_++; + break; + case UNDERLY_FUNCTION_QUERY_TARGET_MOVEMENT_SPEED: + this->get_target_movement_speed(); + this->sg_start_query_data_++; + this->poll_time_base_func_check_ = false; // Avoiding high-speed polling that can cause the device to jam + break; + default: + break; + } + } +} + +// Calculate CRC check digit +static uint8_t get_frame_crc_sum(const uint8_t *data, int len) { + unsigned int crc_sum = 0; + for (int i = 0; i < len - 3; i++) { + crc_sum += data[i]; + } + return crc_sum & 0xff; +} + +// Check that the check digit is correct +static int get_frame_check_status(uint8_t *data, int len) { + uint8_t crc_sum = get_frame_crc_sum(data, len); + uint8_t verified = data[len - 3]; + return (verified == crc_sum) ? 1 : 0; +} + +// split data frame +void MR24HPC1Component::r24_split_data_frame_(uint8_t value) { + switch (this->sg_recv_data_state_) { + case FRAME_IDLE: // starting value + if (FRAME_HEADER1_VALUE == value) { + this->sg_recv_data_state_ = FRAME_HEADER2; + } + break; + case FRAME_HEADER2: + if (FRAME_HEADER2_VALUE == value) { + this->sg_frame_buf_[0] = FRAME_HEADER1_VALUE; + this->sg_frame_buf_[1] = FRAME_HEADER2_VALUE; + this->sg_recv_data_state_ = FRAME_CTL_WORD; + } else { + this->sg_recv_data_state_ = FRAME_IDLE; + ESP_LOGD(TAG, "FRAME_IDLE ERROR value:%x", value); + } + break; + case FRAME_CTL_WORD: + this->sg_frame_buf_[2] = value; + this->sg_recv_data_state_ = FRAME_CMD_WORD; + break; + case FRAME_CMD_WORD: + this->sg_frame_buf_[3] = value; + this->sg_recv_data_state_ = FRAME_DATA_LEN_H; + break; + case FRAME_DATA_LEN_H: + if (value <= 4) { + this->sg_data_len_ = value * 256; + this->sg_frame_buf_[4] = value; + this->sg_recv_data_state_ = FRAME_DATA_LEN_L; + } else { + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + ESP_LOGD(TAG, "FRAME_DATA_LEN_H ERROR value:%x", value); + } + break; + case FRAME_DATA_LEN_L: + this->sg_data_len_ += value; + if (this->sg_data_len_ > 32) { + ESP_LOGD(TAG, "len=%d, FRAME_DATA_LEN_L ERROR value:%x", this->sg_data_len_, value); + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + } else { + this->sg_frame_buf_[5] = value; + this->sg_frame_len_ = 6; + this->sg_recv_data_state_ = FRAME_DATA_BYTES; + } + break; + case FRAME_DATA_BYTES: + this->sg_data_len_ -= 1; + this->sg_frame_buf_[this->sg_frame_len_++] = value; + if (this->sg_data_len_ <= 0) { + this->sg_recv_data_state_ = FRAME_DATA_CRC; + } + break; + case FRAME_DATA_CRC: + this->sg_frame_buf_[this->sg_frame_len_++] = value; + this->sg_recv_data_state_ = FRAME_TAIL1; + break; + case FRAME_TAIL1: + if (FRAME_TAIL1_VALUE == value) { + this->sg_recv_data_state_ = FRAME_TAIL2; + } else { + this->sg_recv_data_state_ = FRAME_IDLE; + this->sg_frame_len_ = 0; + this->sg_data_len_ = 0; + ESP_LOGD(TAG, "FRAME_TAIL1 ERROR value:%x", value); + } + break; + case FRAME_TAIL2: + if (FRAME_TAIL2_VALUE == value) { + this->sg_frame_buf_[this->sg_frame_len_++] = FRAME_TAIL1_VALUE; + this->sg_frame_buf_[this->sg_frame_len_++] = FRAME_TAIL2_VALUE; + memcpy(this->sg_frame_prase_buf_, this->sg_frame_buf_, this->sg_frame_len_); + if (get_frame_check_status(this->sg_frame_prase_buf_, this->sg_frame_len_)) { + this->r24_parse_data_frame_(this->sg_frame_prase_buf_, this->sg_frame_len_); + } else { + ESP_LOGD(TAG, "frame check failer!"); + } + } else { + ESP_LOGD(TAG, "FRAME_TAIL2 ERROR value:%x", value); + } + memset(this->sg_frame_prase_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->sg_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + this->sg_frame_len_ = 0; + this->sg_data_len_ = 0; + this->sg_recv_data_state_ = FRAME_IDLE; + break; + default: + this->sg_recv_data_state_ = FRAME_IDLE; + } +} + +// Parses data frames related to product information +void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) { + uint16_t product_len = encode_uint16(data[FRAME_COMMAND_WORD_INDEX + 1], data[FRAME_COMMAND_WORD_INDEX + 2]); + if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_MODE) { + if ((this->product_model_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_product_mode_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_product_mode_, &data[FRAME_DATA_INDEX], product_len); + this->product_model_text_sensor_->publish_state(this->c_product_mode_); + } else { + ESP_LOGD(TAG, "Reply: get product_mode error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_ID) { + if ((this->product_id_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_product_id_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_product_id_, &data[FRAME_DATA_INDEX], product_len); + this->product_id_text_sensor_->publish_state(this->c_product_id_); + } else { + ESP_LOGD(TAG, "Reply: get productId error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_HARDWARE_MODEL) { + if ((this->hardware_model_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_hardware_model_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_hardware_model_, &data[FRAME_DATA_INDEX], product_len); + this->hardware_model_text_sensor_->publish_state(this->c_hardware_model_); + ESP_LOGD(TAG, "Reply: get hardware_model :%s", this->c_hardware_model_); + } else { + ESP_LOGD(TAG, "Reply: get hardwareModel error!"); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_FIRMWARE_VERSION) { + if ((this->firware_version_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) { + memset(this->c_firmware_version_, 0, PRODUCT_BUF_MAX_SIZE); + memcpy(this->c_firmware_version_, &data[FRAME_DATA_INDEX], product_len); + this->firware_version_text_sensor_->publish_state(this->c_firmware_version_); + } else { + ESP_LOGD(TAG, "Reply: get firmwareVersion error!"); + } + } +} + +// Parsing the underlying open parameters +void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *data) { + if (data[FRAME_COMMAND_WORD_INDEX] == 0x00) { + if (this->underlying_open_function_switch_ != nullptr) { + this->underlying_open_function_switch_->publish_state( + data[FRAME_DATA_INDEX]); // Underlying Open Parameter Switch Status Updates + } + if (data[FRAME_DATA_INDEX]) { + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON; + } else { + this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF; + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) { + if (this->custom_spatial_static_value_sensor_ != nullptr) { + this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + if (this->custom_presence_of_detection_sensor_ != nullptr) { + this->custom_presence_of_detection_sensor_->publish_state(data[FRAME_DATA_INDEX + 1] * 0.5f); + } + if (this->custom_spatial_motion_value_sensor_ != nullptr) { + this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX + 2]); + } + if (this->custom_motion_distance_sensor_ != nullptr) { + this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX + 3] * 0.5f); + } + if (this->custom_motion_speed_sensor_ != nullptr) { + this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f); + } + } else if ((data[FRAME_COMMAND_WORD_INDEX] == 0x06) || (data[FRAME_COMMAND_WORD_INDEX] == 0x86)) { + // none:0x00 close_to:0x01 far_away:0x02 + if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) { + this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]); + } + } else if ((this->movement_signs_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x07) || (data[FRAME_COMMAND_WORD_INDEX] == 0x87))) { + this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->existence_threshold_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) { + this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->motion_threshold_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x09) || (data[FRAME_COMMAND_WORD_INDEX] == 0x89))) { + this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]); + } 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]); + } + } 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]); + } + } else if ((this->motion_trigger_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) { + uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + this->motion_trigger_number_->publish_state(motion_trigger_time); + } else if ((this->motion_to_rest_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0d) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8d))) { + uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + this->motion_to_rest_number_->publish_state(move_to_rest_time); + } else if ((this->custom_unman_time_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0e) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8e))) { + uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1], + data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]); + float custom_unmanned_time = enter_unmanned_time / 1000.0; + this->custom_unman_time_number_->publish_state(custom_unmanned_time); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x80) { + if (data[FRAME_DATA_INDEX]) { + this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON; + } else { + this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF; + } + if (this->underlying_open_function_switch_ != nullptr) { + this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]); + } + } else if ((this->custom_spatial_static_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x81)) { + this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->custom_spatial_motion_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x82)) { + this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->custom_presence_of_detection_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x83)) { + this->custom_presence_of_detection_sensor_->publish_state( + S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]); + } else if ((this->custom_motion_distance_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x84)) { + this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f); + } else if ((this->custom_motion_speed_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x85)) { + this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f); + } +} + +void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) { + switch (data[FRAME_CONTROL_WORD_INDEX]) { + case 0x01: { + if ((this->heartbeat_state_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x01)) { + this->heartbeat_state_text_sensor_->publish_state("Equipment Normal"); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) { + ESP_LOGD(TAG, "Reply: query restart packet"); + } else if (this->heartbeat_state_text_sensor_ != nullptr) { + this->heartbeat_state_text_sensor_->publish_state("Equipment Abnormal"); + } + } break; + case 0x02: { + this->r24_frame_parse_product_information_(data); + } break; + case 0x05: { + this->r24_frame_parse_work_status_(data); + } break; + case 0x08: { + this->r24_frame_parse_open_underlying_information_(data); + } break; + case 0x80: { + this->r24_frame_parse_human_information_(data); + } break; + default: + ESP_LOGD(TAG, "control word:0x%02X not found", data[FRAME_CONTROL_WORD_INDEX]); + break; + } +} + +void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) { + if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) { + 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]]); + } else { + ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); + } + } else if ((this->sensitivity_number_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) { + // 1-3 + this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x09) { + // 1-4 + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Setup in progress..."); + } + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x81) { + 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]]); + } else { + ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]); + } + } else if ((this->custom_mode_end_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x0A)) { + this->custom_mode_end_text_sensor_->publish_state("Set Success!"); + } else if (data[FRAME_COMMAND_WORD_INDEX] == 0x89) { + if (data[FRAME_DATA_INDEX] == 0) { + if (this->custom_mode_end_text_sensor_ != nullptr) { + this->custom_mode_end_text_sensor_->publish_state("Not in custom mode"); + } + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + } else { + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } + } + } else { + ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]); + } +} + +void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) { + if ((this->has_target_binary_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x01) || (data[FRAME_COMMAND_WORD_INDEX] == 0x81))) { + this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]); + } else if ((this->motion_status_text_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x02) || (data[FRAME_COMMAND_WORD_INDEX] == 0x82))) { + if (data[FRAME_DATA_INDEX] < 3) { + this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]); + } + } else if ((this->movement_signs_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x03) || (data[FRAME_COMMAND_WORD_INDEX] == 0x83))) { + this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]); + } else if ((this->unman_time_select_ != nullptr) && + ((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]]); + } + } else if ((this->keep_away_text_sensor_ != nullptr) && + ((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) { + // none:0x00 close_to:0x01 far_away:0x02 + if (data[FRAME_DATA_INDEX] < 3) { + this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]); + } + } else { + ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]); + } +} + +// Sending data frames +void MR24HPC1Component::send_query_(const uint8_t *query, size_t string_length) { + this->write_array(query, string_length); +} + +// Send Heartbeat Packet Command +void MR24HPC1Component::get_heartbeat_packet() { this->send_query_(GET_HEARTBEAT, sizeof(GET_HEARTBEAT)); } + +// Issuance of the underlying open parameter query command +void MR24HPC1Component::get_radar_output_information_switch() { + this->send_query_(GET_RADAR_OUTPUT_INFORMATION_SWITCH, sizeof(GET_RADAR_OUTPUT_INFORMATION_SWITCH)); +} + +// Issuance of product model orders +void MR24HPC1Component::get_product_mode() { this->send_query_(GET_PRODUCT_MODE, sizeof(GET_PRODUCT_MODE)); } + +// Issuing the Get Product ID command +void MR24HPC1Component::get_product_id() { this->send_query_(GET_PRODUCT_ID, sizeof(GET_PRODUCT_ID)); } + +// Issuing hardware model commands +void MR24HPC1Component::get_hardware_model() { this->send_query_(GET_HARDWARE_MODEL, sizeof(GET_HARDWARE_MODEL)); } + +// Issuing software version commands +void MR24HPC1Component::get_firmware_version() { + this->send_query_(GET_FIRMWARE_VERSION, sizeof(GET_FIRMWARE_VERSION)); +} + +void MR24HPC1Component::get_human_status() { this->send_query_(GET_HUMAN_STATUS, sizeof(GET_HUMAN_STATUS)); } + +void MR24HPC1Component::get_human_motion_info() { + this->send_query_(GET_HUMAN_MOTION_INFORMATION, sizeof(GET_HUMAN_MOTION_INFORMATION)); +} + +void MR24HPC1Component::get_body_motion_params() { + this->send_query_(GET_BODY_MOTION_PARAMETERS, sizeof(GET_BODY_MOTION_PARAMETERS)); +} + +void MR24HPC1Component::get_keep_away() { this->send_query_(GET_KEEP_AWAY, sizeof(GET_KEEP_AWAY)); } + +void MR24HPC1Component::get_scene_mode() { this->send_query_(GET_SCENE_MODE, sizeof(GET_SCENE_MODE)); } + +void MR24HPC1Component::get_sensitivity() { this->send_query_(GET_SENSITIVITY, sizeof(GET_SENSITIVITY)); } + +void MR24HPC1Component::get_unmanned_time() { this->send_query_(GET_UNMANNED_TIME, sizeof(GET_UNMANNED_TIME)); } + +void MR24HPC1Component::get_custom_mode() { this->send_query_(GET_CUSTOM_MODE, sizeof(GET_CUSTOM_MODE)); } + +void MR24HPC1Component::get_existence_boundary() { + this->send_query_(GET_EXISTENCE_BOUNDARY, sizeof(GET_EXISTENCE_BOUNDARY)); +} + +void MR24HPC1Component::get_motion_boundary() { this->send_query_(GET_MOTION_BOUNDARY, sizeof(GET_MOTION_BOUNDARY)); } + +void MR24HPC1Component::get_spatial_static_value() { + this->send_query_(GET_SPATIAL_STATIC_VALUE, sizeof(GET_SPATIAL_STATIC_VALUE)); +} + +void MR24HPC1Component::get_spatial_motion_value() { + this->send_query_(GET_SPATIAL_MOTION_VALUE, sizeof(GET_SPATIAL_MOTION_VALUE)); +} + +void MR24HPC1Component::get_distance_of_static_object() { + this->send_query_(GET_DISTANCE_OF_STATIC_OBJECT, sizeof(GET_DISTANCE_OF_STATIC_OBJECT)); +} + +void MR24HPC1Component::get_distance_of_moving_object() { + this->send_query_(GET_DISTANCE_OF_MOVING_OBJECT, sizeof(GET_DISTANCE_OF_MOVING_OBJECT)); +} + +void MR24HPC1Component::get_target_movement_speed() { + this->send_query_(GET_TARGET_MOVEMENT_SPEED, sizeof(GET_TARGET_MOVEMENT_SPEED)); +} + +void MR24HPC1Component::get_existence_threshold() { + this->send_query_(GET_EXISTENCE_THRESHOLD, sizeof(GET_EXISTENCE_THRESHOLD)); +} + +void MR24HPC1Component::get_motion_threshold() { + this->send_query_(GET_MOTION_THRESHOLD, sizeof(GET_MOTION_THRESHOLD)); +} + +void MR24HPC1Component::get_motion_trigger_time() { + this->send_query_(GET_MOTION_TRIGGER_TIME, sizeof(GET_MOTION_TRIGGER_TIME)); +} + +void MR24HPC1Component::get_motion_to_rest_time() { + this->send_query_(GET_MOTION_TO_REST_TIME, sizeof(GET_MOTION_TO_REST_TIME)); +} + +void MR24HPC1Component::get_custom_unman_time() { + this->send_query_(GET_CUSTOM_UNMAN_TIME, sizeof(GET_CUSTOM_UNMAN_TIME)); +} + +// Logic of setting: After setting, query whether the setting is successful or not! + +void MR24HPC1Component::set_underlying_open_function(bool enable) { + if (enable) { + this->send_query_(UNDERLYING_SWITCH_ON, sizeof(UNDERLYING_SWITCH_ON)); + } else { + this->send_query_(UNDERLYING_SWITCH_OFF, sizeof(UNDERLYING_SWITCH_OFF)); + } + if (this->keep_away_text_sensor_ != nullptr) { + this->keep_away_text_sensor_->publish_state(""); + } + if (this->motion_status_text_sensor_ != nullptr) { + this->motion_status_text_sensor_->publish_state(""); + } + if (this->custom_spatial_static_value_sensor_ != nullptr) { + this->custom_spatial_static_value_sensor_->publish_state(NAN); + } + if (this->custom_spatial_motion_value_sensor_ != nullptr) { + this->custom_spatial_motion_value_sensor_->publish_state(NAN); + } + if (this->custom_motion_distance_sensor_ != nullptr) { + this->custom_motion_distance_sensor_->publish_state(NAN); + } + if (this->custom_presence_of_detection_sensor_ != nullptr) { + this->custom_presence_of_detection_sensor_->publish_state(NAN); + } + if (this->custom_motion_speed_sensor_ != nullptr) { + this->custom_motion_speed_sensor_->publish_state(NAN); + } +} + +void MR24HPC1Component::set_scene_mode(uint8_t value) { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x07, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + if (this->custom_mode_num_sensor_ != nullptr) { + this->custom_mode_num_sensor_->publish_state(0); + } + this->get_scene_mode(); + this->get_sensitivity(); + this->get_custom_mode(); + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); +} + +void MR24HPC1Component::set_sensitivity(uint8_t value) { + if (value == 0x00) + return; + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_restart() { + this->send_query_(SET_RESTART, sizeof(SET_RESTART)); + this->check_dev_inf_sign_ = true; +} + +void MR24HPC1Component::set_unman_time(uint8_t value) { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x80, 0x0a, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_unmanned_time(); +} + +void MR24HPC1Component::set_custom_mode(uint8_t mode) { + if (mode == 0) { + this->set_custom_end_mode(); // Equivalent to end setting + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); + } + return; + } + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x09, 0x00, 0x01, mode, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); + this->get_custom_mode(); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_custom_end_mode() { + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x0a, 0x00, 0x01, 0x0F, 0xCB, 0x54, 0x43}; + this->send_query_(send_data, send_data_len); + if (this->custom_mode_number_ != nullptr) { + this->custom_mode_number_->publish_state(0); // Clear setpoints + } + this->get_existence_boundary(); + this->get_motion_boundary(); + this->get_existence_threshold(); + this->get_motion_threshold(); + this->get_motion_trigger_time(); + this->get_motion_to_rest_time(); + this->get_custom_unman_time(); + this->get_custom_mode(); + this->get_scene_mode(); + this->get_sensitivity(); +} + +void MR24HPC1Component::set_existence_boundary(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0A, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_boundary(); +} + +void MR24HPC1Component::set_motion_boundary(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0B, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_boundary(); +} + +void MR24HPC1Component::set_existence_threshold(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_existence_threshold(); +} + +void MR24HPC1Component::set_motion_threshold(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 10; + uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x09, 0x00, 0x01, value, 0x00, 0x54, 0x43}; + send_data[7] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_threshold(); +} + +void MR24HPC1Component::set_motion_trigger_time(uint8_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, value, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_trigger_time(); +} + +void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint8_t h8_num = (value >> 8) & 0xff; + uint8_t l8_num = value & 0xff; + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0D, 0x00, 0x04, 0x00, 0x00, h8_num, l8_num, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_motion_to_rest_time(); +} + +void MR24HPC1Component::set_custom_unman_time(uint16_t value) { + if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0)) + return; // You'll have to check that you're in custom mode to set it up + uint32_t value_ms = value * 1000; + uint8_t h24_num = (value_ms >> 24) & 0xff; + uint8_t h16_num = (value_ms >> 16) & 0xff; + uint8_t h8_num = (value_ms >> 8) & 0xff; + uint8_t l8_num = value_ms & 0xff; + uint8_t send_data_len = 13; + uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0E, 0x00, 0x04, h24_num, h16_num, h8_num, l8_num, 0x00, 0x54, 0x43}; + send_data[10] = get_frame_crc_sum(send_data, send_data_len); + this->send_query_(send_data, send_data_len); + this->get_custom_unman_time(); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h new file mode 100644 index 000000000000..8fc61ad37c77 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.h @@ -0,0 +1,217 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_SWITCH +#include "esphome/components/switch/switch.h" +#endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include "seeed_mr24hpc1_constants.h" + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +enum FrameState { + FRAME_IDLE, + FRAME_HEADER2, + FRAME_CTL_WORD, + FRAME_CMD_WORD, + FRAME_DATA_LEN_H, + FRAME_DATA_LEN_L, + FRAME_DATA_BYTES, + FRAME_DATA_CRC, + FRAME_TAIL1, + FRAME_TAIL2, +}; + +enum PollingState { + STANDARD_FUNCTION_QUERY_PRODUCT_MODE = 0, + STANDARD_FUNCTION_QUERY_PRODUCT_ID, + STANDARD_FUNCTION_QUERY_FIRMWARE_VERSION, + STANDARD_FUNCTION_QUERY_HARDWARE_MODE, // Above is the equipment information + STANDARD_FUNCTION_QUERY_SCENE_MODE, + STANDARD_FUNCTION_QUERY_SENSITIVITY, + STANDARD_FUNCTION_QUERY_UNMANNED_TIME, + STANDARD_FUNCTION_QUERY_HUMAN_STATUS, + STANDARD_FUNCTION_QUERY_HUMAN_MOTION_INF, + STANDARD_FUNCTION_QUERY_BODY_MOVE_PARAMETER, + STANDARD_FUNCTION_QUERY_KEEPAWAY_STATUS, + STANDARD_QUERY_CUSTOM_MODE, + STANDARD_FUNCTION_QUERY_HEARTBEAT_STATE, // Above is the basic function + + CUSTOM_FUNCTION_QUERY_EXISTENCE_BOUNDARY, + CUSTOM_FUNCTION_QUERY_MOTION_BOUNDARY, + CUSTOM_FUNCTION_QUERY_EXISTENCE_THRESHOLD, + CUSTOM_FUNCTION_QUERY_MOTION_THRESHOLD, + CUSTOM_FUNCTION_QUERY_MOTION_TRIGGER_TIME, + CUSTOM_FUNCTION_QUERY_MOTION_TO_REST_TIME, + CUSTOM_FUNCTION_QUERY_TIME_OF_ENTER_UNMANNED, + + UNDERLY_FUNCTION_QUERY_HUMAN_STATUS, + UNDERLY_FUNCTION_QUERY_SPATIAL_STATIC_VALUE, + UNDERLY_FUNCTION_QUERY_SPATIAL_MOTION_VALUE, + UNDERLY_FUNCTION_QUERY_DISTANCE_OF_STATIC_OBJECT, + UNDERLY_FUNCTION_QUERY_DISTANCE_OF_MOVING_OBJECT, + UNDERLY_FUNCTION_QUERY_TARGET_MOVEMENT_SPEED, +}; + +enum OutputSwitch { + OUTPUT_SWITCH_INIT, + OUTPUT_SWITCH_ON, + OUTPUT_SWTICH_OFF, +}; + +static const char *const S_SCENE_STR[5] = {"None", "Living Room", "Bedroom", "Washroom", "Area Detection"}; +static const bool S_SOMEONE_EXISTS_STR[2] = {false, true}; +static const char *const S_MOTION_STATUS_STR[3] = {"None", "Motionless", "Active"}; +static const char *const S_KEEP_AWAY_STR[3] = {"None", "Close", "Away"}; +static const char *const S_UNMANNED_TIME_STR[9] = {"None", "10s", "30s", "1min", "2min", + "5min", "10min", "30min", "60min"}; +static const char *const S_BOUNDARY_STR[10] = {"0.5m", "1.0m", "1.5m", "2.0m", "2.5m", + "3.0m", "3.5m", "4.0m", "4.5m", "5.0m"}; // uint: m +static const float S_PRESENCE_OF_DETECTION_RANGE_STR[7] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f}; // uint: m + +class MR24HPC1Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_TEXT_SENSOR + SUB_TEXT_SENSOR(heartbeat_state) + SUB_TEXT_SENSOR(product_model) + SUB_TEXT_SENSOR(product_id) + SUB_TEXT_SENSOR(hardware_model) + SUB_TEXT_SENSOR(firware_version) + SUB_TEXT_SENSOR(keep_away) + SUB_TEXT_SENSOR(motion_status) + SUB_TEXT_SENSOR(custom_mode_end) +#endif +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(has_target) +#endif +#ifdef USE_SENSOR + SUB_SENSOR(custom_presence_of_detection) + SUB_SENSOR(movement_signs) + SUB_SENSOR(custom_motion_distance) + SUB_SENSOR(custom_spatial_static_value) + SUB_SENSOR(custom_spatial_motion_value) + SUB_SENSOR(custom_motion_speed) + SUB_SENSOR(custom_mode_num) +#endif +#ifdef USE_SWITCH + SUB_SWITCH(underlying_open_function) +#endif +#ifdef USE_BUTTON + SUB_BUTTON(restart) + SUB_BUTTON(custom_set_end) +#endif +#ifdef USE_SELECT + SUB_SELECT(scene_mode) + SUB_SELECT(unman_time) + SUB_SELECT(existence_boundary) + SUB_SELECT(motion_boundary) +#endif +#ifdef USE_NUMBER + SUB_NUMBER(sensitivity) + SUB_NUMBER(custom_mode) + SUB_NUMBER(existence_threshold) + SUB_NUMBER(motion_threshold) + SUB_NUMBER(motion_trigger) + SUB_NUMBER(motion_to_rest) + SUB_NUMBER(custom_unman_time) +#endif + + protected: + char c_product_mode_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_product_id_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_hardware_model_[PRODUCT_BUF_MAX_SIZE + 1]; + char c_firmware_version_[PRODUCT_BUF_MAX_SIZE + 1]; + uint8_t s_output_info_switch_flag_; + uint8_t sg_recv_data_state_; + uint8_t sg_frame_len_; + uint8_t sg_data_len_; + uint8_t sg_frame_buf_[FRAME_BUF_MAX_SIZE]; + uint8_t sg_frame_prase_buf_[FRAME_BUF_MAX_SIZE]; + int sg_start_query_data_; + bool check_dev_inf_sign_; + bool poll_time_base_func_check_; + + void update_(); + void r24_split_data_frame_(uint8_t value); + void r24_parse_data_frame_(uint8_t *data, uint8_t len); + void r24_frame_parse_open_underlying_information_(uint8_t *data); + void r24_frame_parse_work_status_(uint8_t *data); + void r24_frame_parse_product_information_(uint8_t *data); + void r24_frame_parse_human_information_(uint8_t *data); + void send_query_(const uint8_t *query, size_t string_length); + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void setup() override; + void dump_config() override; + void loop() override; + + void get_heartbeat_packet(); + void get_radar_output_information_switch(); + void get_product_mode(); + void get_product_id(); + void get_hardware_model(); + void get_firmware_version(); + void get_human_status(); + void get_human_motion_info(); + void get_body_motion_params(); + void get_keep_away(); + void get_scene_mode(); + void get_sensitivity(); + void get_unmanned_time(); + void get_custom_mode(); + void get_existence_boundary(); + void get_motion_boundary(); + void get_spatial_static_value(); + void get_spatial_motion_value(); + void get_distance_of_static_object(); + void get_distance_of_moving_object(); + void get_target_movement_speed(); + void get_existence_threshold(); + void get_motion_threshold(); + void get_motion_trigger_time(); + void get_motion_to_rest_time(); + void get_custom_unman_time(); + + void set_scene_mode(uint8_t value); + void set_underlying_open_function(bool enable); + void set_sensitivity(uint8_t value); + void set_restart(); + void set_unman_time(uint8_t value); + void set_custom_mode(uint8_t mode); + void set_custom_end_mode(); + void set_existence_boundary(uint8_t value); + void set_motion_boundary(uint8_t value); + void set_existence_threshold(uint8_t value); + void set_motion_threshold(uint8_t value); + void set_motion_trigger_time(uint8_t value); + void set_motion_to_rest_time(uint16_t value); + void set_custom_unman_time(uint16_t value); +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h new file mode 100644 index 000000000000..dafc6c036806 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1_constants.h @@ -0,0 +1,173 @@ +#pragma once + +#include + +namespace esphome { +namespace seeed_mr24hpc1 { + +static const uint8_t FRAME_BUF_MAX_SIZE = 128; +static const uint8_t PRODUCT_BUF_MAX_SIZE = 32; + +static const uint8_t FRAME_CONTROL_WORD_INDEX = 2; +static const uint8_t FRAME_COMMAND_WORD_INDEX = 3; +static const uint8_t FRAME_DATA_INDEX = 6; + +static const uint8_t FRAME_HEADER1_VALUE = 0x53; +static const uint8_t FRAME_HEADER2_VALUE = 0x59; +static const uint8_t FRAME_TAIL1_VALUE = 0x54; +static const uint8_t FRAME_TAIL2_VALUE = 0x43; + +static const uint8_t CONTROL_MAIN = 0x01; +static const uint8_t CONTROL_PRODUCT_INFORMATION = 0x02; +static const uint8_t CONTROL_WORK = 0x05; +static const uint8_t CONTROL_UNDERLYING_FUNCTION = 0x08; +static const uint8_t CONTROL_HUMAN_INFORMATION = 0x80; + +static const uint8_t COMMAND_HEARTBEAT = 0x01; +static const uint8_t COMMAND_RESTART = 0x02; + +static const uint8_t COMMAND_PRODUCT_MODE = 0xA1; +static const uint8_t COMMAND_PRODUCT_ID = 0xA2; +static const uint8_t COMMAND_HARDWARE_MODEL = 0xA3; +static const uint8_t COMMAND_FIRMWARE_VERSION = 0xA4; + +static const uint8_t GET_HEARTBEAT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_MAIN, COMMAND_HEARTBEAT, 0x00, 0x01, 0x0F, 0xBE, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t SET_RESTART[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_MAIN, COMMAND_RESTART, 0x00, 0x01, 0x0F, 0xBF, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_PRODUCT_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_PRODUCT_INFORMATION, COMMAND_PRODUCT_MODE, 0x00, 0x01, 0x0F, 0x5F, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_PRODUCT_ID[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_PRODUCT_INFORMATION, COMMAND_PRODUCT_ID, 0x00, 0x01, 0x0F, 0x60, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_HARDWARE_MODEL[] = { + FRAME_HEADER1_VALUE, + FRAME_HEADER2_VALUE, + CONTROL_PRODUCT_INFORMATION, + COMMAND_HARDWARE_MODEL, + 0x00, + 0x01, + 0x0F, + 0x61, + FRAME_TAIL1_VALUE, + FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_FIRMWARE_VERSION[] = { + FRAME_HEADER1_VALUE, + FRAME_HEADER2_VALUE, + CONTROL_PRODUCT_INFORMATION, + COMMAND_FIRMWARE_VERSION, + 0x00, + 0x01, + 0x0F, + 0x62, + FRAME_TAIL1_VALUE, + FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_SCENE_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x87, 0x00, 0x01, 0x0F, 0x48, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SENSITIVITY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x88, 0x00, 0x01, 0x0F, 0x49, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_CUSTOM_MODE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_WORK, 0x89, 0x00, 0x01, 0x0F, 0x4A, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t UNDERLYING_SWITCH_ON[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x00, 0x00, 0x01, 0x01, 0xB6, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t UNDERLYING_SWITCH_OFF[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x00, 0x00, 0x01, 0x00, 0xB5, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_RADAR_OUTPUT_INFORMATION_SWITCH[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x80, 0x00, 0x01, 0x0F, 0x44, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SPATIAL_STATIC_VALUE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x81, 0x00, 0x01, 0x0F, 0x45, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_SPATIAL_MOTION_VALUE[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x82, 0x00, 0x01, 0x0F, 0x46, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_DISTANCE_OF_STATIC_OBJECT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x83, 0x00, 0x01, 0x0F, 0x47, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_DISTANCE_OF_MOVING_OBJECT[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x84, 0x00, 0x01, 0x0F, 0x48, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_TARGET_MOVEMENT_SPEED[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x85, 0x00, 0x01, 0x0F, 0x49, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_EXISTENCE_THRESHOLD[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x88, 0x00, 0x01, 0x0F, 0x4C, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_THRESHOLD[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x89, 0x00, 0x01, 0x0F, 0x4D, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_EXISTENCE_BOUNDARY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8A, 0x00, 0x01, 0x0F, 0x4E, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_BOUNDARY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8B, 0x00, 0x01, 0x0F, 0x4F, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_TRIGGER_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8C, 0x00, 0x01, 0x0F, 0x50, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_MOTION_TO_REST_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8D, 0x00, 0x01, 0x0F, 0x51, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_CUSTOM_UNMAN_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_UNDERLYING_FUNCTION, 0x8E, 0x00, 0x01, 0x0F, 0x52, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +static const uint8_t GET_HUMAN_STATUS[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x81, 0x00, 0x01, 0x0F, 0xBD, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_HUMAN_MOTION_INFORMATION[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x82, 0x00, 0x01, 0x0F, 0xBE, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_BODY_MOTION_PARAMETERS[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x83, 0x00, 0x01, 0x0F, 0xBF, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_UNMANNED_TIME[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x8A, 0x00, 0x01, 0x0F, 0xC6, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; +static const uint8_t GET_KEEP_AWAY[] = { + FRAME_HEADER1_VALUE, FRAME_HEADER2_VALUE, CONTROL_HUMAN_INFORMATION, 0x8B, 0x00, 0x01, 0x0F, 0xC7, + FRAME_TAIL1_VALUE, FRAME_TAIL2_VALUE, +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/__init__.py b/esphome/components/seeed_mr24hpc1/select/__init__.py new file mode 100644 index 000000000000..7da83627b95c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/__init__.py @@ -0,0 +1,103 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +SceneModeSelect = mr24hpc1_ns.class_("SceneModeSelect", select.Select) +UnmanTimeSelect = mr24hpc1_ns.class_("UnmanTimeSelect", select.Select) +ExistenceBoundarySelect = mr24hpc1_ns.class_("ExistenceBoundarySelect", select.Select) +MotionBoundarySelect = mr24hpc1_ns.class_("MotionBoundarySelect", select.Select) + +CONF_SCENE_MODE = "scene_mode" +CONF_UNMAN_TIME = "unman_time" +CONF_EXISTENCE_BOUNDARY = "existence_boundary" +CONF_MOTION_BOUNDARY = "motion_boundary" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_SCENE_MODE): select.select_schema( + SceneModeSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:hoop-house", + ), + cv.Optional(CONF_UNMAN_TIME): select.select_schema( + UnmanTimeSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:timeline-clock", + ), + cv.Optional(CONF_EXISTENCE_BOUNDARY): select.select_schema( + ExistenceBoundarySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + cv.Optional(CONF_MOTION_BOUNDARY): select.select_schema( + MotionBoundarySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if scenemode_config := config.get(CONF_SCENE_MODE): + s = await select.new_select( + scenemode_config, + options=["None", "Living Room", "Bedroom", "Washroom", "Area Detection"], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_scene_mode_select(s)) + if unmantime_config := config.get(CONF_UNMAN_TIME): + s = await select.new_select( + unmantime_config, + options=[ + "None", + "10s", + "30s", + "1min", + "2min", + "5min", + "10min", + "30min", + "60min", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_unman_time_select(s)) + if existence_boundary_config := config.get(CONF_EXISTENCE_BOUNDARY): + s = await select.new_select( + existence_boundary_config, + options=[ + "0.5m", + "1.0m", + "1.5m", + "2.0m", + "2.5m", + "3.0m", + "3.5m", + "4.0m", + "4.5m", + "5.0m", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_existence_boundary_select(s)) + if motion_boundary_config := config.get(CONF_MOTION_BOUNDARY): + s = await select.new_select( + motion_boundary_config, + options=[ + "0.5m", + "1.0m", + "1.5m", + "2.0m", + "2.5m", + "3.0m", + "3.5m", + "4.0m", + "4.5m", + "5.0m", + ], + ) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_motion_boundary_select(s)) diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp new file mode 100644 index 000000000000..03c2ec474585 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.cpp @@ -0,0 +1,15 @@ +#include "existence_boundary_select.h" + +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()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h new file mode 100644 index 000000000000..ad770a729648 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/existence_boundary_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class ExistenceBoundarySelect : public select::Select, public Parented { + public: + ExistenceBoundarySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp new file mode 100644 index 000000000000..619a4f093527 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.cpp @@ -0,0 +1,15 @@ +#include "motion_boundary_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void MotionBoundarySelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_motion_boundary(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h new file mode 100644 index 000000000000..9058e3130b76 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/motion_boundary_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class MotionBoundarySelect : public select::Select, public Parented { + public: + MotionBoundarySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp new file mode 100644 index 000000000000..153ae603cf3c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.cpp @@ -0,0 +1,15 @@ +#include "scene_mode_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void SceneModeSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_scene_mode(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h new file mode 100644 index 000000000000..95508d49b042 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/scene_mode_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class SceneModeSelect : public select::Select, public Parented { + public: + SceneModeSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp b/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp new file mode 100644 index 000000000000..a9d96c8f6706 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/unman_time_select.cpp @@ -0,0 +1,15 @@ +#include "unman_time_select.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void UnmanTimeSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_unman_time(index.value()); + } +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/select/unman_time_select.h b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h new file mode 100644 index 000000000000..7131988cdafa --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/select/unman_time_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class UnmanTimeSelect : public select::Select, public Parented { + public: + UnmanTimeSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/sensor.py b/esphome/components/seeed_mr24hpc1/sensor.py new file mode 100644 index 000000000000..d5eb09e2656c --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/sensor.py @@ -0,0 +1,82 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_SPEED, + UNIT_METER, +) +from . import CONF_MR24HPC1_ID, MR24HPC1Component + +CONF_CUSTOM_PRESENCE_OF_DETECTION = "custom_presence_of_detection" +CONF_MOVEMENT_SIGNS = "movement_signs" +CONF_CUSTOM_MOTION_DISTANCE = "custom_motion_distance" +CONF_CUSTOM_SPATIAL_STATIC_VALUE = "custom_spatial_static_value" +CONF_CUSTOM_SPATIAL_MOTION_VALUE = "custom_spatial_motion_value" +CONF_CUSTOM_MOTION_SPEED = "custom_motion_speed" +CONF_CUSTOM_MODE_NUM = "custom_mode_num" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_CUSTOM_PRESENCE_OF_DETECTION): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + unit_of_measurement=UNIT_METER, + accuracy_decimals=2, # Specify the number of decimal places + icon="mdi:signal-distance-variant", + ), + cv.Optional(CONF_MOVEMENT_SIGNS): sensor.sensor_schema( + icon="mdi:human-greeting-variant", + ), + cv.Optional(CONF_CUSTOM_MOTION_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_METER, + accuracy_decimals=2, + icon="mdi:signal-distance-variant", + ), + cv.Optional(CONF_CUSTOM_SPATIAL_STATIC_VALUE): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, + icon="mdi:counter", + ), + cv.Optional(CONF_CUSTOM_SPATIAL_MOTION_VALUE): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, + icon="mdi:counter", + ), + cv.Optional(CONF_CUSTOM_MOTION_SPEED): sensor.sensor_schema( + unit_of_measurement="m/s", + device_class=DEVICE_CLASS_SPEED, + accuracy_decimals=2, + icon="mdi:run-fast", + ), + cv.Optional(CONF_CUSTOM_MODE_NUM): sensor.sensor_schema( + icon="mdi:counter", + ), + } +) + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if custompresenceofdetection_config := config.get( + CONF_CUSTOM_PRESENCE_OF_DETECTION + ): + sens = await sensor.new_sensor(custompresenceofdetection_config) + cg.add(mr24hpc1_component.set_custom_presence_of_detection_sensor(sens)) + if movementsigns_config := config.get(CONF_MOVEMENT_SIGNS): + sens = await sensor.new_sensor(movementsigns_config) + cg.add(mr24hpc1_component.set_movement_signs_sensor(sens)) + if custommotiondistance_config := config.get(CONF_CUSTOM_MOTION_DISTANCE): + sens = await sensor.new_sensor(custommotiondistance_config) + cg.add(mr24hpc1_component.set_custom_motion_distance_sensor(sens)) + if customspatialstaticvalue_config := config.get(CONF_CUSTOM_SPATIAL_STATIC_VALUE): + sens = await sensor.new_sensor(customspatialstaticvalue_config) + cg.add(mr24hpc1_component.set_custom_spatial_static_value_sensor(sens)) + if customspatialmotionvalue_config := config.get(CONF_CUSTOM_SPATIAL_MOTION_VALUE): + sens = await sensor.new_sensor(customspatialmotionvalue_config) + cg.add(mr24hpc1_component.set_custom_spatial_motion_value_sensor(sens)) + if custommotionspeed_config := config.get(CONF_CUSTOM_MOTION_SPEED): + sens = await sensor.new_sensor(custommotionspeed_config) + cg.add(mr24hpc1_component.set_custom_motion_speed_sensor(sens)) + if custommodenum_config := config.get(CONF_CUSTOM_MODE_NUM): + sens = await sensor.new_sensor(custommodenum_config) + cg.add(mr24hpc1_component.set_custom_mode_num_sensor(sens)) diff --git a/esphome/components/seeed_mr24hpc1/switch/__init__.py b/esphome/components/seeed_mr24hpc1/switch/__init__.py new file mode 100644 index 000000000000..bbf5391a578d --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_SWITCH, + ENTITY_CATEGORY_CONFIG, +) +from .. import CONF_MR24HPC1_ID, MR24HPC1Component, mr24hpc1_ns + +UnderlyingOpenFuncSwitch = mr24hpc1_ns.class_( + "UnderlyOpenFunctionSwitch", switch.Switch +) + +CONF_UNDERLYING_OPEN_FUNCTION = "underlying_open_function" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_UNDERLYING_OPEN_FUNCTION): switch.switch_schema( + UnderlyingOpenFuncSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:electric-switch", + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if underlying_open_function_config := config.get(CONF_UNDERLYING_OPEN_FUNCTION): + s = await switch.new_switch(underlying_open_function_config) + await cg.register_parented(s, config[CONF_MR24HPC1_ID]) + cg.add(mr24hpc1_component.set_underlying_open_function_switch(s)) diff --git a/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp new file mode 100644 index 000000000000..0fcc49bc4c3d --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.cpp @@ -0,0 +1,12 @@ +#include "underlyFuc_switch.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +void UnderlyOpenFunctionSwitch::write_state(bool state) { + this->publish_state(state); + this->parent_->set_underlying_open_function(state); +} + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h new file mode 100644 index 000000000000..1baabb25ce78 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/switch/underlyFuc_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../seeed_mr24hpc1.h" + +namespace esphome { +namespace seeed_mr24hpc1 { + +class UnderlyOpenFunctionSwitch : public switch_::Switch, public Parented { + public: + UnderlyOpenFunctionSwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace seeed_mr24hpc1 +} // namespace esphome diff --git a/esphome/components/seeed_mr24hpc1/text_sensor.py b/esphome/components/seeed_mr24hpc1/text_sensor.py new file mode 100644 index 000000000000..aa50f577d4a2 --- /dev/null +++ b/esphome/components/seeed_mr24hpc1/text_sensor.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC +from . import CONF_MR24HPC1_ID, MR24HPC1Component + +CONF_HEART_BEAT = "heart_beat" +CONF_PRODUCT_MODEL = "product_model" +CONF_PRODUCT_ID = "product_id" +CONF_HARDWARE_MODEL = "hardware_model" +CONF_HARDWARE_VERSION = "hardware_version" + +CONF_KEEP_AWAY = "keep_away" +CONF_MOTION_STATUS = "motion_status" + +CONF_CUSTOM_MODE_END = "custom_mode_end" + + +# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR24HPC1_ID): cv.use_id(MR24HPC1Component), + cv.Optional(CONF_HEART_BEAT): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:connection" + ), + cv.Optional(CONF_PRODUCT_MODEL): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_PRODUCT_ID): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_HARDWARE_MODEL): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_HARDWARE_VERSION): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:information-outline" + ), + cv.Optional(CONF_KEEP_AWAY): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:walk" + ), + cv.Optional(CONF_MOTION_STATUS): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:human-greeting" + ), + cv.Optional(CONF_CUSTOM_MODE_END): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon="mdi:account-check" + ), +} + + +async def to_code(config): + mr24hpc1_component = await cg.get_variable(config[CONF_MR24HPC1_ID]) + if heartbeat_config := config.get(CONF_HEART_BEAT): + sens = await text_sensor.new_text_sensor(heartbeat_config) + cg.add(mr24hpc1_component.set_heartbeat_state_text_sensor(sens)) + if productmodel_config := config.get(CONF_PRODUCT_MODEL): + sens = await text_sensor.new_text_sensor(productmodel_config) + cg.add(mr24hpc1_component.set_product_model_text_sensor(sens)) + if productid_config := config.get(CONF_PRODUCT_ID): + sens = await text_sensor.new_text_sensor(productid_config) + cg.add(mr24hpc1_component.set_product_id_text_sensor(sens)) + if hardwaremodel_config := config.get(CONF_HARDWARE_MODEL): + sens = await text_sensor.new_text_sensor(hardwaremodel_config) + cg.add(mr24hpc1_component.set_hardware_model_text_sensor(sens)) + if firwareversion_config := config.get(CONF_HARDWARE_VERSION): + sens = await text_sensor.new_text_sensor(firwareversion_config) + cg.add(mr24hpc1_component.set_firware_version_text_sensor(sens)) + if keepaway_config := config.get(CONF_KEEP_AWAY): + sens = await text_sensor.new_text_sensor(keepaway_config) + cg.add(mr24hpc1_component.set_keep_away_text_sensor(sens)) + if motionstatus_config := config.get(CONF_MOTION_STATUS): + sens = await text_sensor.new_text_sensor(motionstatus_config) + cg.add(mr24hpc1_component.set_motion_status_text_sensor(sens)) + if custommodeend_config := config.get(CONF_CUSTOM_MODE_END): + sens = await text_sensor.new_text_sensor(custommodeend_config) + cg.add(mr24hpc1_component.set_custom_mode_end_text_sensor(sens)) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 760f7600b774..7ad14f244093 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -95,8 +95,8 @@ async def setup_select_core_(var, config, *, options: list[str]): trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf ) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) @@ -223,14 +223,14 @@ async def select_set_index_to_code(config, action_id, template_arg, args): async def select_operation_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) - if CONF_OPERATION in config: - op_ = await cg.templatable(config[CONF_OPERATION], args, SelectOperation) + if (operation := config.get(CONF_OPERATION)) is not None: + op_ = await cg.templatable(operation, args, SelectOperation) cg.add(var.set_operation(op_)) - if CONF_CYCLE in config: - cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool) - cg.add(var.set_cycle(cycle_)) - if CONF_MODE in config: - cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[config[CONF_MODE]])) - if CONF_CYCLE in config: - cg.add(var.set_cycle(config[CONF_CYCLE])) + if (cycle := config.get(CONF_CYCLE)) is not None: + template_ = await cg.templatable(cycle, args, bool) + cg.add(var.set_cycle(template_)) + if (mode := config.get(CONF_MODE)) is not None: + cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[mode])) + if (cycle := config.get(CONF_CYCLE)) is not None: + cg.add(var.set_cycle(cycle)) return var diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index f4583b4e2ea4..806882ad9462 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -12,7 +12,7 @@ void Select::publish_state(const std::string &state) { if (index.has_value()) { this->has_state_ = true; this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %s (index %d)", name, state.c_str(), index.value()); + ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value()); this->state_callback_.call(state, index.value()); } else { ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str()); diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index 6ee41b10297b..85f755645c75 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -71,7 +71,7 @@ void SelectCall::perform() { return; } if (this->index_.value() >= options.size()) { - ESP_LOGW(TAG, "'%s' - Index value %d out of bounds", name, this->index_.value()); + ESP_LOGW(TAG, "'%s' - Index value %zu out of bounds", name, this->index_.value()); return; } target_value = options[this->index_.value()]; diff --git a/esphome/components/sen0321/__init__.py b/esphome/components/sen0321/__init__.py new file mode 100644 index 000000000000..458ffa67f95f --- /dev/null +++ b/esphome/components/sen0321/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@notjj"] diff --git a/esphome/components/sen0321/sen0321.cpp b/esphome/components/sen0321/sen0321.cpp new file mode 100644 index 000000000000..7801c8c38942 --- /dev/null +++ b/esphome/components/sen0321/sen0321.cpp @@ -0,0 +1,36 @@ +#include "sen0321.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace sen0321_sensor { + +static const char *const TAG = "sen0321_sensor.sensor"; + +void Sen0321Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up sen0321..."); + if (!this->write_byte(SENSOR_MODE_REGISTER, SENSOR_MODE_AUTO)) { + ESP_LOGW(TAG, "Error setting measurement mode."); + this->mark_failed(); + }; +} + +void Sen0321Sensor::update() { this->read_data_(); } + +void Sen0321Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "DF Robot Ozone Sensor sen0321:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with sen0321 failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +void Sen0321Sensor::read_data_() { + uint8_t result[2]; + this->read_bytes(SENSOR_AUTO_READ_REG, result, (uint8_t) 2); + this->publish_state(((uint16_t) (result[0] << 8) + result[1])); +} + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sen0321.h b/esphome/components/sen0321/sen0321.h new file mode 100644 index 000000000000..3bb3d5b01548 --- /dev/null +++ b/esphome/components/sen0321/sen0321.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/DFRobot/DFRobot_OzoneSensor + +namespace esphome { +namespace sen0321_sensor { +// Sensor Mode +// While passive is supposedly supported, it does not appear to work reliably. +static const uint8_t SENSOR_MODE_REGISTER = 0x03; +static const uint8_t SENSOR_MODE_AUTO = 0x00; +static const uint8_t SENSOR_MODE_PASSIVE = 0x01; +static const uint8_t SET_REGISTER = 0x04; + +// Each register is 2 wide, so 0x07-0x08 for passive, or 0x09-0x0A for auto +// First register is high bits, next low. +static const uint8_t SENSOR_PASS_READ_REG = 0x07; +static const uint8_t SENSOR_AUTO_READ_REG = 0x09; + +class Sen0321Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void update() override; + void dump_config() override; + void setup() override; + + protected: + void read_data_(); +}; + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sensor.py b/esphome/components/sen0321/sensor.py new file mode 100644 index 000000000000..ee613dc4402e --- /dev/null +++ b/esphome/components/sen0321/sensor.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + ICON_CHEMICAL_WEAPON, + UNIT_PARTS_PER_BILLION, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@notjj"] +DEPENDENCIES = ["i2c"] + +sen0321_sensor_ns = cg.esphome_ns.namespace("sen0321_sensor") +Sen0321Sensor = sen0321_sensor_ns.class_( + "Sen0321Sensor", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + Sen0321Sensor, + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x73)) +) + + +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/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index 8b4dcda9efaa..0efc96194386 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -1,5 +1,6 @@ #include "sen5x.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -135,9 +136,12 @@ void SEN5XComponent::setup() { ESP_LOGD(TAG, "Firmware version %d", this->firmware_version_); if (this->voc_sensor_ && this->store_baseline_) { - // Hash with compilation time + 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 // This ensures the baseline storage is cleared after OTA - uint32_t hash = fnv1_hash(App.get_compilation_time()); + // 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)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { @@ -197,13 +201,19 @@ void SEN5XComponent::setup() { ESP_LOGE(TAG, "Failed to read RHT Acceleration mode"); } } - if (this->voc_tuning_params_.has_value()) + if (this->voc_tuning_params_.has_value()) { this->write_tuning_parameters_(SEN5X_CMD_VOC_ALGORITHM_TUNING, this->voc_tuning_params_.value()); - if (this->nox_tuning_params_.has_value()) + delay(20); + } + if (this->nox_tuning_params_.has_value()) { this->write_tuning_parameters_(SEN5X_CMD_NOX_ALGORITHM_TUNING, this->nox_tuning_params_.value()); + delay(20); + } - if (this->temperature_compensation_.has_value()) + if (this->temperature_compensation_.has_value()) { this->write_temperature_compensation_(this->temperature_compensation_.value()); + delay(20); + } // Finally start sensor measurements auto cmd = SEN5X_CMD_START_MEASUREMENTS_RHT_ONLY; @@ -342,7 +352,7 @@ void SEN5XComponent::update() { float humidity = measurements[4] / 100.0; if (measurements[4] == 0xFFFF) humidity = NAN; - float temperature = measurements[5] / 200.0; + float temperature = (int16_t) measurements[5] / 200.0; if (measurements[5] == 0xFFFF) temperature = NAN; float voc = measurements[6] / 10.0; diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h index f306003a8246..6d90636a8985 100644 --- a/esphome/components/sen5x/sen5x.h +++ b/esphome/components/sen5x/sen5x.h @@ -41,8 +41,8 @@ struct GasTuning { }; struct TemperatureCompensation { - uint16_t offset; - uint16_t normalized_offset_slope; + int16_t offset; + int16_t normalized_offset_slope; uint16_t time_constant; }; @@ -70,27 +70,33 @@ class SEN5XComponent : public PollingComponent, public sensirion_common::Sensiri void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, uint16_t std_initial, uint16_t gain_factor) { - voc_tuning_params_.value().index_offset = index_offset; - voc_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; - voc_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; - voc_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; - voc_tuning_params_.value().std_initial = std_initial; - voc_tuning_params_.value().gain_factor = gain_factor; + GasTuning tuning_params; + tuning_params.index_offset = index_offset; + tuning_params.learning_time_offset_hours = learning_time_offset_hours; + tuning_params.learning_time_gain_hours = learning_time_gain_hours; + tuning_params.gating_max_duration_minutes = gating_max_duration_minutes; + tuning_params.std_initial = std_initial; + tuning_params.gain_factor = gain_factor; + voc_tuning_params_ = tuning_params; } void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, uint16_t gain_factor) { - nox_tuning_params_.value().index_offset = index_offset; - nox_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; - nox_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; - nox_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; - nox_tuning_params_.value().std_initial = 50; - nox_tuning_params_.value().gain_factor = gain_factor; + GasTuning tuning_params; + tuning_params.index_offset = index_offset; + tuning_params.learning_time_offset_hours = learning_time_offset_hours; + tuning_params.learning_time_gain_hours = learning_time_gain_hours; + tuning_params.gating_max_duration_minutes = gating_max_duration_minutes; + tuning_params.std_initial = 50; + tuning_params.gain_factor = gain_factor; + nox_tuning_params_ = tuning_params; } void set_temperature_compensation(float offset, float normalized_offset_slope, uint16_t time_constant) { - temperature_compensation_.value().offset = offset * 200; - temperature_compensation_.value().normalized_offset_slope = normalized_offset_slope * 100; - temperature_compensation_.value().time_constant = time_constant; + TemperatureCompensation temp_comp; + temp_comp.offset = offset * 200; + temp_comp.normalized_offset_slope = normalized_offset_slope * 10000; + temp_comp.time_constant = time_constant; + temperature_compensation_ = temp_comp; } bool start_fan_cleaning(); diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 392510e41779..67bd627f7f4c 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -14,13 +14,12 @@ CONF_PM_4_0, CONF_STORE_BASELINE, CONF_TEMPERATURE, + DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_NITROUS_OXIDE, DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, ICON_CHEMICAL_WEAPON, ICON_RADIATOR, ICON_THERMOMETER, @@ -88,6 +87,15 @@ } ) + +def float_previously_pct(value): + if isinstance(value, str) and "%" in value: + raise cv.Invalid( + f"The value '{value}' is a percentage. Suggested value: {float(value.strip('%')) / 100}" + ) + return value + + CONFIG_SCHEMA = ( cv.Schema( { @@ -123,13 +131,13 @@ cv.Optional(CONF_VOC): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend(GAS_SENSOR), cv.Optional(CONF_NOX): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_NITROUS_OXIDE, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend(GAS_SENSOR), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, @@ -151,7 +159,9 @@ cv.Optional(CONF_TEMPERATURE_COMPENSATION): cv.Schema( { cv.Optional(CONF_OFFSET, default=0): cv.float_, - cv.Optional(CONF_NORMALIZED_OFFSET_SLOPE, default=0): cv.percentage, + cv.Optional(CONF_NORMALIZED_OFFSET_SLOPE, default=0): cv.All( + float_previously_pct, cv.float_ + ), cv.Optional(CONF_TIME_CONSTANT, default=0): cv.int_, } ), diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index e0504eb2b9ba..e58ee157f7bf 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -54,9 +54,9 @@ void SenseAirComponent::update() { this->status_clear_warning(); const uint8_t length = response[2]; const uint16_t status = (uint16_t(response[3]) << 8) | response[4]; - const uint16_t ppm = (uint16_t(response[length + 1]) << 8) | response[length + 2]; + const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]); - ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status); + ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status); if (this->co2_sensor_ != nullptr) this->co2_sensor_->publish_state(ppm); } diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 8f7d581b2d5e..ece232e1a62f 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -16,6 +16,7 @@ CONF_FROM, CONF_ICON, CONF_ID, + CONF_IGNORE_OUT_OF_RANGE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -81,10 +82,12 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WIND_SPEED, + ENTITY_CATEGORY_CONFIG, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_generator import MockObjClass @@ -139,6 +142,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_FLOW_RATE, DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, @@ -188,6 +192,15 @@ def validate_datapoint(value): return validate_datapoint({CONF_FROM: cv.float_(a), CONF_TO: cv.float_(b)}) +_SENSOR_ENTITY_CATEGORIES = { + k: v for k, v in cv.ENTITY_CATEGORIES.items() if k != ENTITY_CATEGORY_CONFIG +} + + +def sensor_entity_category(value): + return cv.enum(_SENSOR_ENTITY_CATEGORIES, lower=True)(value) + + # Base Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") @@ -232,6 +245,7 @@ def validate_datapoint(value): CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter) SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter) ClampFilter = sensor_ns.class_("ClampFilter", Filter) +RoundFilter = sensor_ns.class_("RoundFilter", Filter) validate_unit_of_measurement = cv.string_strict validate_accuracy_decimals = cv.int_ @@ -246,6 +260,7 @@ def validate_datapoint(value): cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, 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." ), @@ -301,7 +316,7 @@ def sensor_schema( (CONF_ACCURACY_DECIMALS, accuracy_decimals, validate_accuracy_decimals), (CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_STATE_CLASS, state_class, validate_state_class), - (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), + (CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category), ]: if default is not _UNDEF: schema[cv.Optional(key, default=default)] = validator @@ -676,6 +691,7 @@ def validate_clamp(config): { cv.Optional(CONF_MIN_VALUE, default="NaN"): cv.float_, cv.Optional(CONF_MAX_VALUE, default="NaN"): cv.float_, + cv.Optional(CONF_IGNORE_OUT_OF_RANGE, default=False): cv.boolean, } ), validate_clamp, @@ -688,6 +704,24 @@ async def clamp_filter_to_code(config, filter_id): filter_id, config[CONF_MIN_VALUE], config[CONF_MAX_VALUE], + config[CONF_IGNORE_OUT_OF_RANGE], + ) + + +@FILTER_REGISTRY.register( + "round", + RoundFilter, + cv.maybe_simple_value( + { + cv.Required(CONF_ACCURACY_DECIMALS): cv.uint8_t, + }, + key=CONF_ACCURACY_DECIMALS, + ), +) +async def round_filter_to_code(config, filter_id): + return cg.new_Pvariable( + filter_id, + config[CONF_ACCURACY_DECIMALS], ) @@ -698,14 +732,14 @@ async def build_filters(config): async def setup_sensor_core_(var, config): await setup_entity(var, config) - if CONF_DEVICE_CLASS in config: - cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) - if CONF_STATE_CLASS in config: - cg.add(var.set_state_class(config[CONF_STATE_CLASS])) - if CONF_UNIT_OF_MEASUREMENT in config: - cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) - if CONF_ACCURACY_DECIMALS in config: - cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: + cg.add(var.set_device_class(device_class)) + if (state_class := config.get(CONF_STATE_CLASS)) is not None: + cg.add(var.set_state_class(state_class)) + if (unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)) is not None: + cg.add(var.set_unit_of_measurement(unit_of_measurement)) + if (accuracy_decimals := config.get(CONF_ACCURACY_DECIMALS)) is not None: + cg.add(var.set_accuracy_decimals(accuracy_decimals)) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) @@ -720,23 +754,23 @@ async def setup_sensor_core_(var, config): for conf in config.get(CONF_ON_VALUE_RANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await cg.register_component(trigger, conf) - if CONF_ABOVE in conf: - template_ = await cg.templatable(conf[CONF_ABOVE], [(float, "x")], float) + if (above := conf.get(CONF_ABOVE)) is not None: + template_ = await cg.templatable(above, [(float, "x")], float) cg.add(trigger.set_min(template_)) - if CONF_BELOW in conf: - template_ = await cg.templatable(conf[CONF_BELOW], [(float, "x")], float) + if (below := conf.get(CONF_BELOW)) is not None: + template_ = await cg.templatable(below, [(float, "x")], float) cg.add(trigger.set_max(template_)) await automation.build_automation(trigger, [(float, "x")], conf) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) - if CONF_EXPIRE_AFTER in config: - if config[CONF_EXPIRE_AFTER] is None: + if (expire_after := config.get(CONF_EXPIRE_AFTER, _UNDEF)) is not _UNDEF: + if expire_after is None: cg.add(mqtt_.disable_expire_after()) else: - cg.add(mqtt_.set_expire_after(config[CONF_EXPIRE_AFTER])) + cg.add(mqtt_.set_expire_after(expire_after)) async def register_sensor(var, config): @@ -769,10 +803,10 @@ async def sensor_in_range_to_code(config, condition_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(condition_id, template_arg, paren) - if CONF_ABOVE in config: - cg.add(var.set_min(config[CONF_ABOVE])) - if CONF_BELOW in config: - cg.add(var.set_max(config[CONF_BELOW])) + if (above := config.get(CONF_ABOVE)) is not None: + cg.add(var.set_min(above)) + if (below := config.get(CONF_BELOW)) is not None: + cg.add(var.set_max(below)) return var diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 6323023d5060..eaa909429b4f 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,8 +1,8 @@ #include "filter.h" +#include #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "sensor.h" -#include namespace esphome { namespace sensor { @@ -79,7 +79,7 @@ SkipInitialFilter::SkipInitialFilter(size_t num_to_ignore) : num_to_ignore_(num_ optional SkipInitialFilter::new_value(float value) { if (num_to_ignore_ > 0) { num_to_ignore_--; - ESP_LOGV(TAG, "SkipInitialFilter(%p)::new_value(%f) SKIPPING, %u left", this, value, num_to_ignore_); + ESP_LOGV(TAG, "SkipInitialFilter(%p)::new_value(%f) SKIPPING, %zu left", this, value, num_to_ignore_); return {}; } @@ -252,7 +252,9 @@ ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period optional ThrottleAverageFilter::new_value(float value) { ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value); - if (!std::isnan(value)) { + if (std::isnan(value)) { + this->have_nan_ = true; + } else { this->sum_ += value; this->n_++; } @@ -262,12 +264,14 @@ void ThrottleAverageFilter::setup() { this->set_interval("throttle_average", this->time_period_, [this]() { ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_); if (this->n_ == 0) { - this->output(NAN); + if (this->have_nan_) + this->output(NAN); } else { this->output(this->sum_ / this->n_); this->sum_ = 0.0f; this->n_ = 0; } + this->have_nan_ = false; }); } float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; } @@ -355,11 +359,15 @@ OrFilter::OrFilter(std::vector filters) : filters_(std::move(filters)) OrFilter::PhiNode::PhiNode(OrFilter *or_parent) : or_parent_(or_parent) {} optional OrFilter::PhiNode::new_value(float value) { - this->or_parent_->output(value); + if (!this->or_parent_->has_value_) { + this->or_parent_->output(value); + this->or_parent_->has_value_ = true; + } return {}; } optional OrFilter::new_value(float value) { + this->has_value_ = false; for (Filter *filter : this->filters_) filter->input(value); @@ -376,9 +384,7 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { // TimeoutFilter optional TimeoutFilter::new_value(float value) { this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_); }); - this->output(value); - - return {}; + return value; } TimeoutFilter::TimeoutFilter(uint32_t time_period, float new_value) : time_period_(time_period), value_(new_value) {} @@ -434,13 +440,34 @@ optional CalibratePolynomialFilter::new_value(float value) { return res; } -ClampFilter::ClampFilter(float min, float max) : min_(min), max_(max) {} +ClampFilter::ClampFilter(float min, float max, bool ignore_out_of_range) + : min_(min), max_(max), ignore_out_of_range_(ignore_out_of_range) {} optional ClampFilter::new_value(float value) { if (std::isfinite(value)) { - if (std::isfinite(this->min_) && value < this->min_) - return this->min_; - if (std::isfinite(this->max_) && value > this->max_) - return this->max_; + if (std::isfinite(this->min_) && value < this->min_) { + if (this->ignore_out_of_range_) { + return {}; + } else { + return this->min_; + } + } + + if (std::isfinite(this->max_) && value > this->max_) { + if (this->ignore_out_of_range_) { + return {}; + } else { + return this->max_; + } + } + } + return value; +} + +RoundFilter::RoundFilter(uint8_t precision) : precision_(precision) {} +optional RoundFilter::new_value(float value) { + if (std::isfinite(value)) { + float accuracy_mult = powf(10.0f, this->precision_); + return roundf(accuracy_mult * value) / accuracy_mult; } return value; } diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 46aeefac5633..c13cb3420ada 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -245,6 +245,7 @@ class ThrottleAverageFilter : public Filter, public Component { uint32_t time_period_; float sum_{0.0f}; unsigned int n_{0}; + bool have_nan_{false}; }; using lambda_filter_t = std::function(float)>; @@ -387,6 +388,7 @@ class OrFilter : public Filter { }; std::vector filters_; + bool has_value_{false}; PhiNode phi_; }; @@ -411,12 +413,22 @@ class CalibratePolynomialFilter : public Filter { class ClampFilter : public Filter { public: - ClampFilter(float min, float max); + ClampFilter(float min, float max, bool ignore_out_of_range); optional new_value(float value) override; protected: float min_{NAN}; float max_{NAN}; + bool ignore_out_of_range_; +}; + +class RoundFilter : public Filter { + public: + explicit RoundFilter(uint8_t precision); + optional new_value(float value) override; + + protected: + uint8_t precision_; }; } // namespace sensor diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 78fc45c67938..18e8c8087ee9 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -1,6 +1,7 @@ #include "servo.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include namespace esphome { namespace servo { @@ -14,8 +15,24 @@ void Servo::dump_config() { ESP_LOGCONFIG(TAG, " Idle Level: %.1f%%", this->idle_level_ * 100.0f); ESP_LOGCONFIG(TAG, " Min Level: %.1f%%", this->min_level_ * 100.0f); ESP_LOGCONFIG(TAG, " Max Level: %.1f%%", this->max_level_ * 100.0f); - ESP_LOGCONFIG(TAG, " auto detach time: %d ms", this->auto_detach_time_); - ESP_LOGCONFIG(TAG, " run duration: %d ms", this->transition_length_); + ESP_LOGCONFIG(TAG, " auto detach time: %" PRIu32 " ms", this->auto_detach_time_); + ESP_LOGCONFIG(TAG, " run duration: %" PRIu32 " ms", this->transition_length_); +} + +void Servo::setup() { + float v; + if (this->restore_) { + this->rtc_ = global_preferences->make_preference(global_servo_id); + global_servo_id++; + if (this->rtc_.load(&v)) { + this->target_value_ = v; + this->internal_write(v); + this->state_ = STATE_ATTACHED; + this->start_millis_ = millis(); + return; + } + } + this->detach(); } void Servo::loop() { @@ -24,7 +41,6 @@ void Servo::loop() { if (millis() - this->start_millis_ > this->auto_detach_time_) { this->detach(); this->start_millis_ = 0; - this->state_ = STATE_DETACHED; ESP_LOGD(TAG, "Servo detached on auto_detach_time"); } } @@ -53,8 +69,11 @@ void Servo::loop() { void Servo::write(float value) { value = clamp(value, -1.0f, 1.0f); - if (this->target_value_ == value) + if ((this->state_ == STATE_DETACHED) && (this->target_value_ == value)) { this->internal_write(value); + } else { + this->save_level_(value); + } this->target_value_ = value; this->source_value_ = this->current_value_; this->state_ = STATE_ATTACHED; @@ -71,11 +90,18 @@ void Servo::internal_write(float value) { level = lerp(value, this->idle_level_, this->max_level_); } this->output_->set_level(level); - if (this->target_value_ == this->current_value_) { - this->save_level_(level); - } this->current_value_ = value; } +void Servo::detach() { + this->state_ = STATE_DETACHED; + this->output_->set_level(0.0f); +} + +void Servo::save_level_(float v) { + if (this->restore_) + this->rtc_.save(&v); +} + } // namespace servo } // namespace esphome diff --git a/esphome/components/servo/servo.h b/esphome/components/servo/servo.h index e2e382315848..13a7472ae5fc 100644 --- a/esphome/components/servo/servo.h +++ b/esphome/components/servo/servo.h @@ -17,22 +17,8 @@ class Servo : public Component { void loop() override; void write(float value); void internal_write(float value); - void detach() { - this->output_->set_level(0.0f); - this->save_level_(0.0f); - } - void setup() override { - float v; - if (this->restore_) { - this->rtc_ = global_preferences->make_preference(global_servo_id); - global_servo_id++; - if (this->rtc_.load(&v)) { - this->output_->set_level(v); - return; - } - } - this->detach(); - } + void detach(); + void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } void set_min_level(float min_level) { min_level_ = min_level; } @@ -42,8 +28,10 @@ class Servo : public Component { void set_auto_detach_time(uint32_t auto_detach_time) { auto_detach_time_ = auto_detach_time; } void set_transition_length(uint32_t transition_length) { transition_length_ = transition_length; } + bool has_reached_target() { return this->current_value_ == this->target_value_; } + protected: - void save_level_(float v) { this->rtc_.save(&v); } + void save_level_(float v); output::FloatOutput *output_; float min_level_ = 0.0300f; diff --git a/esphome/components/sfa30/__init__.py b/esphome/components/sfa30/__init__.py new file mode 100644 index 000000000000..28b665906f58 --- /dev/null +++ b/esphome/components/sfa30/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ghsensdev"] diff --git a/esphome/components/sfa30/sensor.py b/esphome/components/sfa30/sensor.py new file mode 100644 index 000000000000..428f6b874b9d --- /dev/null +++ b/esphome/components/sfa30/sensor.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor, sensirion_common + +from esphome.const import ( + CONF_ID, + CONF_FORMALDEHYDE, + CONF_HUMIDITY, + CONF_TEMPERATURE, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_RADIATOR, + ICON_WATER_PERCENT, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_BILLION, + UNIT_PERCENT, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@ghsensdev"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] + +sfa30_ns = cg.esphome_ns.namespace("sfa30") + +SFA30Component = sfa30_ns.class_( + "SFA30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SFA30Component), + cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=1, + device_class=DEVICE_CLASS_GAS, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5D)) +) + +SENSOR_MAP = { + CONF_FORMALDEHYDE: "set_formaldehyde_sensor", + CONF_HUMIDITY: "set_humidity_sensor", + CONF_TEMPERATURE: "set_temperature_sensor", +} + + +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) + + for key, funcName in SENSOR_MAP.items(): + if sensor_config := config.get(key): + sens = await sensor.new_sensor(sensor_config) + cg.add(getattr(var, funcName)(sens)) diff --git a/esphome/components/sfa30/sfa30.cpp b/esphome/components/sfa30/sfa30.cpp new file mode 100644 index 000000000000..20d5ddad5e57 --- /dev/null +++ b/esphome/components/sfa30/sfa30.cpp @@ -0,0 +1,99 @@ +#include "sfa30.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sfa30 { + +static const char *const TAG = "sfa30"; + +static const uint16_t SFA30_CMD_GET_DEVICE_MARKING = 0xD060; +static const uint16_t SFA30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0006; +static const uint16_t SFA30_CMD_READ_MEASUREMENT = 0x0327; + +void SFA30Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up sfa30..."); + + // Serial Number identification + uint16_t raw_device_marking[16]; + if (!this->get_register(SFA30_CMD_GET_DEVICE_MARKING, raw_device_marking, 16, 5)) { + ESP_LOGE(TAG, "Failed to read device marking"); + this->error_code_ = DEVICE_MARKING_READ_FAILED; + this->mark_failed(); + return; + } + + for (size_t i = 0; i < 16; i++) { + this->device_marking_[i * 2] = static_cast(raw_device_marking[i] >> 8); + this->device_marking_[i * 2 + 1] = static_cast(raw_device_marking[i] & 0xFF); + } + ESP_LOGD(TAG, "Device Marking: '%s'", this->device_marking_); + + if (!this->write_command(SFA30_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + + ESP_LOGD(TAG, "Sensor initialized"); +} + +void SFA30Component::dump_config() { + ESP_LOGCONFIG(TAG, "sfa30:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case DEVICE_MARKING_READ_FAILED: + ESP_LOGW(TAG, "Unable to read device marking!"); + break; + case MEASUREMENT_INIT_FAILED: + ESP_LOGW(TAG, "Measurement initialization failed!"); + break; + default: + ESP_LOGW(TAG, "Unknown setup error!"); + break; + } + } + LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Device Marking: '%s'", this->device_marking_); + LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +void SFA30Component::update() { + if (!this->write_command(SFA30_CMD_READ_MEASUREMENT)) { + ESP_LOGW(TAG, "Error reading measurement!"); + this->status_set_warning(); + return; + } + + this->set_timeout(5, [this]() { + uint16_t raw_data[3]; + if (!this->read_data(raw_data, 3)) { + ESP_LOGW(TAG, "Error reading measurement data!"); + this->status_set_warning(); + return; + } + + if (this->formaldehyde_sensor_ != nullptr) { + const float formaldehyde = raw_data[0] / 5.0f; + this->formaldehyde_sensor_->publish_state(formaldehyde); + } + + if (this->humidity_sensor_ != nullptr) { + const float humidity = raw_data[1] / 100.0f; + this->humidity_sensor_->publish_state(humidity); + } + + if (this->temperature_sensor_ != nullptr) { + const float temperature = raw_data[2] / 200.0f; + this->temperature_sensor_->publish_state(temperature); + } + + this->status_clear_warning(); + }); +} + +} // namespace sfa30 +} // namespace esphome diff --git a/esphome/components/sfa30/sfa30.h b/esphome/components/sfa30/sfa30.h new file mode 100644 index 000000000000..fa2c59f624ff --- /dev/null +++ b/esphome/components/sfa30/sfa30.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" + +namespace esphome { +namespace sfa30 { + +class SFA30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice { + enum ErrorCode { DEVICE_MARKING_READ_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; + + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_formaldehyde_sensor(sensor::Sensor *formaldehyde) { this->formaldehyde_sensor_ = formaldehyde; } + void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; } + void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; } + + protected: + char device_marking_[32] = {0}; + + ErrorCode error_code_{UNKNOWN}; + + sensor::Sensor *formaldehyde_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; +}; + +} // namespace sfa30 +} // namespace esphome diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 6f8ed42d2587..13e859cc09a3 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -3,6 +3,7 @@ from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( + CONF_COMPENSATION, CONF_ID, CONF_BASELINE, CONF_ECO2, @@ -30,7 +31,6 @@ CONF_ECO2_BASELINE = "eco2_baseline" CONF_TVOC_BASELINE = "tvoc_baseline" CONF_UPTIME = "uptime" -CONF_COMPENSATION = "compensation" CONF_HUMIDITY_SOURCE = "humidity_source" diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 25a3c1ab8fff..261604b992e9 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -73,9 +73,10 @@ void SGP30Component::setup() { return; } - // Hash with compilation time + // Hash with compilation time and serial number // This ensures the baseline storage is cleared after OTA - uint32_t hash = fnv1_hash(App.get_compilation_time()); + // 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_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->baselines_storage_)) { @@ -255,7 +256,7 @@ void SGP30Component::dump_config() { } else { ESP_LOGCONFIG(TAG, " Baseline: No baseline configured"); } - ESP_LOGCONFIG(TAG, " Warm up time: %us", this->required_warm_up_time_); + ESP_LOGCONFIG(TAG, " Warm up time: %" PRIu32 "s", this->required_warm_up_time_); } LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "eCO2 sensor", this->eco2_sensor_); diff --git a/esphome/components/sgp30/sgp30.h b/esphome/components/sgp30/sgp30.h index d61eee00db79..9e882e6b0524 100644 --- a/esphome/components/sgp30/sgp30.h +++ b/esphome/components/sgp30/sgp30.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" #include "esphome/core/preferences.h" + +#include #include namespace esphome { diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index cb4231c1686c..ad9de6fe244a 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -2,7 +2,7 @@ CODEOWNERS = ["@SenexCrenshaw"] -CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( +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" ) diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index 3d24f6c409ce..b7cec542bfba 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -2,6 +2,7 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor, sensirion_common from esphome.const import ( + CONF_COMPENSATION, CONF_ID, CONF_STORE_BASELINE, CONF_TEMPERATURE_SOURCE, @@ -23,7 +24,6 @@ ) CONF_ALGORITHM_TUNING = "algorithm_tuning" -CONF_COMPENSATION = "compensation" CONF_GAIN_FACTOR = "gain_factor" CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" CONF_HUMIDITY_SOURCE = "humidity_source" diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 52f9adc80804..7e474b9371d6 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -61,23 +61,24 @@ void SGP4xComponent::setup() { ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF)); if (this->store_baseline_) { - // Hash with compilation time + // Hash with compilation time and serial number // This ensures the baseline storage is cleared after OTA - uint32_t hash = fnv1_hash(App.get_compilation_time()); + // 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_)); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { this->voc_state0_ = this->voc_baselines_storage_.state0; this->voc_state1_ = this->voc_baselines_storage_.state1; - ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->voc_baselines_storage_.state0, - voc_baselines_storage_.state1); + ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32, + this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); } // Initialize storage timestamp this->seconds_since_last_store_ = 0; if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) { - ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", + ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32, this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); voc_algorithm_.set_states(this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1); } @@ -165,7 +166,7 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { if (nox_sensor_) { nox = nox_algorithm_.process(nox_sraw); } - ESP_LOGV(TAG, "VOC = %d, NOx = %d", voc, nox); + ESP_LOGV(TAG, "VOC = %" PRId32 ", NOx = %" PRId32, voc, nox); // Store baselines after defined interval or if the difference between current and stored baseline becomes too // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { @@ -177,8 +178,8 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { this->voc_baselines_storage_.state1 = this->voc_state1_; if (this->pref_.save(&this->voc_baselines_storage_)) { - ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->voc_baselines_storage_.state0, - voc_baselines_storage_.state1); + ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 " ,state1: 0x%04" PRIX32, + this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); } else { ESP_LOGW(TAG, "Could not store VOC baselines"); } @@ -272,7 +273,7 @@ void SGP4xComponent::update_gas_indices() { } if (this->samples_read_ < this->samples_to_stabilize_) { this->samples_read_++; - ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_, + ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %" PRIu32, this->samples_read_, this->samples_to_stabilize_, this->voc_index_); return; } diff --git a/esphome/components/sgp4x/sgp4x.h b/esphome/components/sgp4x/sgp4x.h index 3a8d8200a717..aa5ae4b9d27a 100644 --- a/esphome/components/sgp4x/sgp4x.h +++ b/esphome/components/sgp4x/sgp4x.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" @@ -8,8 +11,6 @@ #include #include -#include - namespace esphome { namespace sgp4x { diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index c49193d13512..625784427f11 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -29,7 +29,8 @@ from esphome.core import HexInt, CORE DOMAIN = "shelly_dimmer" -DEPENDENCIES = ["sensor", "uart", "esp8266"] +AUTO_LOAD = ["sensor"] +DEPENDENCIES = ["uart", "esp8266"] shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer") ShellyDimmer = shelly_dimmer_ns.class_( @@ -57,6 +58,10 @@ "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.6/shelly-dimmer-stm32_v51.6.bin", "eda483e111c914723a33f5088f1397d5c0b19333db4a88dc965636b976c16c36", ), + "51.7": ( + "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.7/shelly-dimmer-stm32_v51.7.bin", + "7a20f1c967c469917368a79bc56498009045237080408cef7190743e08031889", + ), } @@ -87,12 +92,7 @@ def dl(url): url = value[CONF_URL] if CONF_SHA256 in value: # we have a hash, enable caching - path = ( - Path(CORE.config_dir) - / ".esphome" - / DOMAIN - / (value[CONF_SHA256] + "_fw_stm.bin") - ) + path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin") if not path.is_file(): firmware_data, dl_hash = dl(url) diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index 8e1ef426adfd..1286489b2901 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -12,6 +12,10 @@ UNIT_PERCENT, ) +CONF_HEATER_ENABLED = "heater_enabled" + +CODEOWNERS = ["@mrtoy-me"] + DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensirion_common"] @@ -24,19 +28,20 @@ cv.Schema( { cv.GenerateID(): cv.declare_id(SHT3XDComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), - } + cv.Optional(CONF_HEATER_ENABLED, default=False): cv.boolean, + }, ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x44)) @@ -48,6 +53,8 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) + cg.add(var.set_heater_enabled(config[CONF_HEATER_ENABLED])) + if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 4e1c9742bcd7..ffaf5db322e2 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -6,7 +6,16 @@ namespace sht3xd { static const char *const TAG = "sht3xd"; -static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER = 0x3780; +// https://sensirion.com/media/documents/E5762713/63D103C2/Sensirion_electronic_identification_code_SHT3x.pdf +// indicates two possible read serial number registers either with clock stretching enabled or disabled. +// Other SHT3XD_COMMAND registers use the clock stretching disabled register. +// To ensure compatibility, reading serial number using the register with clock stretching register enabled +// (used originally in this component) is tried first and if that fails the alternate register address +// with clock stretching disabled is read. + +static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER_CLOCK_STRETCHING = 0x3780; +static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER = 0x3682; + static const uint16_t SHT3XD_COMMAND_READ_STATUS = 0xF32D; static const uint16_t SHT3XD_COMMAND_CLEAR_STATUS = 0x3041; static const uint16_t SHT3XD_COMMAND_HEATER_ENABLE = 0x306D; @@ -18,25 +27,53 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000; void SHT3XDComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SHT3xD..."); uint16_t raw_serial_number[2]; - if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) { + if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER_CLOCK_STRETCHING, raw_serial_number, 2)) { + this->error_code_ = READ_SERIAL_STRETCHED_FAILED; + if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) { + this->error_code_ = READ_SERIAL_FAILED; + this->mark_failed(); + return; + } + } + + this->serial_number_ = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]); + + if (!this->write_command(heater_enabled_ ? SHT3XD_COMMAND_HEATER_ENABLE : SHT3XD_COMMAND_HEATER_DISABLE)) { + this->error_code_ = WRITE_HEATER_MODE_FAILED; this->mark_failed(); return; } - uint32_t serial_number = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]); - ESP_LOGV(TAG, " Serial Number: 0x%08X", serial_number); } + void SHT3XDComponent::dump_config() { ESP_LOGCONFIG(TAG, "SHT3xD:"); - LOG_I2C_DEVICE(this); + switch (this->error_code_) { + case READ_SERIAL_FAILED: + ESP_LOGD(TAG, " Error reading serial number"); + break; + case WRITE_HEATER_MODE_FAILED: + ESP_LOGD(TAG, " Error writing heater mode"); + break; + default: + break; + } if (this->is_failed()) { - ESP_LOGE(TAG, "Communication with SHT3xD failed!"); + ESP_LOGE(TAG, " Communication with SHT3xD failed!"); + return; } + ESP_LOGD(TAG, " Setup successful"); + ESP_LOGD(TAG, " Serial Number: 0x%08" PRIX32, this->serial_number_); + ESP_LOGD(TAG, " Heater Enabled: %s", this->heater_enabled_ ? "true" : "false"); + + LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); } + float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA; } + void SHT3XDComponent::update() { if (this->status_has_warning()) { ESP_LOGD(TAG, "Retrying to reconnect the sensor."); diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 41ca3c5d6e05..74f155121b35 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" +#include + namespace esphome { namespace sht3xd { @@ -17,10 +19,20 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir void dump_config() override; float get_setup_priority() const override; void update() override; + void set_heater_enabled(bool heater_enabled) { heater_enabled_ = heater_enabled; } protected: + enum ErrorCode { + NONE = 0, + READ_SERIAL_STRETCHED_FAILED, + READ_SERIAL_FAILED, + WRITE_HEATER_MODE_FAILED, + } error_code_{NONE}; + sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + bool heater_enabled_{true}; + uint32_t serial_number_{0}; }; } // namespace sht3xd diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 0f9123434d63..dea542ea9e0a 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -20,7 +20,7 @@ void SHT4XComponent::setup() { if (this->duty_cycle_ > 0.0) { uint32_t heater_interval = (uint32_t) (this->heater_time_ / this->duty_cycle_); - ESP_LOGD(TAG, "Heater interval: %i", heater_interval); + ESP_LOGD(TAG, "Heater interval: %" PRIu32, heater_interval); if (this->heater_power_ == SHT4X_HEATERPOWER_HIGH) { if (this->heater_time_ == SHT4X_HEATERTIME_LONG) { diff --git a/esphome/components/sht4x/sht4x.h b/esphome/components/sht4x/sht4x.h index 01553d5c154f..46037bb0e8f4 100644 --- a/esphome/components/sht4x/sht4x.h +++ b/esphome/components/sht4x/sht4x.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" +#include + namespace esphome { namespace sht4x { diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 698e3cda9ea6..faa6cefe279d 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -3,6 +3,7 @@ from esphome import automation from esphome.const import ( CONF_ID, + CONF_MESSAGE, CONF_TRIGGER_ID, ) from esphome.components import uart @@ -52,7 +53,6 @@ CONF_ON_CALL_CONNECTED = "on_call_connected" CONF_ON_CALL_DISCONNECTED = "on_call_disconnected" CONF_RECIPIENT = "recipient" -CONF_MESSAGE = "message" CONF_USSD = "ussd" CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/sm10bit_base/sm10bit_base.cpp b/esphome/components/sm10bit_base/sm10bit_base.cpp index 9c7abb48e2fe..d380f31c6fa9 100644 --- a/esphome/components/sm10bit_base/sm10bit_base.cpp +++ b/esphome/components/sm10bit_base/sm10bit_base.cpp @@ -11,6 +11,8 @@ static const uint8_t SM10BIT_ADDR_START_3CH = 0x8; static const uint8_t SM10BIT_ADDR_START_2CH = 0x10; static const uint8_t SM10BIT_ADDR_START_5CH = 0x18; +static const uint8_t SM10BIT_DELAY = 2; + // Power current values // HEX | Binary | RGB level | White level | Config value // 0x0 | 0000 | RGB 10mA | CW 5mA | 0 @@ -37,10 +39,13 @@ void Sm10BitBase::loop() { uint8_t data[12]; if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) { - // Off / Sleep - data[0] = this->model_id_ + SM10BIT_ADDR_STANDBY; for (int i = 1; i < 12; i++) data[i] = 0; + // First turn all channels off + data[0] = this->model_id_ + SM10BIT_ADDR_START_5CH; + this->write_buffer_(data, 12); + // Then sleep + data[0] = this->model_id_ + SM10BIT_ADDR_STANDBY; this->write_buffer_(data, 12); } else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && (this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) { @@ -84,28 +89,42 @@ void Sm10BitBase::set_channel_value_(uint8_t channel, uint16_t value) { this->pwm_amounts_[channel] = value; } void Sm10BitBase::write_bit_(bool value) { - this->clock_pin_->digital_write(false); this->data_pin_->digital_write(value); + delayMicroseconds(SM10BIT_DELAY); this->clock_pin_->digital_write(true); + delayMicroseconds(SM10BIT_DELAY); + this->clock_pin_->digital_write(false); + delayMicroseconds(SM10BIT_DELAY); } void Sm10BitBase::write_byte_(uint8_t data) { for (uint8_t mask = 0x80; mask; mask >>= 1) { this->write_bit_(data & mask); } - this->clock_pin_->digital_write(false); - this->data_pin_->digital_write(true); + + // ack bit + this->data_pin_->pin_mode(gpio::FLAG_INPUT); this->clock_pin_->digital_write(true); + delayMicroseconds(SM10BIT_DELAY); + this->clock_pin_->digital_write(false); + delayMicroseconds(SM10BIT_DELAY); + this->data_pin_->pin_mode(gpio::FLAG_OUTPUT); } void Sm10BitBase::write_buffer_(uint8_t *buffer, uint8_t size) { this->data_pin_->digital_write(false); + delayMicroseconds(SM10BIT_DELAY); + this->clock_pin_->digital_write(false); + delayMicroseconds(SM10BIT_DELAY); + for (uint32_t i = 0; i < size; i++) { this->write_byte_(buffer[i]); } - this->clock_pin_->digital_write(false); + this->clock_pin_->digital_write(true); + delayMicroseconds(SM10BIT_DELAY); this->data_pin_->digital_write(true); + delayMicroseconds(SM10BIT_DELAY); } } // namespace sm10bit_base diff --git a/esphome/components/sm2135/__init__.py b/esphome/components/sm2135/__init__.py index ce78d5337f9e..52128f1f24d7 100644 --- a/esphome/components/sm2135/__init__.py +++ b/esphome/components/sm2135/__init__.py @@ -15,6 +15,7 @@ CONF_RGB_CURRENT = "rgb_current" CONF_CW_CURRENT = "cw_current" +CONF_SEPARATE_MODES = "separate_modes" SM2135Current = sm2135_ns.enum("SM2135Current") @@ -51,6 +52,7 @@ cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RGB_CURRENT, "20mA"): cv.enum(DRIVE_STRENGTHS_RGB), cv.Optional(CONF_CW_CURRENT, "10mA"): cv.enum(DRIVE_STRENGTHS_CW), + cv.Optional(CONF_SEPARATE_MODES, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -66,3 +68,4 @@ async def to_code(config): cg.add(var.set_rgb_current(config[CONF_RGB_CURRENT])) cg.add(var.set_cw_current(config[CONF_CW_CURRENT])) + cg.add(var.set_separate_modes(config[CONF_SEPARATE_MODES])) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index f9cd4235ed99..ee5948bb3a08 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -97,23 +97,32 @@ void SM2135::loop() { this->write_byte_(SM2135_ADDR_MC); this->write_byte_(current_mask_); - if (this->update_channel_ == 3 || this->update_channel_ == 4) { - // No color so must be Cold/Warm - - this->write_byte_(SM2135_CW); - this->sm2135_stop_(); - delay(1); - this->sm2135_start_(); - this->write_byte_(SM2135_ADDR_C); - this->write_byte_(this->pwm_amounts_[4]); // Warm - this->write_byte_(this->pwm_amounts_[3]); // Cold - } else { - // Color + if (this->separate_modes_) { + if (this->update_channel_ == 3 || this->update_channel_ == 4) { + // No color so must be Cold/Warm + + this->write_byte_(SM2135_CW); + this->sm2135_stop_(); + delay(1); + this->sm2135_start_(); + this->write_byte_(SM2135_ADDR_C); + this->write_byte_(this->pwm_amounts_[3]); + this->write_byte_(this->pwm_amounts_[4]); + } else { + // Color + this->write_byte_(SM2135_RGB); + this->write_byte_(this->pwm_amounts_[0]); + this->write_byte_(this->pwm_amounts_[1]); + this->write_byte_(this->pwm_amounts_[2]); + } + } else { this->write_byte_(SM2135_RGB); - this->write_byte_(this->pwm_amounts_[1]); // Green - this->write_byte_(this->pwm_amounts_[0]); // Red - this->write_byte_(this->pwm_amounts_[2]); // Blue + this->write_byte_(this->pwm_amounts_[0]); + this->write_byte_(this->pwm_amounts_[1]); + this->write_byte_(this->pwm_amounts_[2]); + this->write_byte_(this->pwm_amounts_[3]); + this->write_byte_(this->pwm_amounts_[4]); } this->sm2135_stop_(); diff --git a/esphome/components/sm2135/sm2135.h b/esphome/components/sm2135/sm2135.h index a557fc3287f0..6f207d093aa5 100644 --- a/esphome/components/sm2135/sm2135.h +++ b/esphome/components/sm2135/sm2135.h @@ -39,6 +39,8 @@ class SM2135 : public Component { this->current_mask_ = (this->rgb_current_ << 4) | this->cw_current_; } + void set_separate_modes(bool separate_modes) { this->separate_modes_ = separate_modes; } + void setup() override; void dump_config() override; @@ -78,6 +80,7 @@ class SM2135 : public Component { uint8_t current_mask_; SM2135Current rgb_current_; SM2135Current cw_current_; + bool separate_modes_; uint8_t update_channel_; std::vector pwm_amounts_; bool update_{true}; diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index f3b6dd95ef6f..8bcfb69a4507 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -1,9 +1,10 @@ import re +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_TRIGGER_ID CODEOWNERS = ["@alengwenus"] @@ -16,10 +17,26 @@ 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") + +DataTrigger = sml_ns.class_( + "DataTrigger", + automation.Trigger.template( + cg.std_vector.template(cg.uint8).operator("ref"), cg.bool_ + ), +) + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Sml), + cv.Optional(CONF_ON_DATA): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger), + } + ), } ).extend(uart.UART_DEVICE_SCHEMA) @@ -28,6 +45,19 @@ 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_DATA, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, + [ + ( + cg.std_vector.template(cg.uint8).operator("ref").operator("const"), + "bytes", + ), + (cg.bool_, "valid"), + ], + conf, + ) def obis_code(value): diff --git a/esphome/components/sml/automation.h b/esphome/components/sml/automation.h new file mode 100644 index 000000000000..d51063065d11 --- /dev/null +++ b/esphome/components/sml/automation.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "sml.h" + +#include + +namespace esphome { +namespace sml { + +class DataTrigger : public Trigger &, bool> { + public: + explicit DataTrigger(Sml *sml) { + sml->add_on_data_callback([this](const std::vector &data, bool valid) { this->trigger(data, valid); }); + } +}; + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/constants.h b/esphome/components/sml/constants.h index 08a124ccad70..d6761d4bb7c2 100644 --- a/esphome/components/sml/constants.h +++ b/esphome/components/sml/constants.h @@ -18,8 +18,10 @@ enum SmlType : uint8_t { enum SmlMessageType : uint16_t { SML_PUBLIC_OPEN_RES = 0x0101, SML_GET_LIST_RES = 0x701 }; // masks with two-bit mapping 0x1b -> 0b01; 0x01 -> 0b10; 0x1a -> 0b11 -const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 1b 01 01 01 01 +const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 01 01 01 01 const uint16_t END_MASK = 0x0157; // 0x1b 1b 1b 1b 1a +const std::vector START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01}; + } // namespace sml } // namespace esphome diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp index 921623d4fdb3..bac13be9236e 100644 --- a/esphome/components/sml/sml.cpp +++ b/esphome/components/sml/sml.cpp @@ -35,16 +35,24 @@ void Sml::loop() { case START_BYTES_DETECTED: { this->record_ = true; this->sml_data_.clear(); + // add start sequence (for callbacks) + this->sml_data_.insert(this->sml_data_.begin(), START_SEQ.begin(), START_SEQ.end()); break; }; case END_BYTES_DETECTED: { if (this->record_) { this->record_ = false; - if (!check_sml_data(this->sml_data_)) + bool valid = check_sml_data(this->sml_data_); + + // call callbacks + this->data_callbacks_.call(this->sml_data_, valid); + + if (!valid) break; - // remove footer bytes + // remove start/end sequence + this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size()); this->sml_data_.resize(this->sml_data_.size() - 8); this->process_sml_file_(this->sml_data_); } @@ -54,6 +62,10 @@ void Sml::loop() { } } +void Sml::add_on_data_callback(std::function, bool)> &&callback) { + this->data_callbacks_.add(std::move(callback)); +} + void Sml::process_sml_file_(const bytes &sml_data) { SmlFile sml_file = SmlFile(sml_data); std::vector obis_info = sml_file.get_obis_info(); @@ -100,14 +112,14 @@ bool check_sml_data(const bytes &buffer) { } uint16_t crc_received = (buffer.at(buffer.size() - 2) << 8) | buffer.at(buffer.size() - 1); - uint16_t crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0x6e23, 0x8408, true, true); + uint16_t crc_calculated = crc16(buffer.data() + 8, buffer.size() - 10, 0x6e23, 0x8408, true, true); crc_calculated = (crc_calculated >> 8) | (crc_calculated << 8); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/X25."); return true; } - crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0xed50, 0x8408); + crc_calculated = crc16(buffer.data() + 8, buffer.size() - 10, 0xed50, 0x8408); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/KERMIT."); return true; diff --git a/esphome/components/sml/sml.h b/esphome/components/sml/sml.h index ebc8b17d7fb5..b0c932ca9568 100644 --- a/esphome/components/sml/sml.h +++ b/esphome/components/sml/sml.h @@ -3,6 +3,7 @@ #include #include #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #include "sml_parser.h" @@ -23,6 +24,7 @@ class Sml : public Component, public uart::UARTDevice { void loop() override; void dump_config() override; std::vector sml_listeners_{}; + void add_on_data_callback(std::function, bool)> &&callback); protected: void process_sml_file_(const bytes &sml_data); @@ -35,6 +37,8 @@ class Sml : public Component, public uart::UARTDevice { bool record_ = false; uint16_t incoming_mask_ = 0; bytes sml_data_; + + CallbackManager &, bool)> data_callbacks_{}; }; bool check_sml_data(const bytes &buffer); diff --git a/esphome/components/sml/sml_parser.cpp b/esphome/components/sml/sml_parser.cpp index 91b320a30e02..3b23522b21c3 100644 --- a/esphome/components/sml/sml_parser.cpp +++ b/esphome/components/sml/sml_parser.cpp @@ -88,11 +88,6 @@ uint64_t bytes_to_uint(const bytes &buffer) { for (auto const value : buffer) { val = (val << 8) + value; } - // Some smart meters send 24 bit signed integers. Sign extend to 64 bit if the - // 24 bit value is negative. - if (buffer.size() == 3 && buffer[0] & 0x80) { - val |= 0xFFFFFFFFFF000000; - } return val; } @@ -100,19 +95,15 @@ int64_t bytes_to_int(const bytes &buffer) { uint64_t tmp = bytes_to_uint(buffer); int64_t val; - switch (buffer.size()) { - case 1: // int8 - val = (int8_t) tmp; - break; - case 2: // int16 - val = (int16_t) tmp; - break; - case 4: // int32 - val = (int32_t) tmp; - break; - default: // int64 - val = (int64_t) tmp; + // sign extension for abbreviations of leading ones (e.g. 3 byte transmissions, see 6.2.2 of SML protocol definition) + // see https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c + if (buffer.size() < 8) { + const int bits = buffer.size() * 8; + const uint64_t m = 1u << (bits - 1); + tmp = (tmp ^ m) - m; } + + val = (int64_t) tmp; return val; } diff --git a/esphome/components/sn74hc165/__init__.py b/esphome/components/sn74hc165/__init__.py index 85d0220a880d..0f2abd3678e0 100644 --- a/esphome/components/sn74hc165/__init__.py +++ b/esphome/components/sn74hc165/__init__.py @@ -77,7 +77,15 @@ def _validate_input_mode(value): ) -@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC165, SN74HC165_PIN_SCHEMA) +def sn74hc165_pin_final_validate(pin_config, parent_config): + max_pins = parent_config[CONF_SR_COUNT] * 8 + if pin_config[CONF_NUMBER] >= max_pins: + raise cv.Invalid(f"Pin number must be less than {max_pins}") + + +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_SN74HC165, SN74HC165_PIN_SCHEMA, sn74hc165_pin_final_validate +) async def sn74hc165_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_parented(var, config[CONF_SN74HC165]) diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 92b6d8d0e520..2fd49f68247f 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -1,81 +1,117 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins +from esphome.components import spi from esphome.const import ( CONF_ID, - CONF_MODE, CONF_NUMBER, CONF_INVERTED, CONF_DATA_PIN, CONF_CLOCK_PIN, + CONF_OE_PIN, CONF_OUTPUT, + CONF_TYPE, ) -DEPENDENCIES = [] MULTI_CONF = True sn74hc595_ns = cg.esphome_ns.namespace("sn74hc595") SN74HC595Component = sn74hc595_ns.class_("SN74HC595Component", cg.Component) +SN74HC595GPIOComponent = sn74hc595_ns.class_( + "SN74HC595GPIOComponent", SN74HC595Component +) +SN74HC595SPIComponent = sn74hc595_ns.class_( + "SN74HC595SPIComponent", SN74HC595Component, spi.SPIDevice +) + SN74HC595GPIOPin = sn74hc595_ns.class_( "SN74HC595GPIOPin", cg.GPIOPin, cg.Parented.template(SN74HC595Component) ) CONF_SN74HC595 = "sn74hc595" CONF_LATCH_PIN = "latch_pin" -CONF_OE_PIN = "oe_pin" CONF_SR_COUNT = "sr_count" -CONFIG_SCHEMA = cv.Schema( + +TYPE_GPIO = "gpio" +TYPE_SPI = "spi" + +_COMMON_SCHEMA = cv.Schema( { - cv.Required(CONF_ID): cv.declare_id(SN74HC595Component), - cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(min=1, max=256), } -).extend(cv.COMPONENT_SCHEMA) +) + +CONFIG_SCHEMA = cv.typed_schema( + { + TYPE_GPIO: _COMMON_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(SN74HC595GPIOComponent), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + } + ).extend(cv.COMPONENT_SCHEMA), + TYPE_SPI: _COMMON_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(SN74HC595SPIComponent), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=False)), + }, + default_type=TYPE_GPIO, +) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - data_pin = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) - cg.add(var.set_data_pin(data_pin)) - clock_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) - cg.add(var.set_clock_pin(clock_pin)) + if config[CONF_TYPE] == TYPE_GPIO: + data_pin = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data_pin)) + clock_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock_pin)) + else: + await spi.register_spi_device(var, config) + latch_pin = await cg.gpio_pin_expression(config[CONF_LATCH_PIN]) cg.add(var.set_latch_pin(latch_pin)) - if CONF_OE_PIN in config: - oe_pin = await cg.gpio_pin_expression(config[CONF_OE_PIN]) + if oe_pin := config.get(CONF_OE_PIN): + oe_pin = await cg.gpio_pin_expression(oe_pin) cg.add(var.set_oe_pin(oe_pin)) cg.add(var.set_sr_count(config[CONF_SR_COUNT])) def _validate_output_mode(value): - if value is not True: + if value.get(CONF_OUTPUT) is not True: raise cv.Invalid("Only output mode is supported") return value -SN74HC595_PIN_SCHEMA = cv.All( +SN74HC595_PIN_SCHEMA = pins.gpio_base_schema( + SN74HC595GPIOPin, + cv.int_range(min=0, max=2047), + modes=[CONF_OUTPUT], + mode_validator=_validate_output_mode, + invertable=True, +).extend( { - cv.GenerateID(): cv.declare_id(SN74HC595GPIOPin), cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=2048, max_included=False), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_OUTPUT, default=True): cv.All( - cv.boolean, _validate_output_mode - ), - }, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, SN74HC595_PIN_SCHEMA) +def sn74hc595_pin_final_validate(pin_config, parent_config): + max_pins = parent_config[CONF_SR_COUNT] * 8 + if pin_config[CONF_NUMBER] >= max_pins: + raise cv.Invalid(f"Pin number must be less than {max_pins}") + + +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_SN74HC595, SN74HC595_PIN_SCHEMA, sn74hc595_pin_final_validate +) async def sn74hc595_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_parented(var, config[CONF_SN74HC595]) diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index 1895b1d5a6ef..8a37c3bece48 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -6,25 +6,38 @@ namespace sn74hc595 { static const char *const TAG = "sn74hc595"; -void SN74HC595Component::setup() { +void SN74HC595Component::pre_setup_() { ESP_LOGCONFIG(TAG, "Setting up SN74HC595..."); if (this->have_oe_pin_) { // disable output this->oe_pin_->setup(); this->oe_pin_->digital_write(true); } +} +void SN74HC595Component::post_setup_() { + this->latch_pin_->setup(); + this->latch_pin_->digital_write(false); + + // send state to shift register + this->write_gpio(); +} - // initialize output pins +void SN74HC595GPIOComponent::setup() { + this->pre_setup_(); this->clock_pin_->setup(); this->data_pin_->setup(); - this->latch_pin_->setup(); this->clock_pin_->digital_write(false); this->data_pin_->digital_write(false); - this->latch_pin_->digital_write(false); + this->post_setup_(); +} - // send state to shift register - this->write_gpio_(); +#ifdef USE_SPI +void SN74HC595SPIComponent::setup() { + this->pre_setup_(); + this->spi_setup(); + this->post_setup_(); } +#endif void SN74HC595Component::dump_config() { ESP_LOGCONFIG(TAG, "SN74HC595:"); } @@ -34,17 +47,38 @@ void SN74HC595Component::digital_write_(uint16_t pin, bool value) { (this->sr_count_ * 8) - 1); return; } - this->output_bits_[pin] = value; - this->write_gpio_(); + if (value) { + this->output_bytes_[pin / 8] |= (1 << (pin % 8)); + } else { + this->output_bytes_[pin / 8] &= ~(1 << (pin % 8)); + } + this->write_gpio(); +} + +void SN74HC595GPIOComponent::write_gpio() { + for (auto byte = this->output_bytes_.rbegin(); byte != this->output_bytes_.rend(); byte++) { + for (int8_t i = 7; i >= 0; i--) { + bool bit = (*byte >> i) & 1; + this->data_pin_->digital_write(bit); + this->clock_pin_->digital_write(true); + this->clock_pin_->digital_write(false); + } + } + SN74HC595Component::write_gpio(); } -void SN74HC595Component::write_gpio_() { - for (auto bit = this->output_bits_.rbegin(); bit != this->output_bits_.rend(); bit++) { - this->data_pin_->digital_write(*bit); - this->clock_pin_->digital_write(true); - this->clock_pin_->digital_write(false); +#ifdef USE_SPI +void SN74HC595SPIComponent::write_gpio() { + for (auto byte = this->output_bytes_.rbegin(); byte != this->output_bytes_.rend(); byte++) { + this->enable(); + this->transfer_byte(*byte); + this->disable(); } + SN74HC595Component::write_gpio(); +} +#endif +void SN74HC595Component::write_gpio() { // pulse latch to activate new values this->latch_pin_->digital_write(true); this->latch_pin_->digital_write(false); @@ -60,11 +94,7 @@ 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 { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via SN74HC595", pin_); - return buffer; -} +std::string SN74HC595GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC595", 18, pin_); } } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h index 64bf06d88193..cb9d7bf1402e 100644 --- a/esphome/components/sn74hc595/sn74hc595.h +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -1,9 +1,14 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#ifdef USE_SPI +#include "esphome/components/spi/spi.h" +#endif + #include namespace esphome { @@ -13,34 +18,33 @@ class SN74HC595Component : public Component { public: SN74HC595Component() = default; - void setup() override; + void setup() override = 0; float get_setup_priority() const override; void dump_config() override; - void set_data_pin(GPIOPin *pin) { data_pin_ = pin; } - void set_clock_pin(GPIOPin *pin) { clock_pin_ = pin; } - void set_latch_pin(GPIOPin *pin) { latch_pin_ = pin; } + void set_latch_pin(GPIOPin *pin) { this->latch_pin_ = pin; } void set_oe_pin(GPIOPin *pin) { - oe_pin_ = pin; - have_oe_pin_ = true; + this->oe_pin_ = pin; + this->have_oe_pin_ = true; } void set_sr_count(uint8_t count) { - sr_count_ = count; - this->output_bits_.resize(count * 8); + this->sr_count_ = count; + this->output_bytes_.resize(count); } protected: friend class SN74HC595GPIOPin; void digital_write_(uint16_t pin, bool value); - void write_gpio_(); + virtual void write_gpio(); + + void pre_setup_(); + void post_setup_(); - GPIOPin *data_pin_; - GPIOPin *clock_pin_; GPIOPin *latch_pin_; GPIOPin *oe_pin_; uint8_t sr_count_; bool have_oe_pin_{false}; - std::vector output_bits_; + std::vector output_bytes_; }; /// Helper class to expose a SC74HC595 pin as an internal output GPIO pin. @@ -60,5 +64,31 @@ class SN74HC595GPIOPin : public GPIOPin, public Parented { bool inverted_; }; +class SN74HC595GPIOComponent : public SN74HC595Component { + public: + void setup() override; + void set_data_pin(GPIOPin *pin) { data_pin_ = pin; } + void set_clock_pin(GPIOPin *pin) { clock_pin_ = pin; } + + protected: + void write_gpio() override; + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; +}; + +#ifdef USE_SPI +class SN74HC595SPIComponent : public SN74HC595Component, + public spi::SPIDevice { + public: + void setup() override; + + protected: + void write_gpio() override; +}; + +#endif + } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 418eacd870de..6a60e8d5c1fe 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -25,6 +25,7 @@ namespace sntp { static const char *const TAG = "sntp"; void SNTPComponent::setup() { +#ifndef USE_HOST ESP_LOGCONFIG(TAG, "Setting up SNTP..."); #if defined(USE_ESP32) || defined(USE_LIBRETINY) if (sntp_enabled()) { @@ -48,6 +49,7 @@ void SNTPComponent::setup() { #endif sntp_init(); +#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); @@ -57,7 +59,7 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } void SNTPComponent::update() { -#ifndef USE_ESP_IDF +#if !defined(USE_ESP_IDF) && !defined(USE_HOST) // force resync if (sntp_enabled()) { sntp_stop(); diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 5d44cd768933..f07f5c8f81c6 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -86,6 +86,13 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(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); +#else + return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#endif + } ssize_t readv(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) return ::lwip_readv(fd_, iov, iovcnt); diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 4c78397873c9..b200046d7fb3 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -10,15 +10,15 @@ namespace socket { Socket::~Socket() {} std::unique_ptr socket_ip(int type, int protocol) { -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 return socket(AF_INET6, type, protocol); #else return socket(AF_INET, type, protocol); -#endif +#endif /* USE_NETWORK_IPV6 */ } socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) { -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return 0; @@ -47,11 +47,11 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri server->sin_addr.s_addr = inet_addr(ip_address.c_str()); server->sin_port = htons(port); return sizeof(sockaddr_in); -#endif +#endif /* USE_NETWORK_IPV6 */ } socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return 0; @@ -60,7 +60,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po memset(server, 0, sizeof(sockaddr_in6)); server->sin6_family = AF_INET6; server->sin6_port = htons(port); - server->sin6_addr = in6addr_any; + server->sin6_addr = IN6ADDR_ANY_INIT; return sizeof(sockaddr_in6); #else if (addrlen < sizeof(sockaddr_in)) { @@ -73,7 +73,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po server->sin_addr.s_addr = ESPHOME_INADDR_ANY; server->sin_port = htons(port); return sizeof(sockaddr_in); -#endif +#endif /* USE_NETWORK_IPV6 */ } } // namespace socket } // namespace esphome diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index c9b8be88a09f..5c12210d15d0 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,6 +31,9 @@ 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; diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 3f520e3c5e96..b494873160a0 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -18,6 +18,8 @@ class Speaker { virtual void start() = 0; virtual void stop() = 0; + virtual bool has_buffered_data() const = 0; + bool is_running() const { return this->state_ == STATE_RUNNING; } protected: diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index 978e68d1e91c..5fbf01166952 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -1,14 +1,17 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import fan, output +from esphome.components.fan import validate_preset_modes from esphome.const import ( + CONF_PRESET_MODES, + CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, - CONF_DIRECTION_OUTPUT, CONF_OUTPUT_ID, CONF_SPEED, CONF_SPEED_COUNT, ) + from .. import speed_ns SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan) @@ -23,16 +26,19 @@ "Configuring individual speeds is deprecated." ), cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, } ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - output_ = await cg.get_variable(config[CONF_OUTPUT]) - var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT]) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT]) await cg.register_component(var, config) await fan.register_fan(var, config) + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) + if CONF_OSCILLATION_OUTPUT in config: oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) cg.add(var.set_oscillating(oscillation_output)) @@ -40,3 +46,6 @@ async def to_code(config): if CONF_DIRECTION_OUTPUT in config: direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT]) cg.add(var.set_direction(direction_output)) + + if CONF_PRESET_MODES in config: + cg.add(var.set_preset_modes(config[CONF_PRESET_MODES])) diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 3a65f2c36545..57bd79541693 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -12,11 +12,14 @@ void SpeedFan::setup() { restore->apply(*this); this->write_state_(); } + + // Construct traits + this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); } + void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); } -fan::FanTraits SpeedFan::get_traits() { - return fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); -} + void SpeedFan::control(const fan::FanCall &call) { if (call.get_state().has_value()) this->state = *call.get_state(); @@ -26,14 +29,15 @@ 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->write_state_(); this->publish_state(); } + void SpeedFan::write_state_() { float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; this->output_->set_level(speed); - if (this->oscillating_ != nullptr) this->oscillating_->set_state(this->oscillating); if (this->direction_ != nullptr) diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 1fad53813a7f..6537bce3f6d3 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" @@ -10,12 +12,14 @@ namespace speed { class SpeedFan : public Component, public fan::Fan { public: - SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {} + SpeedFan(int speed_count) : speed_count_(speed_count) {} void setup() override; void dump_config() override; + 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; } - fan::FanTraits get_traits() override; + void set_preset_modes(const std::set &presets) { this->preset_modes_ = presets; } + fan::FanTraits get_traits() override { return this->traits_; } protected: void control(const fan::FanCall &call) override; @@ -25,6 +29,8 @@ class SpeedFan : public Component, public fan::Fan { output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; int speed_count_{}; + fan::FanTraits traits_; + std::set preset_modes_{}; }; } // namespace speed diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index a2ef956200cf..fdf19bb56e4c 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,6 +1,17 @@ +import re + import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv +from esphome.components.esp32.const import ( + KEY_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, +) from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -9,14 +20,29 @@ CONF_MOSI_PIN, CONF_SPI_ID, CONF_CS_PIN, + CONF_NUMBER, + CONF_INVERTED, + KEY_CORE, + KEY_TARGET_PLATFORM, + KEY_VARIANT, + CONF_DATA_RATE, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + CONF_DATA_PINS, +) +from esphome.core import ( + coroutine_with_priority, + CORE, ) -from esphome.core import coroutine_with_priority, CORE -CODEOWNERS = ["@esphome/core"] +CODEOWNERS = ["@esphome/core", "@clydebarrow"] spi_ns = cg.esphome_ns.namespace("spi") SPIComponent = spi_ns.class_("SPIComponent", cg.Component) +QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component) SPIDevice = spi_ns.class_("SPIDevice") SPIDataRate = spi_ns.enum("SPIDataRate") +SPIMode = spi_ns.enum("SPIMode") SPI_DATA_RATE_OPTIONS = { 80e6: SPIDataRate.DATA_RATE_80MHZ, @@ -34,51 +60,310 @@ } SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) -MULTI_CONF = True +SPI_MODE_OPTIONS = { + "MODE0": SPIMode.MODE0, + "MODE1": SPIMode.MODE1, + "MODE2": SPIMode.MODE2, + "MODE3": SPIMode.MODE3, + 0: SPIMode.MODE0, + 1: SPIMode.MODE1, + 2: SPIMode.MODE2, + 3: SPIMode.MODE3, +} + +CONF_SPI_MODE = "spi_mode" CONF_FORCE_SW = "force_sw" +CONF_INTERFACE = "interface" +CONF_INTERFACE_INDEX = "interface_index" +TYPE_SINGLE = "single" +TYPE_QUAD = "quad" -CONFIG_SCHEMA = cv.All( +# RP2040 SPI pin assignments are complicated; +# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf + +RP_SPI_PINSETS = [ + { + CONF_MISO_PIN: [0, 4, 16, 20, -1], + CONF_CLK_PIN: [2, 6, 18, 22], + CONF_MOSI_PIN: [3, 7, 19, 23, -1], + }, + { + CONF_MISO_PIN: [8, 12, 24, 28, -1], + CONF_CLK_PIN: [10, 14, 26], + CONF_MOSI_PIN: [11, 15, 27, -1], + }, +] + + +def get_target_platform(): + return ( + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] + if KEY_TARGET_PLATFORM in CORE.data[KEY_CORE] + else "" + ) + + +def get_target_variant(): + return ( + CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else "" + ) + + +# Get a list of available hardware interfaces based on target and variant. +# The returned value is a list of lists of names +def get_hw_interface_list(): + target_platform = get_target_platform() + if target_platform == PLATFORM_ESP8266: + return [["spi", "hspi"]] + if target_platform == PLATFORM_ESP32: + if get_target_variant() in [ + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, + ]: + return [["spi", "spi2"]] + return [["spi", "spi2"], ["spi3"]] + if target_platform == PLATFORM_RP2040: + return [["spi"], ["spi1"]] + return [] + + +# Given an SPI name, return the index of it in the available list +def get_spi_index(name): + for i, ilist in enumerate(get_hw_interface_list()): + if name in ilist: + return i + # Should never get to here. + raise cv.Invalid(f"{name} is not an available SPI") + + +# Check that pins are suitable for HW spi +# \param spi the config data for the spi instance +# \param index the selected hw interface number, -1 if not yet known +# TODO verify that the pins are internal +def validate_hw_pins(spi, index=-1): + clk_pin = spi[CONF_CLK_PIN] + if clk_pin[CONF_INVERTED]: + return False + clk_pin_no = clk_pin[CONF_NUMBER] + sdo_pin_no = -1 + sdi_pin_no = -1 + if CONF_MOSI_PIN in spi: + sdo_pin = spi[CONF_MOSI_PIN] + if sdo_pin[CONF_INVERTED]: + return False + sdo_pin_no = sdo_pin[CONF_NUMBER] + if CONF_MISO_PIN in spi: + sdi_pin = spi[CONF_MISO_PIN] + if sdi_pin[CONF_INVERTED]: + return False + sdi_pin_no = sdi_pin[CONF_NUMBER] + + target_platform = get_target_platform() + if target_platform == PLATFORM_ESP8266: + if clk_pin_no == 6: + return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7) + if clk_pin_no == 14: + return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12) + return False + + if target_platform == PLATFORM_ESP32: + return clk_pin_no >= 0 + + if target_platform == PLATFORM_RP2040: + pin_set = ( + list(filter(lambda s: clk_pin_no in s[CONF_CLK_PIN], RP_SPI_PINSETS))[0] + if index == -1 + else RP_SPI_PINSETS[index] + ) + if pin_set is None: + return False + if sdo_pin_no not in pin_set[CONF_MOSI_PIN]: + return False + if sdi_pin_no not in pin_set[CONF_MISO_PIN]: + return False + return True + return False + + +def get_hw_spi(config, available): + """Get an available hardware spi interface suitable for this config""" + matching = list(filter(lambda idx: validate_hw_pins(config, idx), available)) + if len(matching) != 0: + return matching[0] + return None + + +def validate_spi_config(config): + available = list(range(len(get_hw_interface_list()))) + for spi in config: + interface = spi[CONF_INTERFACE] + if interface == "software": + pass + elif interface == "any": + if not validate_hw_pins(spi): + spi[CONF_INTERFACE] = "software" + elif interface == "hardware": + index = get_hw_spi(spi, available) + if index is None: + raise cv.Invalid("No suitable hardware interface available") + spi[CONF_INTERFACE_INDEX] = index + available.remove(index) + else: + # Must be a specific name + index = spi[CONF_INTERFACE_INDEX] = get_spi_index(interface) + if index not in available: + raise cv.Invalid( + f"interface '{interface}' not available here (may be already assigned)" + ) + available.remove(index) + + # Second time around: + # Any specific names and any 'hardware' requests will have already been filled, + # so just need to assign remaining hardware to 'any' requests. + for spi in config: + if spi[CONF_INTERFACE] == "any": + index = get_hw_spi(spi, available) + if index is not None: + spi[CONF_INTERFACE_INDEX] = index + available.remove(index) + if CONF_INTERFACE_INDEX in spi and not validate_hw_pins( + spi, spi[CONF_INTERFACE_INDEX] + ): + raise cv.Invalid("Invalid pin selections for hardware SPI interface") + if CONF_DATA_PINS in spi and CONF_INTERFACE_INDEX not in spi: + raise cv.Invalid("Quad mode requires a hardware interface") + + return 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: + return ["SPI2_HOST", "SPI3_HOST"][index] + # Arduino code follows + platform = get_target_platform() + if platform == PLATFORM_RP2040: + return ["&SPI", "&SPI1"][index] + if index == 0: + return "&SPI" + # Following code can't apply to C2, H2 or 8266 since they have only one SPI + if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2): + return "new SPIClass(FSPI)" + return "new SPIClass(HSPI)" + + +SPI_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(SPIComponent), cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_FORCE_SW, default=False): cv.boolean, + cv.Optional(CONF_FORCE_SW): cv.invalid( + "force_sw is deprecated - use interface: software" + ), + cv.Optional(CONF_INTERFACE, default="any"): cv.one_of( + *sum(get_hw_interface_list(), ["software", "hardware", "any"]), + lower=True, + ), + cv.Optional(CONF_DATA_PINS): cv.invalid( + "'data_pins' should be used with 'type: quad' only" + ), } ), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), - cv.only_on(["esp32", "esp8266", "rp2040"]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), +) + +SPI_QUAD_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QuadSPIComponent), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DATA_PINS): cv.All( + cv.ensure_list(pins.internal_gpio_output_pin_number), + cv.Length(min=4, max=4), + ), + cv.Optional(CONF_INTERFACE, default="hardware"): cv.one_of( + *sum(get_hw_interface_list(), ["hardware"]), + lower=True, + ), + cv.Optional(CONF_MISO_PIN): cv.invalid( + "'miso_pin' should not be used with quad SPI" + ), + cv.Optional(CONF_MOSI_PIN): cv.invalid( + "'mosi_pin' should not be used with quad SPI" + ), + } + ), + cv.only_on([PLATFORM_ESP32]), + cv.only_with_esp_idf, +) + +CONFIG_SCHEMA = cv.All( + cv.ensure_list( + cv.typed_schema( + { + TYPE_SINGLE: SPI_SCHEMA, + TYPE_QUAD: SPI_QUAD_SCHEMA, + }, + default_type=TYPE_SINGLE, + ) + ), + validate_spi_config, ) @coroutine_with_priority(1.0) -async def to_code(config): +async def to_code(configs): + cg.add_define("USE_SPI") cg.add_global(spi_ns.using) - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - - clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) - cg.add(var.set_clk(clk)) - cg.add(var.set_force_sw(config[CONF_FORCE_SW])) - if CONF_MISO_PIN in config: - miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN]) - cg.add(var.set_miso(miso)) - if CONF_MOSI_PIN in config: - mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) - cg.add(var.set_mosi(mosi)) - if CORE.using_arduino: cg.add_library("SPI", None) + for spi in configs: + var = cg.new_Pvariable(spi[CONF_ID]) + await cg.register_component(var, spi) + clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN]) + cg.add(var.set_clk(clk)) + if miso := spi.get(CONF_MISO_PIN): + cg.add(var.set_miso(await cg.gpio_pin_expression(miso))) + if mosi := spi.get(CONF_MOSI_PIN): + cg.add(var.set_mosi(await cg.gpio_pin_expression(mosi))) + if data_pins := spi.get(CONF_DATA_PINS): + cg.add(var.set_data_pins(data_pins)) + if (index := spi.get(CONF_INTERFACE_INDEX)) is not None: + interface = get_spi_interface(index) + cg.add(var.set_interface(cg.RawExpression(interface))) + cg.add( + var.set_interface_name( + re.sub(r"\W", "", interface.replace("new SPIClass", "")) + ) + ) -def spi_device_schema(cs_pin_required=True): +def spi_device_schema( + cs_pin_required=True, + default_data_rate=cv.UNDEFINED, + default_mode=cv.UNDEFINED, + quad=False, +): """Create a schema for an SPI device. :param cs_pin_required: If true, make the CS_PIN required in the config. + :param default_data_rate: Optional data_rate to use as default + :param default_mode Optional. The default SPI mode to use. + :param quad If set, will require an SPI component configured as quad data bits. :return: The SPI device schema, `extend` this in your config schema. """ schema = { - cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent), + cv.GenerateID(CONF_SPI_ID): cv.use_id( + QuadSPIComponent if quad else SPIComponent + ), + 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 + ), } if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema @@ -93,6 +378,10 @@ async def register_spi_device(var, config): if CONF_CS_PIN in config: pin = await cg.gpio_pin_expression(config[CONF_CS_PIN]) cg.add(var.set_cs_pin(pin)) + if CONF_DATA_RATE in config: + cg.add(var.set_data_rate(config[CONF_DATA_RATE])) + if CONF_SPI_MODE in config: + cg.add(var.set_mode(config[CONF_SPI_MODE])) def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 33630897f61a..b13826c4430b 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -1,268 +1,124 @@ #include "spi.h" #include "esphome/core/log.h" -#include "esphome/core/helpers.h" #include "esphome/core/application.h" namespace esphome { namespace spi { -static const char *const TAG = "spi"; +const char *const TAG = "spi"; -void IRAM_ATTR HOT SPIComponent::disable() { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - this->hw_spi_->endTransaction(); - } -#endif // USE_SPI_ARDUINO_BACKEND - if (this->active_cs_) { - this->active_cs_->digital_write(true); - this->active_cs_ = nullptr; - } -} -void SPIComponent::setup() { - ESP_LOGCONFIG(TAG, "Setting up SPI bus..."); - this->clk_->setup(); - this->clk_->digital_write(true); - -#ifdef USE_SPI_ARDUINO_BACKEND - bool use_hw_spi = !this->force_sw_; - const bool has_miso = this->miso_ != nullptr; - const bool has_mosi = this->mosi_ != nullptr; - int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1; +SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + new SPIDelegateDummy(); +// https://bugs.llvm.org/show_bug.cgi?id=48040 - if (!this->clk_->is_internal()) - use_hw_spi = false; - if (has_miso && !miso_->is_internal()) - use_hw_spi = false; - if (has_mosi && !mosi_->is_internal()) - use_hw_spi = false; - if (use_hw_spi) { - auto *clk_internal = (InternalGPIOPin *) clk_; - auto *miso_internal = (InternalGPIOPin *) miso_; - auto *mosi_internal = (InternalGPIOPin *) mosi_; +bool SPIDelegate::is_ready() { return true; } - if (clk_internal->is_inverted()) - use_hw_spi = false; - if (has_miso && miso_internal->is_inverted()) - use_hw_spi = false; - if (has_mosi && mosi_internal->is_inverted()) - use_hw_spi = false; +GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - if (use_hw_spi) { - clk_pin = clk_internal->get_pin(); - miso_pin = has_miso ? miso_internal->get_pin() : -1; - mosi_pin = has_mosi ? mosi_internal->get_pin() : -1; - } - } -#ifdef USE_ESP8266 - if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && - !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) - use_hw_spi = false; - - if (use_hw_spi) { - this->hw_spi_ = &SPI; - this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0); - this->hw_spi_->begin(); - return; - } -#endif // USE_ESP8266 -#ifdef USE_ESP32 - static uint8_t spi_bus_num = 0; - if (spi_bus_num >= 2) { - use_hw_spi = false; +SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, + GPIOPin *cs_pin) { + if (this->devices_.count(device) != 0) { + ESP_LOGE(TAG, "SPI device already registered"); + return this->devices_[device]; } + SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin); // NOLINT + this->devices_[device] = delegate; + return delegate; +} - if (use_hw_spi) { - if (spi_bus_num == 0) { - this->hw_spi_ = &SPI; - } else { -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6) - this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory) -#else - this->hw_spi_ = new SPIClass(HSPI); // NOLINT(cppcoreguidelines-owning-memory) -#endif // USE_ESP32_VARIANT - } - spi_bus_num++; - this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); +void SPIComponent::unregister_device(SPIClient *device) { + if (this->devices_.count(device) == 0) { + esph_log_e(TAG, "SPI device not registered"); return; } -#endif // USE_ESP32 -#ifdef USE_RP2040 - static uint8_t spi_bus_num = 0; - if (spi_bus_num >= 2) { - use_hw_spi = false; - } - if (use_hw_spi) { - SPIClassRP2040 *spi; - if (spi_bus_num == 0) { - spi = &SPI; - } else { - spi = &SPI1; - } - spi_bus_num++; + delete this->devices_[device]; // NOLINT + this->devices_.erase(device); +} - if (miso_pin != -1) - spi->setRX(miso_pin); - if (mosi_pin != -1) - spi->setTX(mosi_pin); - spi->setSCK(clk_pin); - this->hw_spi_ = spi; - this->hw_spi_->begin(); +void SPIComponent::setup() { + ESP_LOGD(TAG, "Setting up SPI bus..."); + + if (this->sdo_pin_ == nullptr) + this->sdo_pin_ = NullPin::NULL_PIN; + if (this->sdi_pin_ == nullptr) + this->sdi_pin_ = NullPin::NULL_PIN; + if (this->clk_pin_ == nullptr) { + ESP_LOGE(TAG, "No clock pin for SPI"); + this->mark_failed(); return; } -#endif // USE_RP2040 -#endif // USE_SPI_ARDUINO_BACKEND - if (this->miso_ != nullptr) { - this->miso_->setup(); - } - if (this->mosi_ != nullptr) { - this->mosi_->setup(); - this->mosi_->digital_write(false); + if (this->using_hw_) { + this->spi_bus_ = + SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_, this->data_pins_); + if (this->spi_bus_ == nullptr) { + ESP_LOGE(TAG, "Unable to allocate SPI interface"); + this->mark_failed(); + } + } else { + this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_); // NOLINT + this->clk_pin_->setup(); + this->clk_pin_->digital_write(true); + this->sdo_pin_->setup(); + this->sdi_pin_->setup(); } } + void SPIComponent::dump_config() { ESP_LOGCONFIG(TAG, "SPI bus:"); - LOG_PIN(" CLK Pin: ", this->clk_); - LOG_PIN(" MISO Pin: ", this->miso_); - LOG_PIN(" MOSI Pin: ", this->mosi_); -#ifdef USE_SPI_ARDUINO_BACKEND - ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); -#endif // USE_SPI_ARDUINO_BACKEND + 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]); + } + if (this->spi_bus_->is_hw()) { + ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_); + } else { + ESP_LOGCONFIG(TAG, " Using software SPI"); + } } -float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } -void SPIComponent::cycle_clock_(bool value) { - uint32_t start = arch_get_cpu_cycle_count(); - while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) - ; - this->clk_->digital_write(value); - start += this->wait_cycle_; - while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) - ; -} +void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); } + +uint8_t SPIDelegateBitBash::transfer(uint8_t data) { return this->transfer_(data, 8); } -// NOLINTNEXTLINE -#ifndef CLANG_TIDY -#pragma GCC optimize("unroll-loops") -// NOLINTNEXTLINE -#pragma GCC optimize("O2") -#endif // CLANG_TIDY +void SPIDelegateBitBash::write(uint16_t data, size_t num_bits) { this->transfer_(data, num_bits); } -template -uint8_t HOT SPIComponent::transfer_(uint8_t data) { +uint16_t SPIDelegateBitBash::transfer_(uint16_t data, size_t num_bits) { // Clock starts out at idle level - this->clk_->digital_write(CLOCK_POLARITY); + this->clk_pin_->digital_write(clock_polarity_); uint8_t out_data = 0; - for (uint8_t i = 0; i < 8; i++) { + for (uint8_t i = 0; i != num_bits; i++) { uint8_t shift; - if (BIT_ORDER == BIT_ORDER_MSB_FIRST) { - shift = 7 - i; + if (bit_order_ == BIT_ORDER_MSB_FIRST) { + shift = num_bits - 1 - i; } else { shift = i; } - if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { + if (clock_phase_ == CLOCK_PHASE_LEADING) { // sampling on leading edge - if (WRITE) { - this->mosi_->digital_write(data & (1 << shift)); - } - - // SAMPLE! - this->cycle_clock_(!CLOCK_POLARITY); - - if (READ) { - out_data |= uint8_t(this->miso_->digital_read()) << shift; - } - - this->cycle_clock_(CLOCK_POLARITY); + this->sdo_pin_->digital_write(data & (1 << shift)); + this->cycle_clock_(); + out_data |= uint16_t(this->sdi_pin_->digital_read()) << shift; + this->clk_pin_->digital_write(!this->clock_polarity_); + this->cycle_clock_(); + this->clk_pin_->digital_write(this->clock_polarity_); } else { // sampling on trailing edge - this->cycle_clock_(!CLOCK_POLARITY); - - if (WRITE) { - this->mosi_->digital_write(data & (1 << shift)); - } - - // SAMPLE! - this->cycle_clock_(CLOCK_POLARITY); - - if (READ) { - out_data |= uint8_t(this->miso_->digital_read()) << shift; - } + this->cycle_clock_(); + this->clk_pin_->digital_write(!this->clock_polarity_); + this->sdo_pin_->digital_write(data & (1 << shift)); + this->cycle_clock_(); + out_data |= uint16_t(this->sdi_pin_->digital_read()) << shift; + this->clk_pin_->digital_write(this->clock_polarity_); } } - App.feed_wdt(); - return out_data; } -// Generate with (py3): -// -// from itertools import product -// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST'] -// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH'] -// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING'] -// reads = [False, True] -// writes = [False, True] -// cpp_bool = {False: 'false', True: 'true'} -// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes): -// if not r and not w: -// continue -// print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t -// data);") - -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); - } // namespace spi } // namespace esphome diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 159d1175334a..f581dc3f569f 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,17 +1,36 @@ #pragma once +#include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include +#include #include #ifdef USE_ARDUINO -#define USE_SPI_ARDUINO_BACKEND -#endif -#ifdef USE_SPI_ARDUINO_BACKEND #include + +#ifdef USE_RP2040 +using SPIInterface = SPIClassRP2040 *; +#else +using SPIInterface = SPIClass *; +#endif + #endif +#ifdef USE_ESP_IDF + +#include "driver/spi_master.h" + +using SPIInterface = spi_host_device_t; + +#endif // USE_ESP_IDF + +/** + * Implementation of SPI Controller mode. + */ namespace esphome { namespace spi { @@ -48,10 +67,19 @@ enum SPIClockPhase { /// The data is sampled on a trailing clock edge. (CPHA=1) CLOCK_PHASE_TRAILING, }; -/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW. - * So effectively the rate of bytes can be calculated using + +/** + * Modes mapping to clock phase and polarity. * - * effective_byte_rate = spi_data_rate / 16 + */ + +enum SPIMode { + MODE0 = 0, + MODE1 = 1, + MODE2 = 2, + MODE3 = 3, +}; +/** The SPI clock signal frequency, which determines the transfer bit rate/second. * * Implementations can use the pre-defined constants here, or use an integer in the template definition * to manually use a specific data rate. @@ -71,270 +99,395 @@ enum SPIDataRate : uint32_t { DATA_RATE_80MHZ = 80000000, }; -class SPIComponent : public Component { +/** + * A pin to replace those that don't exist. + */ +class NullPin : public GPIOPin { + friend class SPIComponent; + + friend class SPIDelegate; + + friend class Utility; + public: - void set_clk(GPIOPin *clk) { clk_ = clk; } - void set_miso(GPIOPin *miso) { miso_ = miso; } - void set_mosi(GPIOPin *mosi) { mosi_ = mosi; } - void set_force_sw(bool force_sw) { force_sw_ = force_sw; } + void setup() override {} - void setup() override; + void pin_mode(gpio::Flags flags) override {} - void dump_config() override; + bool digital_read() override { return false; } - template uint8_t read_byte() { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - return this->hw_spi_->transfer(0x00); - } -#endif // USE_SPI_ARDUINO_BACKEND - return this->transfer_(0x00); - } + void digital_write(bool value) override {} - template - void read_array(uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - this->hw_spi_->transfer(data, length); - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - data[i] = this->read_byte(); - } + std::string dump_summary() const override { return std::string(); } + + protected: + static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + // https://bugs.llvm.org/show_bug.cgi?id=48040 +}; + +class Utility { + public: + static int get_pin_no(GPIOPin *pin) { + if (pin == nullptr || !pin->is_internal()) + return -1; + if (((InternalGPIOPin *) pin)->is_inverted()) + return -1; + return ((InternalGPIOPin *) pin)->get_pin(); } - template - void write_byte(uint8_t data) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { -#ifdef USE_RP2040 - this->hw_spi_->transfer(data); -#else - this->hw_spi_->write(data); -#endif - return; + static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) { + if (polarity == CLOCK_POLARITY_HIGH) { + return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3; } -#endif // USE_SPI_ARDUINO_BACKEND - this->transfer_(data); + return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1; } - template - void write_byte16(const uint16_t data) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { -#ifdef USE_RP2040 - this->hw_spi_->transfer16(data); -#else - this->hw_spi_->write16(data); -#endif - return; + static SPIClockPhase get_phase(SPIMode mode) { + switch (mode) { + case MODE0: + case MODE2: + return CLOCK_PHASE_LEADING; + default: + return CLOCK_PHASE_TRAILING; } -#endif // USE_SPI_ARDUINO_BACKEND - - this->write_byte(data >> 8); - this->write_byte(data); } - template - void write_array16(const uint16_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - for (size_t i = 0; i < length; i++) { -#ifdef USE_RP2040 - this->hw_spi_->transfer16(data[i]); -#else - this->hw_spi_->write16(data[i]); -#endif - } - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - this->write_byte16(data[i]); + static SPIClockPolarity get_polarity(SPIMode mode) { + switch (mode) { + case MODE0: + case MODE1: + return CLOCK_POLARITY_LOW; + default: + return CLOCK_POLARITY_HIGH; } } +}; - template - void write_array(const uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - auto *data_c = const_cast(data); -#ifdef USE_RP2040 - this->hw_spi_->transfer(data_c, length); -#else - this->hw_spi_->writeBytes(data_c, length); -#endif - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - this->write_byte(data[i]); - } +class SPIDelegateDummy; + +// represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is +// a thin wrapper over SPIClass. +class SPIDelegate { + friend class SPIClient; + + public: + SPIDelegate() = default; + + SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) + : bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) { + if (this->cs_pin_ == nullptr) + this->cs_pin_ = NullPin::NULL_PIN; + this->cs_pin_->setup(); + this->cs_pin_->digital_write(true); } - template - uint8_t transfer_byte(uint8_t data) { - if (this->miso_ != nullptr) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - return this->hw_spi_->transfer(data); - } else { -#endif // USE_SPI_ARDUINO_BACKEND - return this->transfer_(data); -#ifdef USE_SPI_ARDUINO_BACKEND - } -#endif // USE_SPI_ARDUINO_BACKEND - } - this->write_byte(data); - return 0; + virtual ~SPIDelegate(){}; + + // enable CS if configured. + virtual void begin_transaction() { this->cs_pin_->digital_write(false); } + + // end the transaction + virtual void end_transaction() { this->cs_pin_->digital_write(true); } + + // transfer one byte, return the byte that was read. + virtual uint8_t transfer(uint8_t data) = 0; + + // transfer a buffer, replace the contents with read data + virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); } + + virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) { + for (size_t i = 0; i != length; i++) + rxbuf[i] = this->transfer(txbuf[i]); } - template - void transfer_array(uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - if (this->miso_ != nullptr) { - this->hw_spi_->transfer(data, length); - } else { -#ifdef USE_RP2040 - this->hw_spi_->transfer(data, length); -#else - this->hw_spi_->writeBytes(data, length); -#endif - } - return; - } -#endif // USE_SPI_ARDUINO_BACKEND + /** + * write a variable length data item, up to 16 bits. + * @param data The data to send. Should be LSB-aligned (i.e. top bits will be discarded.) + * @param num_bits The number of bits to send + */ + virtual void write(uint16_t data, size_t num_bits) { + esph_log_e("spi_device", "variable length write not implemented"); + } - if (this->miso_ != nullptr) { - for (size_t i = 0; i < length; i++) { - data[i] = this->transfer_byte(data[i]); - } + virtual void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, + const uint8_t *data, size_t length, uint8_t bus_width) { + esph_log_e("spi_device", "write_cmd_addr_data not implemented"); + } + // write 16 bits + virtual void write16(uint16_t data) { + if (this->bit_order_ == BIT_ORDER_MSB_FIRST) { + uint16_t buffer; + buffer = (data >> 8) | (data << 8); + this->write_array(reinterpret_cast(&buffer), 2); } else { - this->write_array(data, length); + this->write_array(reinterpret_cast(&data), 2); } } - template - void enable(GPIOPin *cs) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - uint8_t data_mode = SPI_MODE0; - if (!CLOCK_POLARITY && CLOCK_PHASE) { - data_mode = SPI_MODE1; - } else if (CLOCK_POLARITY && !CLOCK_PHASE) { - data_mode = SPI_MODE2; - } else if (CLOCK_POLARITY && CLOCK_PHASE) { - data_mode = SPI_MODE3; - } -#ifdef USE_RP2040 - SPISettings settings(DATA_RATE, static_cast(BIT_ORDER), data_mode); -#else - SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); -#endif - this->hw_spi_->beginTransaction(settings); - } else { -#endif // USE_SPI_ARDUINO_BACKEND - this->clk_->digital_write(CLOCK_POLARITY); - uint32_t cpu_freq_hz = arch_get_cpu_freq_hz(); - this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL; -#ifdef USE_SPI_ARDUINO_BACKEND + virtual void write_array16(const uint16_t *data, size_t length) { + for (size_t i = 0; i != length; i++) { + this->write16(data[i]); } -#endif // USE_SPI_ARDUINO_BACKEND + } - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } + // write the contents of a buffer, ignore read data (buffer is unchanged.) + virtual void write_array(const uint8_t *ptr, size_t length) { + for (size_t i = 0; i != length; i++) + this->transfer(ptr[i]); } - void disable(); + // read into a buffer, write nulls + virtual void read_array(uint8_t *ptr, size_t length) { + for (size_t i = 0; i != length; i++) + ptr[i] = this->transfer(0); + } - float get_setup_priority() const override; + // check if device is ready + virtual bool is_ready(); protected: - inline void cycle_clock_(bool value); - - template - uint8_t transfer_(uint8_t data); - - GPIOPin *clk_; - GPIOPin *miso_{nullptr}; - GPIOPin *mosi_{nullptr}; - GPIOPin *active_cs_{nullptr}; - bool force_sw_{false}; -#ifdef USE_SPI_ARDUINO_BACKEND - SPIClass *hw_spi_{nullptr}; -#endif // USE_SPI_ARDUINO_BACKEND - uint32_t wait_cycle_; + SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; + uint32_t data_rate_{1000000}; + SPIMode mode_{MODE0}; + GPIOPin *cs_pin_{NullPin::NULL_PIN}; + static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; -template -class SPIDevice { +/** + * A dummy SPIDelegate that complains if it's used. + */ + +class SPIDelegateDummy : public SPIDelegate { public: - SPIDevice() = default; - SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} + SPIDelegateDummy() = default; - void set_spi_parent(SPIComponent *parent) { parent_ = parent; } - void set_cs_pin(GPIOPin *cs) { cs_ = cs; } + uint8_t transfer(uint8_t data) override { return 0; } + void end_transaction() override{}; - void spi_setup() { - if (this->cs_) { - this->cs_->setup(); - this->cs_->digital_write(true); - } + void begin_transaction() override; +}; + +/** + * An implementation of SPI that relies only on software toggling of pins. + * + */ +class SPIDelegateBitBash : public SPIDelegate { + public: + SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin, + GPIOPin *sdo_pin, GPIOPin *sdi_pin) + : SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) { + // this calculation is pretty meaningless except at very low bit rates. + this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL; + this->clock_polarity_ = Utility::get_polarity(this->mode_); + this->clock_phase_ = Utility::get_phase(this->mode_); } - void enable() { this->parent_->template enable(this->cs_); } + uint8_t transfer(uint8_t data) override; - void disable() { this->parent_->disable(); } + void write(uint16_t data, size_t num_bits) override; - uint8_t read_byte() { return this->parent_->template read_byte(); } + void write16(uint16_t data) override { this->write(data, 16); }; - void read_array(uint8_t *data, size_t length) { - return this->parent_->template read_array(data, length); - } + protected: + GPIOPin *clk_pin_; + GPIOPin *sdo_pin_; + GPIOPin *sdi_pin_; + uint32_t last_transition_{0}; + uint32_t wait_cycle_; + SPIClockPolarity clock_polarity_; + SPIClockPhase clock_phase_; - template std::array read_array() { - std::array data; - this->read_array(data.data(), N); - return data; + void HOT cycle_clock_() { + while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_) + continue; + this->last_transition_ += this->wait_cycle_; } + uint16_t transfer_(uint16_t data, size_t num_bits); +}; - void write_byte(uint8_t data) { - return this->parent_->template write_byte(data); - } +class SPIBus { + public: + SPIBus() = default; - void write_byte16(uint16_t data) { - return this->parent_->template write_byte16(data); - } + SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {} - void write_array16(const uint16_t *data, size_t length) { - this->parent_->template write_array16(data, length); + virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) { + return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); } - void write_array(const uint8_t *data, size_t length) { - this->parent_->template write_array(data, length); + virtual bool is_hw() { return false; } + + protected: + GPIOPin *clk_pin_{}; + GPIOPin *sdo_pin_{}; + GPIOPin *sdi_pin_{}; +}; + +class SPIClient; + +class SPIComponent : public Component { + public: + SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, + GPIOPin *cs_pin); + void unregister_device(SPIClient *device); + + void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; } + + void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; } + + void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; } + void set_data_pins(std::vector pins) { this->data_pins_ = std::move(pins); } + + void set_interface(SPIInterface interface) { + this->interface_ = interface; + this->using_hw_ = true; } - template void write_array(const std::array &data) { this->write_array(data.data(), N); } + void set_interface_name(const char *name) { this->interface_name_ = name; } - void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } + float get_setup_priority() const override { return setup_priority::BUS; } + + void setup() override; + void dump_config() override; - uint8_t transfer_byte(uint8_t data) { - return this->parent_->template transfer_byte(data); + protected: + GPIOPin *clk_pin_{nullptr}; + GPIOPin *sdi_pin_{nullptr}; + GPIOPin *sdo_pin_{nullptr}; + std::vector data_pins_{}; + + SPIInterface interface_{}; + bool using_hw_{false}; + const char *interface_name_{nullptr}; + SPIBus *spi_bus_{}; + std::map devices_; + + static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins); +}; + +using QuadSPIComponent = SPIComponent; +/** + * Base class for SPIDevice, un-templated. + */ +class SPIClient { + public: + SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) + : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {} + + virtual void spi_setup() { + esph_log_d("spi_device", "mode %u, data_rate %ukHz", (unsigned) this->mode_, (unsigned) (this->data_rate_ / 1000)); + this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_); } - void transfer_array(uint8_t *data, size_t length) { - this->parent_->template transfer_array(data, length); + virtual void spi_teardown() { + this->parent_->unregister_device(this); + this->delegate_ = SPIDelegate::NULL_DELEGATE; } - template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } + bool spi_is_ready() { return this->delegate_->is_ready(); } protected: + SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; + SPIMode mode_{MODE0}; + uint32_t data_rate_{1000000}; SPIComponent *parent_{nullptr}; GPIOPin *cs_{nullptr}; + SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; +}; + +/** + * The SPIDevice is what components using the SPI will create. + * + * @tparam BIT_ORDER + * @tparam CLOCK_POLARITY + * @tparam CLOCK_PHASE + * @tparam DATA_RATE + */ +template +class SPIDevice : public SPIClient { + public: + SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {} + + SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) { + this->set_spi_parent(parent); + this->set_cs_pin(cs_pin); + } + + void spi_setup() override { SPIClient::spi_setup(); } + + void spi_teardown() override { SPIClient::spi_teardown(); } + + void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } + + void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } + + void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; } + + void set_bit_order(SPIBitOrder order) { this->bit_order_ = order; } + + void set_mode(SPIMode mode) { this->mode_ = mode; } + + uint8_t read_byte() { return this->delegate_->transfer(0); } + + void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); } + + /** + * Write a single data item, up to 32 bits. + * @param data The data + * @param num_bits The number of bits to write. The lower num_bits of data will be sent. + */ + void write(uint16_t data, size_t num_bits) { this->delegate_->write(data, num_bits); }; + + /* Write command, address and data. Command and address will be written as single-bit SPI, + * data phase can be multiple bit (currently only 1 or 4) + * @param cmd_bits Number of bits to write in the command phase + * @param cmd The command value to write + * @param addr_bits Number of bits to write in addr phase + * @param address Address data + * @param data Plain data bytes + * @param length Number of data bytes + * @param bus_width The number of data lines to use for the data phase. + */ + void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, + size_t length, uint8_t bus_width = 1) { + this->delegate_->write_cmd_addr_data(cmd_bits, cmd, addr_bits, address, data, length, bus_width); + } + + void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); } + + /** + * Write the array data, replace with received data. + * @param data + * @param length + */ + void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } + + uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } + + /** Write 16 bit data. The driver will byte-swap if required. + */ + void write_byte16(uint16_t data) { this->delegate_->write16(data); } + + /** + * Write an array of data as 16 bit values, byte-swapping if required. Use of this should be avoided as + * it is horribly slow. + * @param data + * @param length + */ + void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); } + + void enable() { this->delegate_->begin_transaction(); } + + void disable() { this->delegate_->end_transaction(); } + + void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); } + + template void write_array(const std::array &data) { this->write_array(data.data(), N); } + + void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } + + template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } }; } // namespace spi diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp new file mode 100644 index 000000000000..f7fe523a337e --- /dev/null +++ b/esphome/components/spi/spi_arduino.cpp @@ -0,0 +1,95 @@ +#include "spi.h" +#include + +namespace esphome { +namespace spi { + +#ifdef USE_ARDUINO + +static const char *const TAG = "spi-esp-arduino"; +class SPIDelegateHw : public SPIDelegate { + public: + SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) + : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel) {} + + void begin_transaction() override { +#ifdef USE_RP2040 + SPISettings const settings(this->data_rate_, static_cast(this->bit_order_), this->mode_); +#elif defined(ESP8266) + // Arduino ESP8266 library has mangled values for SPI modes :-( + auto mode = (this->mode_ & 0x01) + ((this->mode_ & 0x02) << 3); + ESP_LOGVV(TAG, "8266 mangled SPI mode 0x%X", mode); + SPISettings const settings(this->data_rate_, this->bit_order_, mode); +#else + SPISettings const settings(this->data_rate_, this->bit_order_, this->mode_); +#endif + this->channel_->beginTransaction(settings); + SPIDelegate::begin_transaction(); + } + + void transfer(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); } + + void end_transaction() override { + this->channel_->endTransaction(); + SPIDelegate::end_transaction(); + } + + uint8_t transfer(uint8_t data) override { return this->channel_->transfer(data); } + + void write16(uint16_t data) override { this->channel_->transfer16(data); } + +#ifdef USE_RP2040 + void write_array(const uint8_t *ptr, size_t length) override { + // avoid overwriting the supplied buffer + uint8_t *rxbuf = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory) + memcpy(rxbuf, ptr, length); + this->channel_->transfer((void *) rxbuf, length); + delete[] rxbuf; // NOLINT(cppcoreguidelines-owning-memory) + } +#else + void write_array(const uint8_t *ptr, size_t length) override { this->channel_->writeBytes(ptr, length); } +#endif + + void read_array(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); } + + protected: + SPIInterface channel_{}; +}; + +class SPIBusHw : public SPIBus { + public: + SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) { +#ifdef USE_ESP8266 + channel->pins(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1); + channel->begin(); +#endif // USE_ESP8266 +#ifdef USE_ESP32 + channel->begin(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1); +#endif +#ifdef USE_RP2040 + if (Utility::get_pin_no(sdi) != -1) + channel->setRX(Utility::get_pin_no(sdi)); + if (Utility::get_pin_no(sdo) != -1) + channel->setTX(Utility::get_pin_no(sdo)); + channel->setSCK(Utility::get_pin_no(clk)); + channel->begin(); +#endif + } + + SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { + return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin); + } + + protected: + SPIInterface channel_{}; + bool is_hw() override { return true; } +}; + +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins) { + return new SPIBusHw(clk, sdo, sdi, interface); +} + +#endif // USE_ARDUINO +} // namespace spi +} // namespace esphome diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp new file mode 100644 index 000000000000..55680f72d32c --- /dev/null +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -0,0 +1,244 @@ +#include "spi.h" +#include + +namespace esphome { +namespace spi { + +#ifdef USE_ESP_IDF +static const char *const TAG = "spi-esp-idf"; +static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API. + +class SPIDelegateHw : public SPIDelegate { + public: + SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, + bool write_only) + : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) { + spi_device_interface_config_t config = {}; + config.mode = static_cast(mode); + config.clock_speed_hz = static_cast(data_rate); + config.spics_io_num = -1; + config.flags = 0; + config.queue_size = 1; + config.pre_cb = nullptr; + config.post_cb = nullptr; + if (bit_order == BIT_ORDER_LSB_FIRST) + config.flags |= SPI_DEVICE_BIT_LSBFIRST; + if (write_only) + config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY; + esp_err_t const err = spi_bus_add_device(channel, &config, &this->handle_); + if (err != ESP_OK) + ESP_LOGE(TAG, "Add device failed - err %X", err); + } + + bool is_ready() override { return this->handle_ != nullptr; } + + void begin_transaction() override { + if (this->is_ready()) { + if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK) + ESP_LOGE(TAG, "Failed to acquire SPI bus"); + SPIDelegate::begin_transaction(); + } else { + ESP_LOGW(TAG, "spi_setup called before initialisation"); + } + } + + void end_transaction() override { + if (this->is_ready()) { + SPIDelegate::end_transaction(); + spi_device_release_bus(this->handle_); + } + } + + ~SPIDelegateHw() override { + esp_err_t const err = spi_bus_remove_device(this->handle_); + if (err != ESP_OK) + ESP_LOGE(TAG, "Remove device failed - err %X", err); + } + + // do a transfer. either txbuf or rxbuf (but not both) may be null. + // transfers above the maximum size will be split. + // TODO - make use of the queue for interrupt transfers to provide a (short) pipeline of blocks + // when splitting is required. + void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) override { + if (rxbuf != nullptr && this->write_only_) { + ESP_LOGE(TAG, "Attempted read from write-only channel"); + return; + } + spi_transaction_t desc = {}; + desc.flags = 0; + while (length != 0) { + size_t const partial = std::min(length, MAX_TRANSFER_SIZE); + desc.length = partial * 8; + desc.rxlength = this->write_only_ ? 0 : partial * 8; + desc.tx_buffer = txbuf; + desc.rx_buffer = rxbuf; + // polling is used as it has about 10% less overhead than queuing an interrupt transfer + esp_err_t err = spi_device_polling_start(this->handle_, &desc, portMAX_DELAY); + if (err == ESP_OK) { + err = spi_device_polling_end(this->handle_, portMAX_DELAY); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "Transmit failed - err %X", err); + break; + } + length -= partial; + if (txbuf != nullptr) + txbuf += partial; + if (rxbuf != nullptr) + rxbuf += partial; + } + } + + void write(uint16_t data, size_t num_bits) override { + spi_transaction_ext_t desc = {}; + desc.command_bits = num_bits; + desc.base.flags = SPI_TRANS_VARIABLE_CMD; + desc.base.cmd = data; + esp_err_t err = spi_device_polling_start(this->handle_, (spi_transaction_t *) &desc, portMAX_DELAY); + if (err == ESP_OK) { + err = spi_device_polling_end(this->handle_, portMAX_DELAY); + } + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Transmit failed - err %X", err); + } + } + + /** + * Write command, address and data + * @param cmd_bits Number of bits to write in the command phase + * @param cmd The command value to write + * @param addr_bits Number of bits to write in addr phase + * @param address Address data + * @param data Remaining data bytes + * @param length Number of data bytes + * @param bus_width The number of data lines to use + */ + void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, + size_t length, uint8_t bus_width) override { + spi_transaction_ext_t desc = {}; + if (length == 0 && cmd_bits == 0 && addr_bits == 0) { + esph_log_w(TAG, "Nothing to transfer"); + return; + } + desc.base.flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_DUMMY; + if (bus_width == 4) { + desc.base.flags |= SPI_TRANS_MODE_QIO; + } else if (bus_width == 8) { + desc.base.flags |= SPI_TRANS_MODE_OCT; + } + desc.command_bits = cmd_bits; + desc.address_bits = addr_bits; + desc.dummy_bits = 0; + desc.base.rxlength = 0; + desc.base.cmd = cmd; + desc.base.addr = address; + do { + size_t chunk_size = std::min(length, MAX_TRANSFER_SIZE); + if (data != nullptr && chunk_size != 0) { + desc.base.length = chunk_size * 8; + desc.base.tx_buffer = data; + length -= chunk_size; + data += chunk_size; + } else { + length = 0; + desc.base.length = 0; + } + esp_err_t err = spi_device_polling_start(this->handle_, (spi_transaction_t *) &desc, portMAX_DELAY); + if (err == ESP_OK) { + err = spi_device_polling_end(this->handle_, portMAX_DELAY); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "Transmit failed - err %X", err); + return; + } + // if more data is to be sent, skip the command and address phases. + desc.command_bits = 0; + desc.address_bits = 0; + } while (length != 0); + } + + void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); } + + uint8_t transfer(uint8_t data) override { + uint8_t rxbuf; + this->transfer(&data, &rxbuf, 1); + return rxbuf; + } + + void write16(uint16_t data) override { this->write(data, 16); } + + void write_array(const uint8_t *ptr, size_t length) override { this->transfer(ptr, nullptr, length); } + + void write_array16(const uint16_t *data, size_t length) override { + if (this->bit_order_ == BIT_ORDER_LSB_FIRST) { + this->write_array((uint8_t *) data, length * 2); + } else { + uint16_t buffer[MAX_TRANSFER_SIZE / 2]; + while (length != 0) { + size_t const partial = std::min(length, MAX_TRANSFER_SIZE / 2); + for (size_t i = 0; i != partial; i++) { + buffer[i] = SPI_SWAP_DATA_TX(*data++, 16); + } + this->write_array((const uint8_t *) buffer, partial * 2); + length -= partial; + } + } + } + + void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); } + + protected: + SPIInterface channel_{}; + spi_device_handle_t handle_{}; + bool write_only_{false}; +}; + +class SPIBusHw : public SPIBus { + public: + SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel, std::vector data_pins) + : SPIBus(clk, sdo, sdi), channel_(channel) { + spi_bus_config_t buscfg = {}; + buscfg.sclk_io_num = Utility::get_pin_no(clk); + buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK; + if (data_pins.empty()) { + buscfg.mosi_io_num = Utility::get_pin_no(sdo); + buscfg.miso_io_num = Utility::get_pin_no(sdi); + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + } else { + buscfg.data0_io_num = data_pins[0]; + buscfg.data1_io_num = data_pins[1]; + buscfg.data2_io_num = data_pins[2]; + buscfg.data3_io_num = data_pins[3]; + buscfg.data4_io_num = -1; + buscfg.data5_io_num = -1; + buscfg.data6_io_num = -1; + buscfg.data7_io_num = -1; + buscfg.flags |= SPICOMMON_BUSFLAG_QUAD; + } + buscfg.max_transfer_sz = MAX_TRANSFER_SIZE; + auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO); + if (err != ESP_OK) + ESP_LOGE(TAG, "Bus init failed - err %X", err); + } + + SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { + return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin, + Utility::get_pin_no(this->sdi_pin_) == -1); + } + + protected: + SPIInterface channel_{}; + + bool is_hw() override { return true; } +}; + +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, + const std::vector &data_pins) { + return new SPIBusHw(clk, sdo, sdi, interface, data_pins); +} + +#endif +} // namespace spi +} // namespace esphome diff --git a/esphome/components/spi_device/__init__.py b/esphome/components/spi_device/__init__.py new file mode 100644 index 000000000000..65e7ee6fc630 --- /dev/null +++ b/esphome/components/spi_device/__init__.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from esphome.const import CONF_ID, CONF_MODE + +DEPENDENCIES = ["spi"] +CODEOWNERS = ["@clydebarrow"] + +MULTI_CONF = True +spi_device_ns = cg.esphome_ns.namespace("spi_device") + +spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice) + +Mode = spi.spi_ns.enum("SPIMode") +MODES = { + "0": Mode.MODE0, + "1": Mode.MODE1, + "2": Mode.MODE2, + "3": Mode.MODE3, + "MODE0": Mode.MODE0, + "MODE1": Mode.MODE1, + "MODE2": Mode.MODE2, + "MODE3": Mode.MODE3, +} + +BitOrder = spi.spi_ns.enum("SPIBitOrder") +ORDERS = { + "msb_first": BitOrder.BIT_ORDER_MSB_FIRST, + "lsb_first": BitOrder.BIT_ORDER_LSB_FIRST, +} +CONF_BIT_ORDER = "bit_order" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(spi_device), + cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), + cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True), + } +).extend(spi.spi_device_schema(False, "1MHz")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_mode(config[CONF_MODE])) + cg.add(var.set_bit_order(config[CONF_BIT_ORDER])) + await spi.register_spi_device(var, config) diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp new file mode 100644 index 000000000000..1f579cb802e4 --- /dev/null +++ b/esphome/components/spi_device/spi_device.cpp @@ -0,0 +1,31 @@ +#include "spi_device.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace spi_device { + +static const char *const TAG = "spi_device"; + +void SPIDeviceComponent::setup() { + ESP_LOGD(TAG, "Setting up SPIDevice..."); + this->spi_setup(); + ESP_LOGCONFIG(TAG, "SPIDevice started!"); +} + +void SPIDeviceComponent::dump_config() { + ESP_LOGCONFIG(TAG, "SPIDevice"); + 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 { + ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "MHz", this->data_rate_ / 1000000); + } +} + +float SPIDeviceComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace spi_device +} // namespace esphome diff --git a/esphome/components/spi_device/spi_device.h b/esphome/components/spi_device/spi_device.h new file mode 100644 index 000000000000..d8aef440a761 --- /dev/null +++ b/esphome/components/spi_device/spi_device.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace spi_device { + +class SPIDeviceComponent : public Component, + public spi::SPIDevice { + public: + void setup() override; + void dump_config() override; + + float get_setup_priority() const override; + + protected: +}; + +} // namespace spi_device +} // namespace esphome diff --git a/esphome/components/spi_led_strip/__init__.py b/esphome/components/spi_led_strip/__init__.py new file mode 100644 index 000000000000..850a1f6e0276 --- /dev/null +++ b/esphome/components/spi_led_strip/__init__.py @@ -0,0 +1,2 @@ +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["spi"] diff --git a/esphome/components/spi_led_strip/light.py b/esphome/components/spi_led_strip/light.py new file mode 100644 index 000000000000..78642935de5a --- /dev/null +++ b/esphome/components/spi_led_strip/light.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light +from esphome.components import spi +from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS + +spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip") +SpiLedStrip = spi_led_strip_ns.class_( + "SpiLedStrip", light.AddressableLight, spi.SPIDevice +) + +CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip), + cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int, + } +).extend(spi.spi_device_schema(False, "1MHz")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) + await light.register_light(var, config) + await spi.register_spi_device(var, config) + await cg.register_component(var, config) diff --git a/esphome/components/spi_led_strip/spi_led_strip.h b/esphome/components/spi_led_strip/spi_led_strip.h new file mode 100644 index 000000000000..0d8c1c1e1cff --- /dev/null +++ b/esphome/components/spi_led_strip/spi_led_strip.h @@ -0,0 +1,91 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/light/addressable_light.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace spi_led_strip { + +static const char *const TAG = "spi_led_strip"; +class SpiLedStrip : public light::AddressableLight, + public spi::SPIDevice { + public: + void setup() { this->spi_setup(); } + + int32_t size() const override { return this->num_leds_; } + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::RGB}); + return traits; + } + void set_num_leds(uint16_t num_leds) { + this->num_leds_ = num_leds; + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_size_ = num_leds * 4 + 8; + this->buf_ = allocator.allocate(this->buffer_size_); + if (this->buf_ == nullptr) { + esph_log_e(TAG, "Failed to allocate buffer of size %u", this->buffer_size_); + this->mark_failed(); + return; + } + + this->effect_data_ = allocator.allocate(num_leds); + if (this->effect_data_ == nullptr) { + esph_log_e(TAG, "Failed to allocate effect data of size %u", num_leds); + this->mark_failed(); + return; + } + memset(this->buf_, 0xFF, this->buffer_size_); + memset(this->buf_, 0, 4); + } + + void dump_config() { + esph_log_config(TAG, "SPI LED Strip:"); + esph_log_config(TAG, " 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 + esph_log_config(TAG, " Data rate: %ukHz", (unsigned) (this->data_rate_ / 1000)); + } + + void write_state(light::LightState *state) override { + if (this->is_failed()) + return; + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { + char strbuf[49]; + size_t len = std::min(this->buffer_size_, (size_t) (sizeof(strbuf) - 1) / 3); + memset(strbuf, 0, sizeof(strbuf)); + for (size_t i = 0; i != len; i++) { + sprintf(strbuf + i * 3, "%02X ", this->buf_[i]); + } + esph_log_v(TAG, "write_state: buf = %s", strbuf); + } + this->enable(); + this->write_array(this->buf_, this->buffer_size_); + this->disable(); + } + + void clear_effect_data() override { + for (int i = 0; i < this->size(); i++) + this->effect_data_[i] = 0; + } + + protected: + light::ESPColorView get_view_internal(int32_t index) const override { + size_t pos = index * 4 + 5; + return {this->buf_ + pos + 2, this->buf_ + pos + 1, this->buf_ + pos + 0, nullptr, + this->effect_data_ + index, &this->correction_}; + } + + size_t buffer_size_{}; + uint8_t *effect_data_{nullptr}; + uint8_t *buf_{nullptr}; + uint16_t num_leds_; +}; + +} // namespace spi_led_strip +} // namespace esphome diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index e1d855778a98..e5bcf681f4be 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -19,6 +19,7 @@ ENTITY_CATEGORY_CONFIG, UNIT_MINUTE, UNIT_SECOND, + CONF_SET_ACTION, ) AUTO_LOAD = ["number", "switch"] @@ -46,7 +47,6 @@ CONF_REPEAT_NUMBER = "repeat_number" CONF_REVERSE_SWITCH = "reverse_switch" CONF_RUN_DURATION_NUMBER = "run_duration_number" -CONF_SET_ACTION = "set_action" CONF_STANDBY_SWITCH = "standby_switch" CONF_VALVE_NUMBER = "valve_number" CONF_VALVE_OPEN_DELAY = "valve_open_delay" @@ -571,18 +571,12 @@ async def sprinkler_simple_action_to_code(config, action_id, template_arg, args) async def to_code(config): for sprinkler_controller in config: - var = cg.new_Pvariable(sprinkler_controller[CONF_ID]) - - if CONF_NAME in sprinkler_controller: - cg.add(var.set_name(sprinkler_controller[CONF_NAME])) + if len(sprinkler_controller[CONF_VALVES]) > 1: + name = sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME] else: - if len(sprinkler_controller[CONF_VALVES]) > 1: - name = sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME] - else: - name = sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][ - CONF_NAME - ] - cg.add(var.set_name(name)) + name = sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][CONF_NAME] + name = sprinkler_controller.get(CONF_NAME, name) + var = cg.new_Pvariable(sprinkler_controller[CONF_ID], name) await cg.register_component(var, sprinkler_controller) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 8afafcb5ced3..982d9add1afe 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -4,6 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -386,12 +387,17 @@ SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; } -void Sprinkler::setup() { +Sprinkler::Sprinkler() {} +Sprinkler::Sprinkler(const std::string &name) { + // The `name` is needed to set timers up, hence non-default constructor + // replaces `set_name()` method previously existed + this->name_ = name; this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)}); this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}); - this->all_valves_off_(true); } +void Sprinkler::setup() { this->all_valves_off_(true); } + void Sprinkler::loop() { for (auto &p : this->pump_) { p.loop(); @@ -870,7 +876,7 @@ void Sprinkler::queue_valve(optional valve_number, optional ru if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) { SprinklerQueueItem item{valve_number.value(), run_duration.value()}; this->queued_valves_.insert(this->queued_valves_.begin(), item); - ESP_LOGD(TAG, "Valve %u placed into queue with run duration of %u seconds", valve_number.value_or(0), + ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0), run_duration.value_or(0)); } } @@ -949,7 +955,7 @@ void Sprinkler::pause() { this->paused_valve_ = this->active_valve(); this->resume_duration_ = this->time_remaining_active_valve(); this->shutdown(false); - ESP_LOGD(TAG, "Paused valve %u with %u seconds remaining", this->paused_valve_.value_or(0), + ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0), this->resume_duration_.value_or(0)); } @@ -962,7 +968,7 @@ void Sprinkler::resume() { if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) { // Resume only if valve has not been completed yet if (!this->valve_cycle_complete_(this->paused_valve_.value())) { - ESP_LOGD(TAG, "Resuming valve %u with %u seconds remaining", this->paused_valve_.value_or(0), + ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0), this->resume_duration_.value_or(0)); this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value()); } @@ -1384,7 +1390,8 @@ void Sprinkler::load_next_valve_run_request_(const optional first_valve) this->next_req_.set_run_duration( this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0))); } else if ((this->repeat_count_++ < this->repeat().value_or(0))) { - ESP_LOGD(TAG, "Repeating - starting cycle %u of %u", this->repeat_count_ + 1, this->repeat().value_or(0) + 1); + ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1, + this->repeat().value_or(0) + 1); // if there are repeats remaining and no more valves were left in the cycle, start a new cycle this->prep_full_cycle_(); if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case... @@ -1415,7 +1422,7 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up 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 %u for %u seconds, cycle %u of %u", + 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); req->set_valve_operator(&vo); @@ -1640,7 +1647,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) { this->timer_[timer_index].start_time = millis(); this->timer_[timer_index].active = true; } - ESP_LOGVV(TAG, "Timer %u started for %u sec", static_cast(timer_index), + ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast(timer_index), this->timer_duration_(timer_index) / 1000); } @@ -1679,48 +1686,48 @@ void Sprinkler::sm_timer_callback_() { void Sprinkler::dump_config() { ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str()); if (this->manual_selection_delay_.has_value()) { - ESP_LOGCONFIG(TAG, " Manual Selection Delay: %u seconds", this->manual_selection_delay_.value_or(0)); + ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0)); } if (this->repeat().has_value()) { - ESP_LOGCONFIG(TAG, " Repeat Cycles: %u times", this->repeat().value_or(0)); + ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0)); } if (this->start_delay_) { if (this->start_delay_is_valve_delay_) { - ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %u seconds", this->start_delay_); + ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_); } else { - ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %u seconds", this->start_delay_); + ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_); } } if (this->stop_delay_) { if (this->stop_delay_is_valve_delay_) { - ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %u seconds", this->stop_delay_); + ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_); } else { - ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %u seconds", this->stop_delay_); + ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_); } } if (this->switching_delay_.has_value()) { if (this->valve_overlap_) { - ESP_LOGCONFIG(TAG, " Valve Overlap: %u seconds", this->switching_delay_.value_or(0)); + ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0)); } else { - ESP_LOGCONFIG(TAG, " Valve Open Delay: %u seconds", this->switching_delay_.value_or(0)); + ESP_LOGCONFIG(TAG, " Valve Open Delay: %" PRIu32 " seconds", this->switching_delay_.value_or(0)); ESP_LOGCONFIG(TAG, " Pump Switch Off During Valve Open Delay: %s", YESNO(this->pump_switch_off_during_valve_open_delay_)); } } for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) { - ESP_LOGCONFIG(TAG, " Valve %u:", valve_number); + ESP_LOGCONFIG(TAG, " Valve %zu:", valve_number); ESP_LOGCONFIG(TAG, " Name: %s", this->valve_name(valve_number)); - ESP_LOGCONFIG(TAG, " Run Duration: %u seconds", this->valve_run_duration(valve_number)); + ESP_LOGCONFIG(TAG, " Run Duration: %" PRIu32 " seconds", this->valve_run_duration(valve_number)); if (this->valve_[valve_number].valve_switch.pulse_duration()) { - ESP_LOGCONFIG(TAG, " Pulse Duration: %u milliseconds", + 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: %u", this->pump_.size()); + ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size()); } if (!this->valve_.empty()) { - ESP_LOGCONFIG(TAG, " Total number of valves: %u", this->valve_.size()); + ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size()); } } diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index ae7554d3af95..5311ae4c0593 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -204,12 +204,12 @@ class SprinklerValveRunRequest { class Sprinkler : public Component { public: + Sprinkler(); + Sprinkler(const std::string &name); void setup() override; void loop() override; void dump_config() override; - void set_name(const std::string &name) { this->name_ = name; } - /// add a valve to the controller void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw = nullptr); diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index ff8d5a35949e..0f01bab51438 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -20,7 +20,7 @@ DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, - UNIT_COUNTS_PER_CUBIC_METER, + UNIT_COUNTS_PER_CUBIC_CENTIMETER, UNIT_MICROMETER, ICON_CHEMICAL_WEAPON, ICON_COUNTER, @@ -73,31 +73,31 @@ state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_COUNTER, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_COUNTER, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_COUNTER, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_4_0): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_COUNTER, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, + unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER, icon=ICON_COUNTER, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index f4abd845c8a1..1fe74dfcb5ec 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -33,6 +33,7 @@ "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, "SH1107_128X64": SSD1306Model.SH1107_MODEL_128_64, + "SH1107_128X128": SSD1306Model.SH1107_MODEL_128_128, "SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32, "SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64, } @@ -63,15 +64,16 @@ def _validate(value): cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, cv.Optional(CONF_FLIP_X, default=True): cv.boolean, cv.Optional(CONF_FLIP_Y, default=True): cv.boolean, - cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=-32, max=32), - cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=-32, max=32), + # Offsets determine shifts of memory location to LCD rows/columns, + # and this family of controllers supports up to 128x128 screens + cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=128), + cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=128), cv.Optional(CONF_INVERT, default=False): cv.boolean, } ).extend(cv.polling_component_schema("1s")) async def setup_ssd1306(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 730e1c8f3586..90b805a79fe7 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -35,16 +35,31 @@ static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7; static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; +static const uint8_t SH1107_COMMAND_SET_START_LINE = 0xDC; +static const uint8_t SH1107_COMMAND_CHARGE_PUMP = 0xAD; + void SSD1306::setup() { this->init_internal_(this->get_buffer_length_()); + // SH1107 resources + // + // Datasheet v2.3: + // www.displayfuture.com/Display/datasheet/controller/SH1107.pdf + // Adafruit C++ driver: + // github.com/adafruit/Adafruit_SH110x + // Adafruit CircuitPython driver: + // github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1107 + // Turn off display during initialization (0xAE) this->command(SSD1306_COMMAND_DISPLAY_OFF); - // Set oscillator frequency to 4'b1000 with no clock division (0xD5) - this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); - // Oscillator frequency <= 4'b1000, no clock division - this->command(0x80); + // If SH1107, use POR defaults (0x50) = divider 1, frequency +0% + if (!this->is_sh1107_()) { + // Set oscillator frequency to 4'b1000 with no clock division (0xD5) + this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); + // Oscillator frequency <= 4'b1000, no clock division + this->command(0x80); + } // Enable low power display mode for SSD1305 (0xD8) if (this->is_ssd1305_()) { @@ -60,11 +75,26 @@ void SSD1306::setup() { this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y); this->command(0x00 + this->offset_y_); - // Set start line at line 0 (0x40) - this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); + if (this->is_sh1107_()) { + // Set start line at line 0 (0xDC) + this->command(SH1107_COMMAND_SET_START_LINE); + this->command(0x00); + } else { + // Set start line at line 0 (0x40) + this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); + } - // SSD1305 does not have charge pump - if (!this->is_ssd1305_()) { + if (this->is_ssd1305_()) { + // SSD1305 does not have charge pump + } else if (this->is_sh1107_()) { + // Enable charge pump (0xAD) + this->command(SH1107_COMMAND_CHARGE_PUMP); + if (this->external_vcc_) { + this->command(0x8A); + } else { + this->command(0x8B); + } + } else { // Enable charge pump (0x8D) this->command(SSD1306_COMMAND_CHARGE_PUMP); if (this->external_vcc_) { @@ -76,34 +106,41 @@ void SSD1306::setup() { // Set addressing mode to horizontal (0x20) this->command(SSD1306_COMMAND_MEMORY_MODE); - this->command(0x00); - + if (!this->is_sh1107_()) { + // SH1107 memory mode is a 1 byte command + this->command(0x00); + } // X flip mode (0xA0, 0xA1) this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_); // Y flip mode (0xC0, 0xC8) this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3)); - // Set pin configuration (0xDA) - this->command(SSD1306_COMMAND_SET_COM_PINS); - switch (this->model_) { - case SSD1306_MODEL_128_32: - case SH1106_MODEL_128_32: - case SSD1306_MODEL_96_16: - case SH1106_MODEL_96_16: - this->command(0x02); - break; - case SSD1306_MODEL_128_64: - case SH1106_MODEL_128_64: - case SSD1306_MODEL_64_48: - case SSD1306_MODEL_64_32: - case SH1106_MODEL_64_48: - case SH1107_MODEL_128_64: - case SSD1305_MODEL_128_32: - case SSD1305_MODEL_128_64: - case SSD1306_MODEL_72_40: - this->command(0x12); - break; + if (!this->is_sh1107_()) { + // Set pin configuration (0xDA) + this->command(SSD1306_COMMAND_SET_COM_PINS); + switch (this->model_) { + case SSD1306_MODEL_128_32: + case SH1106_MODEL_128_32: + case SSD1306_MODEL_96_16: + case SH1106_MODEL_96_16: + this->command(0x02); + break; + case SSD1306_MODEL_128_64: + case SH1106_MODEL_128_64: + case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: + case SH1106_MODEL_64_48: + case SSD1305_MODEL_128_32: + case SSD1305_MODEL_128_64: + case SSD1306_MODEL_72_40: + this->command(0x12); + break; + case SH1107_MODEL_128_64: + case SH1107_MODEL_128_128: + // Not used, but prevents build warning + break; + } } // Pre-charge period (0xD9) @@ -117,7 +154,9 @@ void SSD1306::setup() { // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); switch (this->model_) { + case SH1106_MODEL_128_64: case SH1107_MODEL_128_64: + case SH1107_MODEL_128_128: this->command(0x35); break; case SSD1306_MODEL_72_40: @@ -132,7 +171,7 @@ void SSD1306::setup() { this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME); // Inverse display mode (0xA6, 0xA7) - this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); + this->set_invert(this->invert_); // Disable scrolling mode (0x2E) this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); @@ -149,7 +188,7 @@ void SSD1306::setup() { this->turn_on(); } void SSD1306::display() { - if (this->is_sh1106_()) { + if (this->is_sh1106_() || this->is_sh1107_()) { this->write_display_data(); return; } @@ -183,6 +222,7 @@ bool SSD1306::is_sh1106_() const { return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 || this->model_ == SH1106_MODEL_128_64; } +bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || this->model_ == SH1107_MODEL_128_128; } bool SSD1306::is_ssd1305_() const { return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64; } @@ -190,6 +230,13 @@ void SSD1306::update() { this->do_update_(); this->display(); } + +void SSD1306::set_invert(bool invert) { + this->invert_ = invert; + // Inverse display mode (0xA6, 0xA7) + this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); +} +float SSD1306::get_contrast() { return this->contrast_; }; void SSD1306::set_contrast(float contrast) { // validation this->contrast_ = clamp(contrast, 0.0F, 1.0F); @@ -197,6 +244,7 @@ void SSD1306::set_contrast(float contrast) { this->command(SSD1306_COMMAND_SET_CONTRAST); this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_))); } +float SSD1306::get_brightness() { return this->brightness_; }; void SSD1306::set_brightness(float brightness) { // validation if (!this->is_ssd1305_()) @@ -218,6 +266,7 @@ void SSD1306::turn_off() { int SSD1306::get_height_internal() { switch (this->model_) { case SH1107_MODEL_128_64: + case SH1107_MODEL_128_128: return 128; case SSD1306_MODEL_128_32: case SSD1306_MODEL_64_32: @@ -248,6 +297,7 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_128_64: case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_64: + case SH1107_MODEL_128_128: return 128; case SSD1306_MODEL_96_16: case SH1106_MODEL_96_16: diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 7402ae3af20c..14ec309ae0de 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -19,11 +19,12 @@ enum SSD1306Model { SH1106_MODEL_96_16, SH1106_MODEL_64_48, SH1107_MODEL_128_64, + SH1107_MODEL_128_128, SSD1305_MODEL_128_32, SSD1305_MODEL_128_64, }; -class SSD1306 : public PollingComponent, public display::DisplayBuffer { +class SSD1306 : public display::DisplayBuffer { public: void setup() override; @@ -35,7 +36,9 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } void init_contrast(float contrast) { this->contrast_ = contrast; } + float get_contrast(); void set_contrast(float contrast); + float get_brightness(); void init_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness); void init_flip_x(bool flip_x) { this->flip_x_ = flip_x; } @@ -43,6 +46,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } void init_invert(bool invert) { this->invert_ = invert; } + void set_invert(bool invert); bool is_on(); void turn_on(); void turn_off(); @@ -57,6 +61,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void init_reset_(); bool is_sh1106_() const; + bool is_sh1107_() const; bool is_ssd1305_() const; void draw_absolute_pixel_internal(int x, int y, Color color) override; diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 96734eb61814..ed7cf102eeae 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -38,13 +38,19 @@ void I2CSSD1306::dump_config() { } void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void HOT I2CSSD1306::write_display_data() { - if (this->is_sh1106_()) { + if (this->is_sh1106_() || this->is_sh1107_()) { uint32_t i = 0; for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { this->command(0xB0 + page); // row - this->command(0x02); // lower column - this->command(0x10); // higher column - + if (this->is_sh1106_()) { + this->command(0x02); // lower column - 0x02 is historical SH1106 value + } else { + // Other SH1107 drivers use 0x00 + // Column values dont change and it seems they can be set only once, + // but we follow SH1106 implementation and resend them + this->command(0x00); + } + this->command(0x10); // higher column for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { uint8_t data[16]; for (uint8_t &j : data) diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 7f025d77cd58..0a0debfd659e 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -36,10 +36,14 @@ void SPISSD1306::command(uint8_t value) { this->disable(); } void HOT SPISSD1306::write_display_data() { - if (this->is_sh1106_()) { + if (this->is_sh1106_() || this->is_sh1107_()) { for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { this->command(0xB0 + y); - this->command(0x02); + if (this->is_sh1106_()) { + this->command(0x02); + } else { + this->command(0x00); + } this->command(0x10); this->dc_pin_->digital_write(true); for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { diff --git a/esphome/components/ssd1322_base/__init__.py b/esphome/components/ssd1322_base/__init__.py index 97fb0d2a744b..471c8749863e 100644 --- a/esphome/components/ssd1322_base/__init__.py +++ b/esphome/components/ssd1322_base/__init__.py @@ -33,7 +33,6 @@ async def setup_ssd1322(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/ssd1322_base/ssd1322_base.h b/esphome/components/ssd1322_base/ssd1322_base.h index d672b298d661..9f4d39976c97 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.h +++ b/esphome/components/ssd1322_base/ssd1322_base.h @@ -11,7 +11,7 @@ enum SSD1322Model { SSD1322_MODEL_256_64 = 0, }; -class SSD1322 : public PollingComponent, public display::DisplayBuffer { +class SSD1322 : public display::DisplayBuffer { public: void setup() override; diff --git a/esphome/components/ssd1325_base/__init__.py b/esphome/components/ssd1325_base/__init__.py index 1a6f7fb5193d..e66cfbc6849e 100644 --- a/esphome/components/ssd1325_base/__init__.py +++ b/esphome/components/ssd1325_base/__init__.py @@ -37,7 +37,6 @@ async def setup_ssd1325(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/ssd1325_base/ssd1325_base.h b/esphome/components/ssd1325_base/ssd1325_base.h index 8ba6a56c8b23..ae033e582bb3 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.h +++ b/esphome/components/ssd1325_base/ssd1325_base.h @@ -15,7 +15,7 @@ enum SSD1325Model { SSD1327_MODEL_128_128, }; -class SSD1325 : public PollingComponent, public display::DisplayBuffer { +class SSD1325 : public display::DisplayBuffer { public: void setup() override; diff --git a/esphome/components/ssd1327_base/__init__.py b/esphome/components/ssd1327_base/__init__.py index af2eb3489d78..7f2259cf3280 100644 --- a/esphome/components/ssd1327_base/__init__.py +++ b/esphome/components/ssd1327_base/__init__.py @@ -26,7 +26,6 @@ async def setup_ssd1327(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/ssd1327_base/ssd1327_base.h b/esphome/components/ssd1327_base/ssd1327_base.h index 5639beb82899..207023a3d3ed 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.h +++ b/esphome/components/ssd1327_base/ssd1327_base.h @@ -11,7 +11,7 @@ enum SSD1327Model { SSD1327_MODEL_128_128 = 0, }; -class SSD1327 : public PollingComponent, public display::DisplayBuffer { +class SSD1327 : public display::DisplayBuffer { public: void setup() override; diff --git a/esphome/components/ssd1331_base/__init__.py b/esphome/components/ssd1331_base/__init__.py index 169c0eed1a57..80162979fc1c 100644 --- a/esphome/components/ssd1331_base/__init__.py +++ b/esphome/components/ssd1331_base/__init__.py @@ -18,7 +18,6 @@ async def setup_ssd1331(var, config): - await cg.register_component(var, config) await display.register_display(var, config) if CONF_RESET_PIN in config: diff --git a/esphome/components/ssd1331_base/ssd1331_base.h b/esphome/components/ssd1331_base/ssd1331_base.h index be5713f20866..719bfc1f8ba6 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.h +++ b/esphome/components/ssd1331_base/ssd1331_base.h @@ -7,7 +7,7 @@ namespace esphome { namespace ssd1331_base { -class SSD1331 : public PollingComponent, public display::DisplayBuffer { +class SSD1331 : public display::DisplayBuffer { public: void setup() override; diff --git a/esphome/components/ssd1351_base/__init__.py b/esphome/components/ssd1351_base/__init__.py index 2988dd4bf3b3..150d89afedab 100644 --- a/esphome/components/ssd1351_base/__init__.py +++ b/esphome/components/ssd1351_base/__init__.py @@ -27,7 +27,6 @@ async def setup_ssd1351(var, config): - await cg.register_component(var, config) await display.register_display(var, config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index 036a6a0e82a7..38ea05a0b87f 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -112,6 +112,9 @@ void SSD1351::set_brightness(float brightness) { } else { this->brightness_ = brightness; } + if (!this->is_ready()) { + return; // Component is not yet setup skip the command + } // now write the new brightness level to the display this->command(SSD1351_CONTRASTMASTER); this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1351_base/ssd1351_base.h b/esphome/components/ssd1351_base/ssd1351_base.h index 2f1e0237cdf4..62777a60a059 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.h +++ b/esphome/components/ssd1351_base/ssd1351_base.h @@ -12,7 +12,7 @@ enum SSD1351Model { SSD1351_MODEL_128_128, }; -class SSD1351 : public PollingComponent, public display::DisplayBuffer { +class SSD1351 : public display::DisplayBuffer { public: void setup() override; diff --git a/esphome/components/st7567_base/__init__.py b/esphome/components/st7567_base/__init__.py new file mode 100644 index 000000000000..7ce50fd99f3c --- /dev/null +++ b/esphome/components/st7567_base/__init__.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display +from esphome.const import ( + CONF_LAMBDA, + CONF_RESET_PIN, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_TRANSFORM, + CONF_INVERT_COLORS, +) + +CODEOWNERS = ["@latonita"] + +st7567_base_ns = cg.esphome_ns.namespace("st7567_base") +ST7567 = st7567_base_ns.class_("ST7567", cg.PollingComponent, display.DisplayBuffer) +ST7567Model = st7567_base_ns.enum("ST7567Model") + +# todo in future: reuse following constants from const.py when they are released + + +ST7567_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + } +).extend(cv.polling_component_schema("1s")) + + +async def setup_st7567(var, config): + await display.register_display(var, config) + + if CONF_RESET_PIN in config: + reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset)) + + cg.add(var.init_invert_colors(config[CONF_INVERT_COLORS])) + + if CONF_TRANSFORM in config: + transform = config[CONF_TRANSFORM] + cg.add(var.init_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.init_mirror_y(transform[CONF_MIRROR_Y])) + + 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/st7567_base/st7567_base.cpp b/esphome/components/st7567_base/st7567_base.cpp new file mode 100644 index 000000000000..b22a7d7fd5ce --- /dev/null +++ b/esphome/components/st7567_base/st7567_base.cpp @@ -0,0 +1,152 @@ +#include "st7567_base.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace st7567_base { + +static const char *const TAG = "st7567"; + +void ST7567::setup() { + this->init_internal_(this->get_buffer_length_()); + this->display_init_(); +} + +void ST7567::display_init_() { + ESP_LOGD(TAG, "Initializing ST7567 display..."); + this->display_init_registers_(); + this->clear(); + this->write_display_data(); + this->command(ST7567_DISPLAY_ON); +} + +void ST7567::display_init_registers_() { + this->command(ST7567_BIAS_9); + this->command(this->mirror_x_ ? ST7567_SEG_REVERSE : ST7567_SEG_NORMAL); + this->command(this->mirror_y_ ? ST7567_COM_NORMAL : ST7567_COM_REMAP); + this->command(ST7567_POWER_CTL | 0x4); + this->command(ST7567_POWER_CTL | 0x6); + this->command(ST7567_POWER_CTL | 0x7); + + this->set_brightness(this->brightness_); + this->set_contrast(this->contrast_); + + this->command(ST7567_INVERT_OFF | this->invert_colors_); + + this->command(ST7567_BOOSTER_ON); + this->command(ST7567_REGULATOR_ON); + this->command(ST7567_POWER_ON); + + this->command(ST7567_SCAN_START_LINE); + this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_); +} + +void ST7567::display_sw_refresh_() { + ESP_LOGD(TAG, "Performing refresh sequence..."); + this->command(ST7567_SW_REFRESH); + this->display_init_registers_(); +} + +void ST7567::request_refresh() { + // as per datasheet: It is recommended to use the refresh sequence regularly in a specified interval. + this->refresh_requested_ = true; +} + +void ST7567::update() { + this->do_update_(); + if (this->refresh_requested_) { + this->refresh_requested_ = false; + this->display_sw_refresh_(); + } + this->write_display_data(); +} + +void ST7567::set_all_pixels_on(bool enable) { + this->all_pixels_on_ = enable; + this->command(ST7567_PIXELS_NORMAL | this->all_pixels_on_); +} + +void ST7567::set_invert_colors(bool invert_colors) { + this->invert_colors_ = invert_colors; + this->command(ST7567_INVERT_OFF | this->invert_colors_); +} + +void ST7567::set_contrast(uint8_t val) { + this->contrast_ = val & 0b111111; + // 0..63, 26 is normal + + // two byte command + // first byte 0x81 + // second byte 0-63 + + this->command(ST7567_SET_EV_CMD); + this->command(this->contrast_); +} + +void ST7567::set_brightness(uint8_t val) { + this->brightness_ = val & 0b111; + // 0..7, 5 normal + + //********Adjust display brightness******** + // 0x20-0x27 is the internal Rb/Ra resistance + // adjustment setting of V5 voltage RR=4.5V + + this->command(ST7567_RESISTOR_RATIO | this->brightness_); +} + +bool ST7567::is_on() { return this->is_on_; } + +void ST7567::turn_on() { + this->command(ST7567_DISPLAY_ON); + this->is_on_ = true; +} + +void ST7567::turn_off() { + this->command(ST7567_DISPLAY_OFF); + this->is_on_ = false; +} + +void ST7567::set_scroll(uint8_t line) { this->start_line_ = line % this->get_height_internal(); } + +int ST7567::get_width_internal() { return 128; } + +int ST7567::get_height_internal() { return 64; } + +// 128x64, but memory size 132x64, line starts from 0, but if mirrored then it starts from 131, not 127 +size_t ST7567::get_buffer_length_() { + return size_t(this->get_width_internal() + 4) * size_t(this->get_height_internal()) / 8u; +} + +void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + return; + } + + uint16_t pos = x + (y / 8) * this->get_width_internal(); + uint8_t subpos = y & 0x07; + if (color.is_on()) { + this->buffer_[pos] |= (1 << subpos); + } else { + this->buffer_[pos] &= ~(1 << subpos); + } +} + +void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } + +void ST7567::init_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(true); + delay(1); + // Trigger Reset + this->reset_pin_->digital_write(false); + delay(10); + // Wake up + this->reset_pin_->digital_write(true); + } +} + +const char *ST7567::model_str_() { return "ST7567 128x64"; } + +} // namespace st7567_base +} // namespace esphome diff --git a/esphome/components/st7567_base/st7567_base.h b/esphome/components/st7567_base/st7567_base.h new file mode 100644 index 000000000000..e3053673d1aa --- /dev/null +++ b/esphome/components/st7567_base/st7567_base.h @@ -0,0 +1,100 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7567_base { + +static const uint8_t ST7567_BOOSTER_ON = 0x2C; // internal power supply on +static const uint8_t ST7567_REGULATOR_ON = 0x2E; // internal power supply on +static const uint8_t ST7567_POWER_ON = 0x2F; // internal power supply on + +static const uint8_t ST7567_DISPLAY_ON = 0xAF; // Display ON. Normal Display Mode. +static const uint8_t ST7567_DISPLAY_OFF = 0xAE; // Display OFF. All SEGs/COMs output with VSS +static const uint8_t ST7567_SET_START_LINE = 0x40; +static const uint8_t ST7567_POWER_CTL = 0x28; +static const uint8_t ST7567_SEG_NORMAL = 0xA0; // +static const uint8_t ST7567_SEG_REVERSE = 0xA1; // mirror X axis (horizontal) +static const uint8_t ST7567_COM_NORMAL = 0xC0; // +static const uint8_t ST7567_COM_REMAP = 0xC8; // mirror Y axis (vertical) +static const uint8_t ST7567_PIXELS_NORMAL = 0xA4; // display ram content +static const uint8_t ST7567_PIXELS_ALL_ON = 0xA5; // all pixels on +static const uint8_t ST7567_INVERT_OFF = 0xA6; // normal pixels +static const uint8_t ST7567_INVERT_ON = 0xA7; // inverted pixels +static const uint8_t ST7567_SCAN_START_LINE = 0x40; // scrolling = 0x40 + (0..63) +static const uint8_t ST7567_COL_ADDR_H = 0x10; // x pos (0..95) 4 MSB +static const uint8_t ST7567_COL_ADDR_L = 0x00; // x pos (0..95) 4 LSB +static const uint8_t ST7567_PAGE_ADDR = 0xB0; // y pos, 8.5 rows (0..8) +static const uint8_t ST7567_BIAS_9 = 0xA2; +static const uint8_t ST7567_CONTRAST = 0x80; // 0x80 + (0..31) +static const uint8_t ST7567_SET_EV_CMD = 0x81; +static const uint8_t ST7567_SET_EV_PARAM = 0x00; +static const uint8_t ST7567_RESISTOR_RATIO = 0x20; +static const uint8_t ST7567_SW_REFRESH = 0xE2; + +class ST7567 : public display::DisplayBuffer { + public: + void setup() override; + + void update() override; + + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void init_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } + void init_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } + void init_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void set_invert_colors(bool invert_colors); // inversion of screen colors + void set_contrast(uint8_t val); // 0..63, 27-30 normal + void set_brightness(uint8_t val); // 0..7, 5 normal + void set_all_pixels_on(bool enable); // turn on all pixels, this doesn't affect RAM + void set_scroll(uint8_t line); // set display start line: for screen scrolling w/o affecting RAM + + bool is_on(); + void turn_on(); + void turn_off(); + + void request_refresh(); // from datasheet: It is recommended to use the refresh sequence regularly in a specified + // interval. + + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } + + protected: + virtual void command(uint8_t value) = 0; + virtual void write_display_data() = 0; + + void init_reset_(); + void display_init_(); + void display_init_registers_(); + void display_sw_refresh_(); + + void draw_absolute_pixel_internal(int x, int y, Color color) override; + + int get_height_internal() override; + int get_width_internal() override; + size_t get_buffer_length_(); + + int get_offset_x_() { return mirror_x_ ? 4 : 0; }; + + const char *model_str_(); + + GPIOPin *reset_pin_{nullptr}; + bool is_on_{false}; + // float contrast_{1.0}; + // float brightness_{1.0}; + uint8_t contrast_{27}; + uint8_t brightness_{5}; + bool mirror_x_{true}; + bool mirror_y_{true}; + bool invert_colors_{false}; + bool all_pixels_on_{false}; + uint8_t start_line_{0}; + bool refresh_requested_{false}; +}; + +} // namespace st7567_base +} // namespace esphome diff --git a/esphome/components/st7567_i2c/__init__.py b/esphome/components/st7567_i2c/__init__.py new file mode 100644 index 000000000000..dd06cfffea04 --- /dev/null +++ b/esphome/components/st7567_i2c/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/st7567_i2c/display.py b/esphome/components/st7567_i2c/display.py new file mode 100644 index 000000000000..fa92d652d7b0 --- /dev/null +++ b/esphome/components/st7567_i2c/display.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import st7567_base, i2c +from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES + +CODEOWNERS = ["@latonita"] + +AUTO_LOAD = ["st7567_base"] +DEPENDENCIES = ["i2c"] + +st7567_i2c = cg.esphome_ns.namespace("st7567_i2c") +I2CST7567 = st7567_i2c.class_("I2CST7567", st7567_base.ST7567, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All( + st7567_base.ST7567_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(I2CST7567), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x3F)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await st7567_base.setup_st7567(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/st7567_i2c/st7567_i2c.cpp b/esphome/components/st7567_i2c/st7567_i2c.cpp new file mode 100644 index 000000000000..05173d1be5a8 --- /dev/null +++ b/esphome/components/st7567_i2c/st7567_i2c.cpp @@ -0,0 +1,60 @@ +#include "st7567_i2c.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7567_i2c { + +static const char *const TAG = "st7567_i2c"; + +void I2CST7567::setup() { + ESP_LOGCONFIG(TAG, "Setting up I2C ST7567 display..."); + this->init_reset_(); + + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + ST7567::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, " 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); + + if (this->error_code_ == COMMUNICATION_FAILED) { + ESP_LOGE(TAG, "Communication with I2C ST7567 failed!"); + } +} + +void I2CST7567::command(uint8_t value) { this->write_byte(0x00, value); } + +void HOT I2CST7567::write_display_data() { + // ST7567A has built-in RAM with 132x65 bit capacity which stores the display data. + // but only first 128 pixels from each line are shown on screen + // if screen got flipped horizontally then it shows last 128 pixels, + // so we need to write x coordinate starting from column 4, not column 0 + this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_); + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { + this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page + this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address + this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address + + static const size_t BLOCK_SIZE = 64; + for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x += BLOCK_SIZE) { + this->write_register(esphome::st7567_base::ST7567_SET_START_LINE, &buffer_[y * this->get_width_internal() + x], + this->get_width_internal() - x > BLOCK_SIZE ? BLOCK_SIZE : this->get_width_internal() - x, + true); + } + } +} + +} // namespace st7567_i2c +} // namespace esphome diff --git a/esphome/components/st7567_i2c/st7567_i2c.h b/esphome/components/st7567_i2c/st7567_i2c.h new file mode 100644 index 000000000000..f760a54945c4 --- /dev/null +++ b/esphome/components/st7567_i2c/st7567_i2c.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/st7567_base/st7567_base.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace st7567_i2c { + +class I2CST7567 : public st7567_base::ST7567, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + protected: + void command(uint8_t value) override; + void write_display_data() override; + + enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; +}; + +} // namespace st7567_i2c +} // namespace esphome diff --git a/esphome/components/st7567_spi/__init__.py b/esphome/components/st7567_spi/__init__.py new file mode 100644 index 000000000000..dd06cfffea04 --- /dev/null +++ b/esphome/components/st7567_spi/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/st7567_spi/display.py b/esphome/components/st7567_spi/display.py new file mode 100644 index 000000000000..aabe02a2d844 --- /dev/null +++ b/esphome/components/st7567_spi/display.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi, st7567_base +from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES + +CODEOWNERS = ["@latonita"] + +AUTO_LOAD = ["st7567_base"] +DEPENDENCIES = ["spi"] + +st7567_spi = cg.esphome_ns.namespace("st7567_spi") +SPIST7567 = st7567_spi.class_("SPIST7567", st7567_base.ST7567, spi.SPIDevice) + +CONFIG_SCHEMA = cv.All( + st7567_base.ST7567_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPIST7567), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await st7567_base.setup_st7567(var, config) + await spi.register_spi_device(var, config) + + dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) + cg.add(var.set_dc_pin(dc)) diff --git a/esphome/components/st7567_spi/st7567_spi.cpp b/esphome/components/st7567_spi/st7567_spi.cpp new file mode 100644 index 000000000000..25698d0dd03f --- /dev/null +++ b/esphome/components/st7567_spi/st7567_spi.cpp @@ -0,0 +1,66 @@ +#include "st7567_spi.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7567_spi { + +static const char *const TAG = "st7567_spi"; + +void SPIST7567::setup() { + ESP_LOGCONFIG(TAG, "Setting up SPI ST7567 display..."); + this->spi_setup(); + this->dc_pin_->setup(); + if (this->cs_) + this->cs_->setup(); + + this->init_reset_(); + ST7567::setup(); +} + +void SPIST7567::dump_config() { + LOG_DISPLAY("", "SPI ST7567", 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, " 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); +} + +void SPIST7567::command(uint8_t value) { + if (this->cs_) + this->cs_->digital_write(true); + this->dc_pin_->digital_write(false); + delay(1); + this->enable(); + if (this->cs_) + this->cs_->digital_write(false); + this->write_byte(value); + if (this->cs_) + this->cs_->digital_write(true); + this->disable(); +} + +void HOT SPIST7567::write_display_data() { + // ST7567A has built-in RAM with 132x65 bit capacity which stores the display data. + // but only first 128 pixels from each line are shown on screen + // if screen got flipped horizontally then it shows last 128 pixels, + // so we need to write x coordinate starting from column 4, not column 0 + this->command(esphome::st7567_base::ST7567_SET_START_LINE + this->start_line_); + for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { + this->dc_pin_->digital_write(false); + this->command(esphome::st7567_base::ST7567_PAGE_ADDR + y); // Set Page + this->command(esphome::st7567_base::ST7567_COL_ADDR_H); // Set MSB Column address + this->command(esphome::st7567_base::ST7567_COL_ADDR_L + this->get_offset_x_()); // Set LSB Column address + this->dc_pin_->digital_write(true); + + this->enable(); + this->write_array(&this->buffer_[y * this->get_width_internal()], this->get_width_internal()); + this->disable(); + } +} + +} // namespace st7567_spi +} // namespace esphome diff --git a/esphome/components/st7567_spi/st7567_spi.h b/esphome/components/st7567_spi/st7567_spi.h new file mode 100644 index 000000000000..e8d1ebe0cce6 --- /dev/null +++ b/esphome/components/st7567_spi/st7567_spi.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/st7567_base/st7567_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace st7567_spi { + +class SPIST7567 : public st7567_base::ST7567, + public spi::SPIDevice { + public: + void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } + + void setup() override; + + void dump_config() override; + + protected: + void command(uint8_t value) override; + + void write_display_data() override; + + GPIOPin *dc_pin_; +}; + +} // namespace st7567_spi +} // namespace esphome diff --git a/esphome/components/st7701s/__init__.py b/esphome/components/st7701s/__init__.py new file mode 100644 index 000000000000..c58ce8a01e84 --- /dev/null +++ b/esphome/components/st7701s/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py new file mode 100644 index 000000000000..516d770f8bd5 --- /dev/null +++ b/esphome/components/st7701s/display.py @@ -0,0 +1,253 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import ( + spi, + display, +) +from esphome.const import ( + CONF_DC_PIN, + CONF_HSYNC_PIN, + CONF_RESET_PIN, + CONF_DATA_PINS, + CONF_ID, + CONF_DIMENSIONS, + CONF_VSYNC_PIN, + CONF_WIDTH, + CONF_HEIGHT, + CONF_LAMBDA, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_COLOR_ORDER, + CONF_TRANSFORM, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_INVERT_COLORS, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_NUMBER, + CONF_IGNORE_STRAPPING_WARNING, +) + +from esphome.components.esp32 import ( + only_on_variant, + const, +) +from esphome.components.rpi_dpi_rgb.display import ( + CONF_PCLK_FREQUENCY, + CONF_PCLK_INVERTED, +) +from .init_sequences import ( + ST7701S_INITS, + cmd, +) + +CONF_INIT_SEQUENCE = "init_sequence" +CONF_DE_PIN = "de_pin" +CONF_PCLK_PIN = "pclk_pin" + +CONF_HSYNC_PULSE_WIDTH = "hsync_pulse_width" +CONF_HSYNC_BACK_PORCH = "hsync_back_porch" +CONF_HSYNC_FRONT_PORCH = "hsync_front_porch" +CONF_VSYNC_PULSE_WIDTH = "vsync_pulse_width" +CONF_VSYNC_BACK_PORCH = "vsync_back_porch" +CONF_VSYNC_FRONT_PORCH = "vsync_front_porch" + +DEPENDENCIES = ["spi", "esp32"] + +st7701s_ns = cg.esphome_ns.namespace("st7701s") +ST7701S = st7701s_ns.class_("ST7701S", display.Display, cg.Component, spi.SPIDevice) +ColorOrder = display.display_ns.enum("ColorMode") + +COLOR_ORDERS = { + "RGB": ColorOrder.COLOR_ORDER_RGB, + "BGR": ColorOrder.COLOR_ORDER_BGR, +} +DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema + + +def data_pin_validate(value): + """ + It is safe to use strapping pins as RGB output data bits, as they are outputs only, + and not initialised until after boot. + """ + if not isinstance(value, dict): + try: + return DATA_PIN_SCHEMA( + {CONF_NUMBER: value, CONF_IGNORE_STRAPPING_WARNING: True} + ) + except cv.Invalid: + pass + return DATA_PIN_SCHEMA(value) + + +def data_pin_set(length): + return cv.All( + [data_pin_validate], + cv.Length(min=length, max=length, msg=f"Exactly {length} data pins required"), + ) + + +def map_sequence(value): + """ + An initialisation sequence can be selected from one of the pre-defined sequences in init_sequences.py, + or can be a literal array of data bytes. + The format is a repeated sequence of [CMD, LEN, ] where is LEN bytes. + """ + if not isinstance(value, list): + value = cv.int_(value) + value = cv.one_of(*ST7701S_INITS)(value) + return ST7701S_INITS[value] + # value = cv.ensure_list(cv.uint8_t)(value) + data_length = len(value) + if data_length == 0: + raise cv.Invalid("Empty sequence") + value = cmd(*value) + return value + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ST7701S), + cv.Required(CONF_DIMENSIONS): cv.Any( + cv.dimensions, + cv.Schema( + { + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_, + } + ), + ), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + cv.Required(CONF_DATA_PINS): cv.Any( + data_pin_set(16), + cv.Schema( + { + cv.Required(CONF_RED): data_pin_set(5), + cv.Required(CONF_GREEN): data_pin_set(6), + cv.Required(CONF_BLUE): data_pin_set(5), + } + ), + ), + cv.Optional(CONF_INIT_SEQUENCE, default=1): cv.ensure_list( + map_sequence + ), + cv.Optional(CONF_COLOR_ORDER): cv.one_of( + *COLOR_ORDERS.keys(), upper=True + ), + cv.Optional(CONF_PCLK_FREQUENCY, default="16MHz"): cv.All( + cv.frequency, cv.Range(min=4e6, max=30e6) + ), + cv.Optional(CONF_PCLK_INVERTED, default=True): cv.boolean, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, + cv.Required(CONF_DE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_HSYNC_FRONT_PORCH, default=20): cv.int_, + cv.Optional(CONF_VSYNC_PULSE_WIDTH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_BACK_PORCH, default=10): cv.int_, + cv.Optional(CONF_VSYNC_FRONT_PORCH, default=10): cv.int_, + } + ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) + ), + only_on_variant(supported=[const.VARIANT_ESP32S3]), + cv.only_with_esp_idf, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await display.register_display(var, config) + await spi.register_spi_device(var, config) + + sequence = [] + for seq in config[CONF_INIT_SEQUENCE]: + sequence.extend(seq) + cg.add(var.set_init_sequence(sequence)) + cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) + cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) + cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) + cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) + cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH])) + cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) + cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) + 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] + 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) + # swap bytes to match big-endian format + dpins = dpins[8:16] + dpins[0:8] + else: + dpins = config[CONF_DATA_PINS] + for pin in dpins: + data_pin = await cg.gpio_pin_expression(pin) + cg.add(var.add_data_pin(data_pin, index)) + index += 1 + + if dc_pin := config.get(CONF_DC_PIN): + dc = await cg.gpio_pin_expression(dc_pin) + cg.add(var.set_dc_pin(dc)) + + if reset_pin := config.get(CONF_RESET_PIN): + reset = await cg.gpio_pin_expression(reset_pin) + cg.add(var.set_reset_pin(reset)) + + if transform := config.get(CONF_TRANSFORM): + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + + if CONF_DIMENSIONS in config: + dimensions = config[CONF_DIMENSIONS] + if isinstance(dimensions, dict): + cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) + cg.add( + var.set_offsets( + dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT] + ) + ) + else: + (width, height) = dimensions + cg.add(var.set_dimensions(width, height)) + + if lamb := config.get(CONF_LAMBDA): + lambda_ = await cg.process_lambda( + lamb, [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + + pin = await cg.gpio_pin_expression(config[CONF_DE_PIN]) + cg.add(var.set_de_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_PCLK_PIN]) + cg.add(var.set_pclk_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_HSYNC_PIN]) + cg.add(var.set_hsync_pin(pin)) + pin = await cg.gpio_pin_expression(config[CONF_VSYNC_PIN]) + cg.add(var.set_vsync_pin(pin)) diff --git a/esphome/components/st7701s/init_sequences.py b/esphome/components/st7701s/init_sequences.py new file mode 100644 index 000000000000..4786731c78f5 --- /dev/null +++ b/esphome/components/st7701s/init_sequences.py @@ -0,0 +1,363 @@ +# These are initialisation sequences for ST7701S displays. The contents are somewhat arcane. + + +def cmd(c, *args): + """ + Create a command sequence + :param c: The command (8 bit) + :param args: zero or more arguments (8 bit values) + :return: a list with the command, the argument count and the arguments + """ + return [c, len(args)] + list(args) + + +ST7701S_1_INIT = ( + cmd(0x01) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x10) + + cmd(0xC0, 0x3B, 0x00) + + cmd(0xC1, 0x0D, 0x02) + + cmd(0xC2, 0x31, 0x05) + + cmd(0xCD, 0x08) + + cmd( + 0xB0, + 0x00, + 0x11, + 0x18, + 0x0E, + 0x11, + 0x06, + 0x07, + 0x08, + 0x07, + 0x22, + 0x04, + 0x12, + 0x0F, + 0xAA, + 0x31, + 0x18, + ) + + cmd( + 0xB1, + 0x00, + 0x11, + 0x19, + 0x0E, + 0x12, + 0x07, + 0x08, + 0x08, + 0x08, + 0x22, + 0x04, + 0x11, + 0x11, + 0xA9, + 0x32, + 0x18, + ) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x11) + + cmd(0xB0, 0x60) + + cmd(0xB1, 0x32) + + cmd(0xB2, 0x07) + + cmd(0xB3, 0x80) + + cmd(0xB5, 0x49) + + cmd(0xB7, 0x85) + + cmd(0xB8, 0x21) + + cmd(0xC1, 0x78) + + cmd(0xC2, 0x78) + + cmd(0xE0, 0x00, 0x1B, 0x02) + + cmd(0xE1, 0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44) + + cmd(0xE2, 0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00) + + cmd(0xE3, 0x00, 0x00, 0x11, 0x11) + + cmd(0xE4, 0x44, 0x44) + + cmd( + 0xE5, + 0x0A, + 0xE9, + 0xD8, + 0xA0, + 0x0C, + 0xEB, + 0xD8, + 0xA0, + 0x0E, + 0xED, + 0xD8, + 0xA0, + 0x10, + 0xEF, + 0xD8, + 0xA0, + ) + + cmd(0xE6, 0x00, 0x00, 0x11, 0x11) + + cmd(0xE7, 0x44, 0x44) + + cmd( + 0xE8, + 0x09, + 0xE8, + 0xD8, + 0xA0, + 0x0B, + 0xEA, + 0xD8, + 0xA0, + 0x0D, + 0xEC, + 0xD8, + 0xA0, + 0x0F, + 0xEE, + 0xD8, + 0xA0, + ) + + cmd(0xEB, 0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40) + + cmd(0xEC, 0x3C, 0x00) + + cmd( + 0xED, + 0xAB, + 0x89, + 0x76, + 0x54, + 0x02, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x20, + 0x45, + 0x67, + 0x98, + 0xBA, + ) + + cmd(0xFF, 0x77, 0x01, 0x00, 0x00, 0x13) + + cmd(0xE5, 0xE4) + + cmd(0x3A, 0x60) +) + +# This is untested +ST7701S_7_INIT = ( + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x10, + ) + + cmd(0xC0, 0x3B, 0x00) + + cmd(0xC1, 0x0B, 0x02) + + cmd(0xC2, 0x07, 0x02) + + cmd(0xCC, 0x10) + + cmd(0xCD, 0x08) + + cmd( + 0xB0, + 0x00, + 0x11, + 0x16, + 0x0E, + 0x11, + 0x06, + 0x05, + 0x09, + 0x08, + 0x21, + 0x06, + 0x13, + 0x10, + 0x29, + 0x31, + 0x18, + ) + + cmd( + 0xB1, + 0x00, + 0x11, + 0x16, + 0x0E, + 0x11, + 0x07, + 0x05, + 0x09, + 0x09, + 0x21, + 0x05, + 0x13, + 0x11, + 0x2A, + 0x31, + 0x18, + ) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x11, + ) + + cmd(0xB0, 0x6D) + + cmd(0xB1, 0x37) + + cmd(0xB2, 0x81) + + cmd(0xB3, 0x80) + + cmd(0xB5, 0x43) + + cmd(0xB7, 0x85) + + cmd(0xB8, 0x20) + + cmd(0xC1, 0x78) + + cmd(0xC2, 0x78) + + cmd(0xD0, 0x88) + + cmd( + 0xE0, + 3, + 0x00, + 0x00, + 0x02, + ) + + cmd( + 0xE1, + 0x03, + 0xA0, + 0x00, + 0x00, + 0x04, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x20, + 0x20, + ) + + cmd( + 0xE2, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xE3, + 0x00, + 0x00, + 0x11, + 0x00, + ) + + cmd(0xE4, 0x22, 0x00) + + cmd( + 0xE5, + 0x05, + 0xEC, + 0xA0, + 0xA0, + 0x07, + 0xEE, + 0xA0, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xE6, + 0x00, + 0x00, + 0x11, + 0x00, + ) + + cmd(0xE7, 0x22, 0x00) + + cmd( + 0xE8, + 0x06, + 0xED, + 0xA0, + 0xA0, + 0x08, + 0xEF, + 0xA0, + 0xA0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xEB, + 0x00, + 0x00, + 0x40, + 0x40, + 0x00, + 0x00, + 0x00, + ) + + cmd( + 0xED, + 0xFF, + 0xFF, + 0xFF, + 0xBA, + 0x0A, + 0xBF, + 0x45, + 0xFF, + 0xFF, + 0x54, + 0xFB, + 0xA0, + 0xAB, + 0xFF, + 0xFF, + 0xFF, + ) + + cmd( + 0xEF, + 0x10, + 0x0D, + 0x04, + 0x08, + 0x3F, + 0x1F, + ) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x13, + ) + + cmd(0xEF, 0x08) + + cmd( + 0xFF, + 0x77, + 0x01, + 0x00, + 0x00, + 0x00, + ) + + cmd(0x3A, 0x66) +) + +ST7701S_INITS = { + 1: ST7701S_1_INIT, + # 7: ST7701S_7_INIT, +} diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp new file mode 100644 index 000000000000..43d8653709bb --- /dev/null +++ b/esphome/components/st7701s/st7701s.cpp @@ -0,0 +1,180 @@ +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "st7701s.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace st7701s { + +void ST7701S::setup() { + esph_log_config(TAG, "Setting up ST7701S"); + this->spi_setup(); + esp_lcd_rgb_panel_config_t config{}; + config.flags.fb_in_psram = 1; + config.timings.h_res = this->width_; + config.timings.v_res = this->height_; + config.timings.hsync_pulse_width = this->hsync_pulse_width_; + config.timings.hsync_back_porch = this->hsync_back_porch_; + config.timings.hsync_front_porch = this->hsync_front_porch_; + config.timings.vsync_pulse_width = this->vsync_pulse_width_; + config.timings.vsync_back_porch = this->vsync_back_porch_; + config.timings.vsync_front_porch = this->vsync_front_porch_; + config.timings.flags.pclk_active_neg = this->pclk_inverted_; + config.timings.pclk_hz = this->pclk_frequency_; + config.clk_src = LCD_CLK_SRC_PLL160M; + config.sram_trans_align = 64; + config.psram_trans_align = 64; + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) { + config.data_gpio_nums[i] = this->data_pins_[i]->get_pin(); + } + config.data_width = data_pin_count; + config.disp_gpio_num = -1; + config.hsync_gpio_num = this->hsync_pin_->get_pin(); + config.vsync_gpio_num = this->vsync_pin_->get_pin(); + config.de_gpio_num = this->de_pin_->get_pin(); + config.pclk_gpio_num = this->pclk_pin_->get_pin(); + esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); + if (err != ESP_OK) { + esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); + this->write_init_sequence_(); + esph_log_config(TAG, "ST7701S setup complete"); +} + +void ST7701S::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) { + if (w <= 0 || h <= 0) + return; + // if color mapping is required, pass the buck. + // note that endianness is not considered here - it is assumed to match! + if (bitness != display::COLOR_BITNESS_565) { + return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + } + x_start += this->offset_x_; + y_start += this->offset_y_; + esp_err_t err; + // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr); + } else { + // draw line by line + auto stride = x_offset + w + x_pad; + for (int y = 0; y != h; y++) { + err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, + ptr + ((y + y_offset) * stride + x_offset) * 2); + if (err != ESP_OK) + break; + } + } + if (err != ESP_OK) + esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); +} + +void ST7701S::draw_pixel_at(int x, int y, Color color) { + if (!this->get_clipping().inside(x, y)) + return; // NOLINT + + switch (this->rotation_) { + case display::DISPLAY_ROTATION_0_DEGREES: + break; + case display::DISPLAY_ROTATION_90_DEGREES: + std::swap(x, y); + x = this->width_ - x - 1; + break; + case display::DISPLAY_ROTATION_180_DEGREES: + x = this->width_ - x - 1; + y = this->height_ - y - 1; + break; + case display::DISPLAY_ROTATION_270_DEGREES: + std::swap(x, y); + y = this->height_ - y - 1; + break; + } + auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color)); + + this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true, + 0, 0, 0); + App.feed_wdt(); +} + +void ST7701S::write_command_(uint8_t value) { + this->enable(); + if (this->dc_pin_ == nullptr) { + this->write(value, 9); + } else { + this->dc_pin_->digital_write(false); + this->write_byte(value); + this->dc_pin_->digital_write(true); + } + this->disable(); +} + +void ST7701S::write_data_(uint8_t value) { + this->enable(); + if (this->dc_pin_ == nullptr) { + this->write(value | 0x100, 9); + } else { + this->dc_pin_->digital_write(true); + this->write_byte(value); + } + this->disable(); +} + +/** + * this relies upon the init sequence being well-formed, which is guaranteed by the Python init code. + */ + +void ST7701S::write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes) { + this->write_command_(cmd); + while (len-- != 0) + this->write_data_(*bytes++); +} + +void ST7701S::write_init_sequence_() { + for (size_t i = 0; i != this->init_sequence_.size();) { + uint8_t cmd = this->init_sequence_[i++]; + size_t len = this->init_sequence_[i++]; + this->write_sequence_(cmd, len, &this->init_sequence_[i]); + i += len; + esph_log_v(TAG, "Command %X, %d bytes", cmd, len); + if (cmd == SW_RESET_CMD) + delay(6); + } + // st7701 does not appear to support axis swapping + this->write_sequence_(CMD2_BKSEL, sizeof(CMD2_BK0), CMD2_BK0); + this->write_command_(SDIR_CMD); // this is in the BK0 command set + this->write_data_(this->mirror_x_ ? 0x04 : 0x00); + uint8_t val = this->color_mode_ == display::COLOR_ORDER_BGR ? 0x08 : 0x00; + if (this->mirror_y_) + val |= 0x10; + this->write_command_(MADCTL_CMD); + this->write_data_(val); + esph_log_d(TAG, "write MADCTL %X", val); + this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); + this->set_timeout(120, [this] { + this->write_command_(SLEEP_OUT); + this->write_command_(DISPLAY_ON); + }); +} + +void ST7701S::dump_config() { + ESP_LOGCONFIG("", "ST7701S RGB LCD"); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" DE Pin: ", this->de_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); + for (size_t i = 0; i != data_pin_count; i++) + ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str()); + ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); +} + +} // namespace st7701s +} // namespace esphome +#endif // USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/st7701s/st7701s.h b/esphome/components/st7701s/st7701s.h new file mode 100644 index 000000000000..2328bca965a8 --- /dev/null +++ b/esphome/components/st7701s/st7701s.h @@ -0,0 +1,115 @@ +// +// Created by Clyde Stubbs on 29/10/2023. +// +#pragma once + +// only applicable on ESP32-S3 +#ifdef USE_ESP32_VARIANT_ESP32S3 +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display.h" +#include "esp_lcd_panel_ops.h" + +#include "esp_lcd_panel_rgb.h" + +namespace esphome { +namespace st7701s { + +constexpr static const char *const TAG = "display.st7701s"; +const uint8_t SW_RESET_CMD = 0x01; +const uint8_t SLEEP_OUT = 0x11; +const uint8_t SDIR_CMD = 0xC7; +const uint8_t MADCTL_CMD = 0x36; +const uint8_t INVERT_OFF = 0x20; +const uint8_t INVERT_ON = 0x21; +const uint8_t DISPLAY_ON = 0x29; +const uint8_t CMD2_BKSEL = 0xFF; +const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10}; + +class ST7701S : public display::Display, + public spi::SPIDevice { + public: + void update() override { this->do_update_(); } + void setup() 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; + + display::ColorOrder get_color_mode() { return this->color_mode_; } + void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } + void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } + + void add_data_pin(InternalGPIOPin *data_pin, size_t index) { this->data_pins_[index] = data_pin; }; + void set_de_pin(InternalGPIOPin *de_pin) { this->de_pin_ = de_pin; } + void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; } + void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; } + void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; } + void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void set_width(uint16_t width) { this->width_ = width; } + void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } + void set_pclk_inverted(bool inverted) { this->pclk_inverted_ = inverted; } + void set_dimensions(uint16_t width, uint16_t height) { + this->width_ = width; + this->height_ = height; + } + int get_width() override { return this->width_; } + int get_height() override { return this->height_; } + void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } + void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } + void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } + void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } + void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } + void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } + void set_init_sequence(const std::vector &init_sequence) { this->init_sequence_ = init_sequence; } + void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; } + void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; } + void set_offsets(int16_t offset_x, int16_t offset_y) { + this->offset_x_ = offset_x; + this->offset_y_ = offset_y; + } + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + int get_width_internal() override { return this->width_; } + int get_height_internal() override { return this->height_; } + void dump_config() override; + void draw_pixel_at(int x, int y, Color color) override; + + // this will be horribly slow. + protected: + void write_command_(uint8_t value); + void write_data_(uint8_t value); + void write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes); + void write_init_sequence_(); + + InternalGPIOPin *de_pin_{nullptr}; + InternalGPIOPin *pclk_pin_{nullptr}; + InternalGPIOPin *hsync_pin_{nullptr}; + InternalGPIOPin *vsync_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + GPIOPin *dc_pin_{nullptr}; + InternalGPIOPin *data_pins_[16] = {}; + uint16_t hsync_pulse_width_ = 10; + uint16_t hsync_back_porch_ = 10; + uint16_t hsync_front_porch_ = 20; + uint16_t vsync_pulse_width_ = 10; + uint16_t vsync_back_porch_ = 10; + uint16_t vsync_front_porch_ = 10; + std::vector init_sequence_; + uint32_t pclk_frequency_ = 16 * 1000 * 1000; + bool pclk_inverted_{true}; + + bool invert_colors_{}; + display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; + size_t width_{}; + size_t height_{}; + int16_t offset_x_{0}; + int16_t offset_y_{0}; + bool mirror_x_{}; + bool mirror_y_{}; + + esp_lcd_panel_handle_t handle_{}; +}; + +} // namespace st7701s +} // namespace esphome +#endif diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index 652d31662d72..d5bb2fa3d65d 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -10,6 +10,7 @@ CONF_MODEL, CONF_RESET_PIN, CONF_PAGES, + CONF_INVERT_COLORS, ) from . import st7735_ns @@ -23,7 +24,6 @@ CONF_COL_START = "col_start" CONF_EIGHT_BIT_COLOR = "eight_bit_color" CONF_USE_BGR = "use_bgr" -CONF_INVERT_COLORS = "invert_colors" SPIST7735 = st7735_ns.class_( "ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice @@ -69,7 +69,6 @@ async def setup_st7735(var, config): - await cg.register_component(var, config) await display.register_display(var, config) if CONF_RESET_PIN in config: diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 3baa9b083a45..37fe673962ba 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -32,8 +32,7 @@ enum ST7735Model { ST7735_INITR_18REDTAB = INITR_18REDTAB }; -class ST7735 : public PollingComponent, - public display::DisplayBuffer, +class ST7735 : public display::DisplayBuffer, public spi::SPIDevice { public: diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index 16c1e790bd0d..04dce2cf6cbb 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -12,12 +12,14 @@ CONF_RESET_PIN, CONF_WIDTH, CONF_POWER_SUPPLY, + CONF_ROTATION, + CONF_CS_PIN, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, ) from . import st7789v_ns CONF_EIGHTBITCOLOR = "eightbitcolor" -CONF_OFFSET_HEIGHT = "offset_height" -CONF_OFFSET_WIDTH = "offset_width" CODEOWNERS = ["@kbx81"] @@ -26,48 +28,119 @@ ST7789V = st7789v_ns.class_( "ST7789V", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer ) -ST7789VRef = ST7789V.operator("ref") -ST7789VModel = st7789v_ns.enum("ST7789VModel") + +MODEL_PRESETS = "model_presets" +REQUIRE_PS = "require_ps" + + +def model_spec(require_ps=False, presets=None): + if presets is None: + presets = {} + return {MODEL_PRESETS: presets, REQUIRE_PS: require_ps} + MODELS = { - "TTGO_TDISPLAY_135X240": ST7789VModel.ST7789V_MODEL_TTGO_TDISPLAY_135_240, - "ADAFRUIT_FUNHOUSE_240X240": ST7789VModel.ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240, - "ADAFRUIT_RR_280X240": ST7789VModel.ST7789V_MODEL_ADAFRUIT_RR_280_240, - "ADAFRUIT_S2_TFT_FEATHER_240X135": ST7789VModel.ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135, - "CUSTOM": ST7789VModel.ST7789V_MODEL_CUSTOM, + "TTGO_TDISPLAY_135X240": model_spec( + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 135, + CONF_OFFSET_HEIGHT: 52, + CONF_OFFSET_WIDTH: 40, + CONF_CS_PIN: "GPIO5", + CONF_DC_PIN: "GPIO16", + CONF_RESET_PIN: "GPIO23", + CONF_BACKLIGHT_PIN: "GPIO4", + } + ), + "ADAFRUIT_FUNHOUSE_240X240": model_spec( + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 240, + CONF_OFFSET_HEIGHT: 0, + CONF_OFFSET_WIDTH: 0, + CONF_CS_PIN: "GPIO40", + CONF_DC_PIN: "GPIO39", + CONF_RESET_PIN: "GPIO41", + } + ), + "ADAFRUIT_RR_280X240": model_spec( + presets={ + CONF_HEIGHT: 280, + CONF_WIDTH: 240, + CONF_OFFSET_HEIGHT: 0, + CONF_OFFSET_WIDTH: 20, + } + ), + "ADAFRUIT_S2_TFT_FEATHER_240X135": model_spec( + require_ps=True, + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 135, + CONF_OFFSET_HEIGHT: 52, + CONF_OFFSET_WIDTH: 40, + CONF_CS_PIN: "GPIO7", + CONF_DC_PIN: "GPIO39", + CONF_RESET_PIN: "GPIO40", + CONF_BACKLIGHT_PIN: "GPIO45", + }, + ), + "LILYGO_T-EMBED_170X320": model_spec( + presets={ + CONF_HEIGHT: 320, + CONF_WIDTH: 170, + CONF_OFFSET_HEIGHT: 35, + CONF_OFFSET_WIDTH: 0, + CONF_ROTATION: 270, + CONF_CS_PIN: "GPIO10", + CONF_DC_PIN: "GPIO13", + CONF_RESET_PIN: "GPIO9", + CONF_BACKLIGHT_PIN: "GPIO15", + } + ), + "WAVESHARE_1.47IN_172X320": model_spec( + presets={ + CONF_HEIGHT: 320, + CONF_WIDTH: 172, + CONF_OFFSET_HEIGHT: 34, + CONF_OFFSET_WIDTH: 0, + CONF_ROTATION: 90, + CONF_CS_PIN: "GPIO21", + CONF_DC_PIN: "GPIO22", + CONF_RESET_PIN: "GPIO23", + CONF_BACKLIGHT_PIN: "GPIO4", + } + ), + "CUSTOM": model_spec(), } -ST7789V_MODEL = cv.enum(MODELS, upper=True, space="_") - def validate_st7789v(config): - if config[CONF_MODEL].upper() == "CUSTOM" and ( - CONF_HEIGHT not in config - or CONF_WIDTH not in config - or CONF_OFFSET_HEIGHT not in config - or CONF_OFFSET_WIDTH not in config - ): - raise cv.Invalid( - f'{CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} and {CONF_OFFSET_WIDTH} must be specified when {CONF_MODEL} is "CUSTOM"' - ) - - if config[CONF_MODEL].upper() != "CUSTOM" and ( - CONF_HEIGHT in config - or CONF_WIDTH in config - or CONF_OFFSET_HEIGHT in config - or CONF_OFFSET_WIDTH in config - ): + model_data = MODELS[config[CONF_MODEL]] + presets = model_data[MODEL_PRESETS] + for key, value in presets.items(): + if key not in config: + if key.endswith("pin"): + # All pins are output. + value = pins.gpio_output_pin_schema(value) + config[key] = value + + if model_data[REQUIRE_PS] and CONF_POWER_SUPPLY not in config: raise cv.Invalid( - f'Do not specify {CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} or {CONF_OFFSET_WIDTH} when using {CONF_MODEL} that is not "CUSTOM"' + f'{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is {config[CONF_MODEL]}"' ) if ( - config[CONF_MODEL].upper() == "ADAFRUIT_S2_TFT_FEATHER_240X135" - and CONF_POWER_SUPPLY not in config + CONF_OFFSET_WIDTH not in config + or CONF_OFFSET_HEIGHT not in config + or CONF_HEIGHT not in config + or CONF_WIDTH not in config ): raise cv.Invalid( - f'{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is "ADAFRUIT_S2_TFT_FEATHER_240X135"' + f"{CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} and {CONF_OFFSET_WIDTH} must all be specified" ) + if CONF_DC_PIN not in config or CONF_RESET_PIN not in config: + raise cv.Invalid(f"both {CONF_DC_PIN} and {CONF_RESET_PIN} must be specified") + return config @@ -75,10 +148,13 @@ def validate_st7789v(config): display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ST7789V), - cv.Required(CONF_MODEL): ST7789V_MODEL, - cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_MODEL): cv.one_of(*MODELS.keys(), upper=True, space="_"), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BACKLIGHT_PIN): cv.Any( + cv.boolean, + pins.gpio_output_pin_schema, + ), cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, cv.Optional(CONF_HEIGHT): cv.int_, @@ -95,17 +171,15 @@ def validate_st7789v(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) await spi.register_spi_device(var, config) - cg.add(var.set_model(config[CONF_MODEL])) + cg.add(var.set_model_str(config[CONF_MODEL])) - if config[CONF_MODEL].upper() == "CUSTOM": - cg.add(var.set_height(config[CONF_HEIGHT])) - cg.add(var.set_width(config[CONF_WIDTH])) - cg.add(var.set_offset_height(config[CONF_OFFSET_HEIGHT])) - cg.add(var.set_offset_width(config[CONF_OFFSET_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_offset_height(config[CONF_OFFSET_HEIGHT])) + cg.add(var.set_offset_width(config[CONF_OFFSET_WIDTH])) cg.add(var.set_eightbitcolor(config[CONF_EIGHTBITCOLOR])) @@ -115,7 +189,7 @@ async def to_code(config): reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) cg.add(var.set_reset_pin(reset)) - if CONF_BACKLIGHT_PIN in config: + if CONF_BACKLIGHT_PIN in config and config[CONF_BACKLIGHT_PIN]: bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) cg.add(var.set_backlight_pin(bl)) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 0e7c9b9123e8..74c7a4e9e32f 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -5,6 +5,7 @@ namespace esphome { namespace st7789v { static const char *const TAG = "st7789v"; +static const size_t TEMP_BUFFER_SIZE = 128; void ST7789V::setup() { ESP_LOGCONFIG(TAG, "Setting up SPI ST7789V..."); @@ -121,17 +122,18 @@ void ST7789V::setup() { void ST7789V::dump_config() { LOG_DISPLAY("", "SPI ST7789V", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - if (this->model_ == ST7789V_MODEL_CUSTOM) { - ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_height_); - ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_width_); - } + ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_height_); + ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_width_); ESP_LOGCONFIG(TAG, " 8-bit color mode: %s", YESNO(this->eightbitcolor_)); 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 @@ -144,42 +146,7 @@ void ST7789V::update() { this->write_display_data(); } -void ST7789V::set_model(ST7789VModel model) { - this->model_ = model; - - switch (this->model_) { - case ST7789V_MODEL_TTGO_TDISPLAY_135_240: - this->height_ = 240; - this->width_ = 135; - this->offset_height_ = 52; - this->offset_width_ = 40; - break; - - case ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240: - this->height_ = 240; - this->width_ = 240; - this->offset_height_ = 0; - this->offset_width_ = 0; - break; - - case ST7789V_MODEL_ADAFRUIT_RR_280_240: - this->height_ = 280; - this->width_ = 240; - this->offset_height_ = 0; - this->offset_width_ = 20; - break; - - case ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135: - this->height_ = 240; - this->width_ = 135; - this->offset_height_ = 52; - this->offset_width_ = 40; - break; - - default: - break; - } -} +void ST7789V::set_model_str(const char *model_str) { this->model_str_ = model_str; } void ST7789V::write_display_data() { uint16_t x1 = this->offset_height_; @@ -205,15 +172,23 @@ void ST7789V::write_display_data() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { + uint8_t temp_buffer[TEMP_BUFFER_SIZE]; + size_t temp_index = 0; for (int line = 0; line < this->get_buffer_length_(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color = display::ColorUtil::color_to_565( display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true)); - this->write_byte((color >> 8) & 0xff); - this->write_byte(color & 0xff); + temp_buffer[temp_index++] = (uint8_t) (color >> 8); + temp_buffer[temp_index++] = (uint8_t) color; + if (temp_index == TEMP_BUFFER_SIZE) { + this->write_array(temp_buffer, TEMP_BUFFER_SIZE); + temp_index = 0; + } } } + if (temp_index != 0) + this->write_array(temp_buffer, temp_index); } else { this->write_array(this->buffer_, this->get_buffer_length_()); } @@ -228,9 +203,10 @@ void ST7789V::init_reset_() { delay(1); // Trigger Reset this->reset_pin_->digital_write(false); - delay(10); + delay(1); // Wake up this->reset_pin_->digital_write(true); + delay(5); } } @@ -329,20 +305,5 @@ void HOT ST7789V::draw_absolute_pixel_internal(int x, int y, Color color) { } } -const char *ST7789V::model_str_() { - switch (this->model_) { - case ST7789V_MODEL_TTGO_TDISPLAY_135_240: - return "TTGO T-Display 135x240"; - case ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240: - return "Adafruit Funhouse 240x240"; - case ST7789V_MODEL_ADAFRUIT_RR_280_240: - return "Adafruit Round-Rectangular 280x240"; - case ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135: - return "Adafruit ESP32-S2 TFT Feather"; - default: - return "Custom"; - } -} - } // namespace st7789v } // namespace esphome diff --git a/esphome/components/st7789v/st7789v.h b/esphome/components/st7789v/st7789v.h index ccbe50cf8553..29ea3159791b 100644 --- a/esphome/components/st7789v/st7789v.h +++ b/esphome/components/st7789v/st7789v.h @@ -10,14 +10,6 @@ namespace esphome { namespace st7789v { -enum ST7789VModel { - ST7789V_MODEL_TTGO_TDISPLAY_135_240, - ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240, - ST7789V_MODEL_ADAFRUIT_RR_280_240, - ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135, - ST7789V_MODEL_CUSTOM -}; - static const uint8_t ST7789_NOP = 0x00; // No Operation static const uint8_t ST7789_SWRESET = 0x01; // Software Reset static const uint8_t ST7789_RDDID = 0x04; // Read Display ID @@ -115,12 +107,11 @@ static const uint8_t ST7789_MADCTL_GS = 0x01; static const uint8_t ST7789_MADCTL_COLOR_ORDER = ST7789_MADCTL_BGR; -class ST7789V : public PollingComponent, - public display::DisplayBuffer, - public spi::SPIDevice { +class ST7789V : public display::DisplayBuffer, + public spi::SPIDevice { public: - void set_model(ST7789VModel model); + void set_model_str(const char *model_str); void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_backlight_pin(GPIOPin *backlight_pin) { this->backlight_pin_ = backlight_pin; } @@ -146,7 +137,6 @@ class ST7789V : public PollingComponent, display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } protected: - ST7789VModel model_{ST7789V_MODEL_TTGO_TDISPLAY_135_240}; GPIOPin *dc_pin_{nullptr}; GPIOPin *reset_pin_{nullptr}; GPIOPin *backlight_pin_{nullptr}; @@ -175,7 +165,7 @@ class ST7789V : public PollingComponent, void draw_absolute_pixel_internal(int x, int y, Color color) override; - const char *model_str_(); + const char *model_str_; }; } // namespace st7789v diff --git a/esphome/components/st7920/display.py b/esphome/components/st7920/display.py index 9b544fa64453..1267e2ad632a 100644 --- a/esphome/components/st7920/display.py +++ b/esphome/components/st7920/display.py @@ -28,7 +28,6 @@ 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) if CONF_LAMBDA in config: diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h index c00b7cf5e08d..c9fdad454d37 100644 --- a/esphome/components/st7920/st7920.h +++ b/esphome/components/st7920/st7920.h @@ -11,8 +11,7 @@ class ST7920; using st7920_writer_t = std::function; -class ST7920 : public PollingComponent, - public display::DisplayBuffer, +class ST7920 : public display::DisplayBuffer, public spi::SPIDevice { public: diff --git a/esphome/components/status_led/light/status_led_light.cpp b/esphome/components/status_led/light/status_led_light.cpp index b47d1f5bd002..549024c4df45 100644 --- a/esphome/components/status_led/light/status_led_light.cpp +++ b/esphome/components/status_led/light/status_led_light.cpp @@ -1,6 +1,7 @@ #include "status_led_light.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include namespace esphome { namespace status_led { @@ -11,7 +12,7 @@ void StatusLEDLightOutput::loop() { uint32_t new_state = App.get_app_state() & STATUS_LED_MASK; if (new_state != this->last_app_state_) { - ESP_LOGV(TAG, "New app state 0x%08X", new_state); + ESP_LOGV(TAG, "New app state 0x%08" PRIX32, new_state); } if ((new_state & STATUS_LED_ERROR) != 0u) { diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index 5af808b6e7c6..a533bc1d8720 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -30,7 +30,7 @@ void STS3XComponent::setup() { return; } uint32_t serial_number = (uint32_t(raw_serial_number[0]) << 16); - ESP_LOGV(TAG, " Serial Number: 0x%08X", serial_number); + ESP_LOGV(TAG, " Serial Number: 0x%08" PRIX32, serial_number); } void STS3XComponent::dump_config() { ESP_LOGCONFIG(TAG, "STS3x:"); diff --git a/esphome/components/sts3x/sts3x.h b/esphome/components/sts3x/sts3x.h index 261033efad2c..8f806a347172 100644 --- a/esphome/components/sts3x/sts3x.h +++ b/esphome/components/sts3x/sts3x.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" +#include + namespace esphome { namespace sts3x { diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index ef368015b1fa..2d3a79ccae62 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -116,7 +116,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals if CONF_SUBSTITUTIONS not in config and not command_line_substitutions: return - substitutions = config[CONF_SUBSTITUTIONS] + substitutions = config.get(CONF_SUBSTITUTIONS) if substitutions is None: substitutions = command_line_substitutions elif command_line_substitutions: diff --git a/esphome/components/sun_gtil2/__init__.py b/esphome/components/sun_gtil2/__init__.py new file mode 100644 index 000000000000..f4d46fade712 --- /dev/null +++ b/esphome/components/sun_gtil2/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@Mat931"] +MULTI_CONF = True +DEPENDENCIES = ["uart"] + +CONF_SUN_GTIL2_ID = "sun_gtil2_id" + +sun_gtil2_ns = cg.esphome_ns.namespace("sun_gtil2") + +SunGTIL2Component = sun_gtil2_ns.class_("SunGTIL2", cg.Component, uart.UARTDevice) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SunGTIL2Component), + } +).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) diff --git a/esphome/components/sun_gtil2/sensor.py b/esphome/components/sun_gtil2/sensor.py new file mode 100644 index 000000000000..6d1be9c74029 --- /dev/null +++ b/esphome/components/sun_gtil2/sensor.py @@ -0,0 +1,87 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ICON_FLASH, + UNIT_VOLT, + ICON_THERMOMETER, + UNIT_WATT, + UNIT_CELSIUS, + CONF_TEMPERATURE, +) +from . import SunGTIL2Component, CONF_SUN_GTIL2_ID + +CONF_AC_VOLTAGE = "ac_voltage" +CONF_DC_VOLTAGE = "dc_voltage" +CONF_AC_POWER = "ac_power" +CONF_DC_POWER = "dc_power" +CONF_LIMITER_POWER = "limiter_power" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component), + cv.Optional(CONF_AC_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_DC_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_AC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_DC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_LIMITER_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID]) + if ac_voltage_config := config.get(CONF_AC_VOLTAGE): + sens = await sensor.new_sensor(ac_voltage_config) + cg.add(hub.set_ac_voltage(sens)) + if dc_voltage_config := config.get(CONF_DC_VOLTAGE): + sens = await sensor.new_sensor(dc_voltage_config) + cg.add(hub.set_dc_voltage(sens)) + if ac_power_config := config.get(CONF_AC_POWER): + sens = await sensor.new_sensor(ac_power_config) + cg.add(hub.set_ac_power(sens)) + if dc_power_config := config.get(CONF_DC_POWER): + sens = await sensor.new_sensor(dc_power_config) + cg.add(hub.set_dc_power(sens)) + if limiter_power_config := config.get(CONF_LIMITER_POWER): + sens = await sensor.new_sensor(limiter_power_config) + cg.add(hub.set_limiter_power(sens)) + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(hub.set_temperature(sens)) diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp new file mode 100644 index 000000000000..1653f937dd0e --- /dev/null +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -0,0 +1,135 @@ +#include "sun_gtil2.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sun_gtil2 { + +static const char *const TAG = "sun_gtil2"; + +static const double NTC_A = 0.0011591051055979914; +static const double NTC_B = 0.00022878183547845582; +static const double NTC_C = 1.0396291358342124e-07; +static const float PULLUP_RESISTANCE = 10000.0f; +static const uint16_t ADC_MAX = 1023; // ADC of the inverter controller, not the ESP + +struct SunGTIL2Message { + uint16_t sync; + uint8_t ac_waveform[277]; + uint8_t frequency; + uint16_t ac_voltage; + uint16_t ac_power; + uint16_t dc_voltage; + uint8_t state; + uint8_t unknown1; + uint8_t unknown2; + uint8_t unknown3; + uint8_t limiter_mode; + uint8_t unknown4; + uint16_t temperature; + uint32_t limiter_power; + uint16_t dc_power; + char serial_number[10]; + uint8_t unknown5; + uint8_t end[39]; +} __attribute__((packed)); + +static const uint16_t MESSAGE_SIZE = sizeof(SunGTIL2Message); + +static_assert(MESSAGE_SIZE == 350, "Expected the message size to be 350 bytes"); + +void SunGTIL2::setup() { this->rx_message_.reserve(MESSAGE_SIZE); } + +void SunGTIL2::loop() { + while (this->available()) { + uint8_t c; + this->read_byte(&c); + this->handle_char_(c); + } +} + +std::string SunGTIL2::state_to_string_(uint8_t state) { + switch (state) { + case 0x02: + return "Starting voltage too low"; + case 0x07: + return "Working"; + default: + return str_sprintf("Unknown (0x%02x)", state); + } +} + +float SunGTIL2::calculate_temperature_(uint16_t adc_value) { + if (adc_value >= ADC_MAX || adc_value == 0) { + return NAN; + } + + float ntc_resistance = PULLUP_RESISTANCE / ((static_cast(ADC_MAX) / adc_value) - 1.0f); + double lr = log(double(ntc_resistance)); + double v = NTC_A + NTC_B * lr + NTC_C * lr * lr * lr; + return float(1.0 / v - 273.15); +} + +void SunGTIL2::handle_char_(uint8_t c) { + if (this->rx_message_.size() > 1 || c == 0x07) { + this->rx_message_.push_back(c); + } else if (!this->rx_message_.empty()) { + this->rx_message_.clear(); + } + if (this->rx_message_.size() < MESSAGE_SIZE) { + return; + } + + SunGTIL2Message msg; + memcpy(&msg, this->rx_message_.data(), MESSAGE_SIZE); + this->rx_message_.clear(); + + if (!((msg.end[0] == 0) && (msg.end[38] == 0x08))) + return; + + ESP_LOGVV(TAG, "Frequency raw value: %02x", msg.frequency); + ESP_LOGVV(TAG, "Unknown values: %02x %02x %02x %02x %02x", msg.unknown1, msg.unknown2, msg.unknown3, msg.unknown4, + msg.unknown5); + +#ifdef USE_SENSOR + if (this->ac_voltage_ != nullptr) + this->ac_voltage_->publish_state(__builtin_bswap16(msg.ac_voltage) / 10.0f); + if (this->dc_voltage_ != nullptr) + this->dc_voltage_->publish_state(__builtin_bswap16(msg.dc_voltage) / 8.0f); + if (this->ac_power_ != nullptr) + this->ac_power_->publish_state(__builtin_bswap16(msg.ac_power) / 10.0f); + if (this->dc_power_ != nullptr) + this->dc_power_->publish_state(__builtin_bswap16(msg.dc_power) / 10.0f); + if (this->limiter_power_ != nullptr) + this->limiter_power_->publish_state(static_cast(__builtin_bswap32(msg.limiter_power)) / 10.0f); + if (this->temperature_ != nullptr) + this->temperature_->publish_state(calculate_temperature_(__builtin_bswap16(msg.temperature))); +#endif +#ifdef USE_TEXT_SENSOR + if (this->state_ != nullptr) { + this->state_->publish_state(this->state_to_string_(msg.state)); + } + if (this->serial_number_ != nullptr) { + std::string serial_number; + serial_number.assign(msg.serial_number, 10); + this->serial_number_->publish_state(serial_number); + } +#endif +} + +void SunGTIL2::dump_config() { +#ifdef USE_SENSOR + LOG_SENSOR("", "AC Voltage", this->ac_voltage_); + LOG_SENSOR("", "DC Voltage", this->dc_voltage_); + LOG_SENSOR("", "AC Power", this->ac_power_); + LOG_SENSOR("", "DC Power", this->dc_power_); + LOG_SENSOR("", "Limiter Power", this->limiter_power_); + LOG_SENSOR("", "Temperature", this->temperature_); +#endif +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR("", "State", this->state_); + LOG_TEXT_SENSOR("", "Serial Number", this->serial_number_); +#endif +} + +} // namespace sun_gtil2 +} // namespace esphome diff --git a/esphome/components/sun_gtil2/sun_gtil2.h b/esphome/components/sun_gtil2/sun_gtil2.h new file mode 100644 index 000000000000..0c29ae695d25 --- /dev/null +++ b/esphome/components/sun_gtil2/sun_gtil2.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace sun_gtil2 { + +class SunGTIL2 : public Component, public uart::UARTDevice { + public: + float get_setup_priority() const override { return setup_priority::LATE; } + void setup() override; + void loop() override; + void dump_config() override; + +#ifdef USE_SENSOR + void set_ac_voltage(sensor::Sensor *sensor) { ac_voltage_ = sensor; } + void set_dc_voltage(sensor::Sensor *sensor) { dc_voltage_ = sensor; } + void set_ac_power(sensor::Sensor *sensor) { ac_power_ = sensor; } + void set_dc_power(sensor::Sensor *sensor) { dc_power_ = sensor; } + void set_limiter_power(sensor::Sensor *sensor) { limiter_power_ = sensor; } + void set_temperature(sensor::Sensor *sensor) { temperature_ = sensor; } +#endif +#ifdef USE_TEXT_SENSOR + void set_state(text_sensor::TextSensor *text_sensor) { state_ = text_sensor; } + void set_serial_number(text_sensor::TextSensor *text_sensor) { serial_number_ = text_sensor; } +#endif + + protected: + std::string state_to_string_(uint8_t state); +#ifdef USE_SENSOR + sensor::Sensor *ac_voltage_{nullptr}; + sensor::Sensor *dc_voltage_{nullptr}; + sensor::Sensor *ac_power_{nullptr}; + sensor::Sensor *dc_power_{nullptr}; + sensor::Sensor *limiter_power_{nullptr}; + sensor::Sensor *temperature_{nullptr}; +#endif +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *state_{nullptr}; + text_sensor::TextSensor *serial_number_{nullptr}; +#endif + + float calculate_temperature_(uint16_t adc_value); + void handle_char_(uint8_t c); + std::vector rx_message_; +}; + +} // namespace sun_gtil2 +} // namespace esphome diff --git a/esphome/components/sun_gtil2/text_sensor.py b/esphome/components/sun_gtil2/text_sensor.py new file mode 100644 index 000000000000..d9d3e3ca66b8 --- /dev/null +++ b/esphome/components/sun_gtil2/text_sensor.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_STATE +from . import SunGTIL2Component, CONF_SUN_GTIL2_ID + +CONF_SERIAL_NUMBER = "serial_number" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_SUN_GTIL2_ID): cv.use_id(SunGTIL2Component), + cv.Optional(CONF_STATE): text_sensor.text_sensor_schema( + text_sensor.TextSensor + ), + cv.Optional(CONF_SERIAL_NUMBER): text_sensor.text_sensor_schema( + text_sensor.TextSensor + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SUN_GTIL2_ID]) + if state_config := config.get(CONF_STATE): + sens = await text_sensor.new_text_sensor(state_config) + cg.add(hub.set_state(sens)) + if serial_number_config := config.get(CONF_SERIAL_NUMBER): + sens = await text_sensor.new_text_sensor(serial_number_config) + cg.add(hub.set_serial_number(sens)) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 21cbe3dfe423..e997ec7ca596 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -138,8 +138,8 @@ def switch_schema( async def setup_switch_core_(var, config): await setup_entity(var, config) - if CONF_INVERTED in config: - cg.add(var.set_inverted(config[CONF_INVERTED])) + if (inverted := config.get(CONF_INVERTED)) is not None: + cg.add(var.set_inverted(inverted)) for conf in config.get(CONF_ON_TURN_ON, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) @@ -147,12 +147,12 @@ async def setup_switch_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) - if CONF_DEVICE_CLASS in config: - cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: + cg.add(var.set_device_class(device_class)) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) diff --git a/esphome/components/sx1509/binary_sensor/__init__.py b/esphome/components/sx1509/binary_sensor/__init__.py index bbf0e5d0bc5f..280b5ad90ca9 100644 --- a/esphome/components/sx1509/binary_sensor/__init__.py +++ b/esphome/components/sx1509/binary_sensor/__init__.py @@ -1,12 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor +from esphome.const import CONF_ROW, CONF_COL from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID -CONF_ROW = "row" -CONF_COL = "col" - DEPENDENCIES = ["sx1509"] SX1509BinarySensor = sx1509_ns.class_("SX1509BinarySensor", binary_sensor.BinarySensor) @@ -14,8 +12,8 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(SX1509BinarySensor).extend( { cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), - cv.Required(CONF_ROW): cv.int_range(min=0, max=4), - cv.Required(CONF_COL): cv.int_range(min=0, max=4), + cv.Required(CONF_ROW): cv.int_range(min=0, max=7), + cv.Required(CONF_COL): cv.int_range(min=0, max=7), } ) diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index d0a84b99ffe7..ee90e0e410b5 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -68,9 +68,18 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { uint16_t temp_reg_data = 0; this->read_byte_16(REG_DATA_B, &temp_reg_data); if (bit_value) { - temp_reg_data |= (1 << pin); + output_state_ |= (1 << pin); // set bit in shadow register } else { - temp_reg_data &= ~(1 << pin); + output_state_ &= ~(1 << pin); // reset bit shadow register + } + for (uint16_t b = 0x8000; b; b >>= 1) { + if ((~ddr_mask_) & b) { // transfer bits of outputs, but don't mess with inputs + if (output_state_ & b) { + temp_reg_data |= b; + } else { + temp_reg_data &= ~b; + } + } } this->write_byte_16(REG_DATA_B, temp_reg_data); } @@ -134,6 +143,7 @@ void SX1509Component::setup_led_driver(uint8_t pin) { this->read_byte_16(REG_DATA_B, &temp_word); temp_word &= ~(1 << pin); + output_state_ &= ~(1 << pin); this->write_byte_16(REG_DATA_B, temp_word); } diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 8e3b41e233c4..9e4f31aab0d9 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -61,6 +61,7 @@ class SX1509Component : public Component, public i2c::I2CDevice { uint16_t ddr_mask_ = 0x00; uint16_t input_mask_ = 0x00; uint16_t port_mask_ = 0x00; + uint16_t output_state_ = 0x00; bool has_keypad_ = false; uint8_t rows_ = 0; uint8_t cols_ = 0; diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index caa3dd06550f..770fd5e47c1f 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -7,23 +7,27 @@ namespace tca9548a { static const char *const TAG = "tca9548a"; i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) { - auto err = parent_->switch_to_channel(channel_); + auto err = this->parent_->switch_to_channel(channel_); if (err != i2c::ERROR_OK) return err; - return parent_->bus_->readv(address, buffers, cnt); + err = this->parent_->bus_->readv(address, buffers, cnt); + this->parent_->disable_all_channels(); + return err; } i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) { - auto err = parent_->switch_to_channel(channel_); + auto err = this->parent_->switch_to_channel(channel_); if (err != i2c::ERROR_OK) return err; - return parent_->bus_->writev(address, buffers, cnt, stop); + err = this->parent_->bus_->writev(address, buffers, cnt, stop); + this->parent_->disable_all_channels(); + return err; } void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; if (this->read(&status, 1) != i2c::ERROR_OK) { - ESP_LOGI(TAG, "TCA9548A failed"); + ESP_LOGE(TAG, "TCA9548A failed"); this->mark_failed(); return; } @@ -37,15 +41,16 @@ void TCA9548AComponent::dump_config() { i2c::ErrorCode TCA9548AComponent::switch_to_channel(uint8_t channel) { if (this->is_failed()) return i2c::ERROR_NOT_INITIALIZED; - if (current_channel_ == channel) - return i2c::ERROR_OK; uint8_t channel_val = 1 << channel; - auto err = this->write(&channel_val, 1); - if (err == i2c::ERROR_OK) { - current_channel_ = channel; + return this->write(&channel_val, 1); +} + +void TCA9548AComponent::disable_all_channels() { + if (this->write(&TCA9548A_DISABLE_CHANNELS_COMMAND, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to disable all channels."); + this->status_set_error(); // couldn't disable channels, set error status } - return err; } } // namespace tca9548a diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 02553f8cd063..08f1674d116a 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -6,6 +6,8 @@ namespace esphome { namespace tca9548a { +static const uint8_t TCA9548A_DISABLE_CHANNELS_COMMAND = 0x00; + class TCA9548AComponent; class TCA9548AChannel : public i2c::I2CBus { public: @@ -28,10 +30,10 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice { void update(); i2c::ErrorCode switch_to_channel(uint8_t channel); + void disable_all_channels(); protected: friend class TCA9548AChannel; - uint8_t current_channel_ = 255; }; } // namespace tca9548a } // namespace esphome diff --git a/esphome/components/template/alarm_control_panel/__init__.py b/esphome/components/template/alarm_control_panel/__init__.py index 27b7e92b4fed..3555f2fafdc0 100644 --- a/esphome/components/template/alarm_control_panel/__init__.py +++ b/esphome/components/template/alarm_control_panel/__init__.py @@ -12,11 +12,13 @@ ) from .. import template_ns -CODEOWNERS = ["@grahambrown11"] +CODEOWNERS = ["@grahambrown11", "@hwstar"] CONF_CODES = "codes" CONF_BYPASS_ARMED_HOME = "bypass_armed_home" CONF_BYPASS_ARMED_NIGHT = "bypass_armed_night" +CONF_CHIME = "chime" +CONF_TRIGGER_MODE = "trigger_mode" CONF_REQUIRES_CODE_TO_ARM = "requires_code_to_arm" CONF_ARMING_HOME_TIME = "arming_home_time" CONF_ARMING_NIGHT_TIME = "arming_night_time" @@ -24,16 +26,20 @@ CONF_PENDING_TIME = "pending_time" CONF_TRIGGER_TIME = "trigger_time" + FLAG_NORMAL = "normal" FLAG_BYPASS_ARMED_HOME = "bypass_armed_home" FLAG_BYPASS_ARMED_NIGHT = "bypass_armed_night" +FLAG_CHIME = "chime" BinarySensorFlags = { FLAG_NORMAL: 1 << 0, FLAG_BYPASS_ARMED_HOME: 1 << 1, FLAG_BYPASS_ARMED_NIGHT: 1 << 2, + FLAG_CHIME: 1 << 3, } + TemplateAlarmControlPanel = template_ns.class_( "TemplateAlarmControlPanel", alarm_control_panel.AlarmControlPanel, cg.Component ) @@ -46,6 +52,14 @@ "RESTORE_DEFAULT_DISARMED": TemplateAlarmControlPanelRestoreMode.ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, } +AlarmSensorType = template_ns.enum("AlarmSensorType") + +ALARM_SENSOR_TYPES = { + "DELAYED": AlarmSensorType.ALARM_SENSOR_TYPE_DELAYED, + "INSTANT": AlarmSensorType.ALARM_SENSOR_TYPE_INSTANT, + "DELAYED_FOLLOWER": AlarmSensorType.ALARM_SENSOR_TYPE_DELAYED_FOLLOWER, +} + def validate_config(config): if config.get(CONF_REQUIRES_CODE_TO_ARM, False) and not config.get(CONF_CODES, []): @@ -60,6 +74,10 @@ def validate_config(config): cv.Required(CONF_INPUT): cv.use_id(binary_sensor.BinarySensor), cv.Optional(CONF_BYPASS_ARMED_HOME, default=False): cv.boolean, cv.Optional(CONF_BYPASS_ARMED_NIGHT, default=False): cv.boolean, + cv.Optional(CONF_CHIME, default=False): cv.boolean, + cv.Optional(CONF_TRIGGER_MODE, default="DELAYED"): cv.enum( + ALARM_SENSOR_TYPES, upper=True, space="_" + ), }, key=CONF_INPUT, ) @@ -123,6 +141,7 @@ async def to_code(config): for sensor in config.get(CONF_BINARY_SENSORS, []): bs = await cg.get_variable(sensor[CONF_INPUT]) + flags = BinarySensorFlags[FLAG_NORMAL] if sensor[CONF_BYPASS_ARMED_HOME]: flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_HOME] @@ -130,7 +149,9 @@ async def to_code(config): if sensor[CONF_BYPASS_ARMED_NIGHT]: flags |= BinarySensorFlags[FLAG_BYPASS_ARMED_NIGHT] supports_arm_night = True - cg.add(var.add_sensor(bs, flags)) + if sensor[CONF_CHIME]: + flags |= BinarySensorFlags[FLAG_CHIME] + cg.add(var.add_sensor(bs, flags, sensor[CONF_TRIGGER_MODE])) cg.add(var.set_supports_arm_home(supports_arm_home)) cg.add(var.set_supports_arm_night(supports_arm_night)) 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 da56976b5669..99843417faba 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 @@ -1,3 +1,4 @@ + #include "template_alarm_control_panel.h" #include #include "esphome/components/alarm_control_panel/alarm_control_panel.h" @@ -15,8 +16,14 @@ static const char *const TAG = "template.alarm_control_panel"; TemplateAlarmControlPanel::TemplateAlarmControlPanel(){}; #ifdef USE_BINARY_SENSOR -void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags) { - this->sensor_map_[sensor] = flags; +void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags, AlarmSensorType type) { + // 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; + this->sensor_data_.push_back(sd); + this->sensor_map_[sensor].store_index = this->next_store_index_++; }; #endif @@ -26,22 +33,36 @@ void TemplateAlarmControlPanel::dump_config() { ESP_LOGCONFIG(TAG, " Number of Codes: %u", 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: %us", (this->arming_away_time_ / 1000)); + 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: %us", (this->arming_home_time_ / 1000)); + 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: %us", (this->arming_night_time_ / 1000)); - ESP_LOGCONFIG(TAG, " Pending Time: %us", (this->pending_time_ / 1000)); - ESP_LOGCONFIG(TAG, " Trigger Time: %us", (this->trigger_time_ / 1000)); - ESP_LOGCONFIG(TAG, " Supported Features: %u", this->get_supported_features()); + ESP_LOGCONFIG(TAG, " Arming Night Time: %" PRIu32 "s", (this->arming_night_time_ / 1000)); + ESP_LOGCONFIG(TAG, " Pending Time: %" PRIu32 "s", (this->pending_time_ / 1000)); + ESP_LOGCONFIG(TAG, " Trigger Time: %" PRIu32 "s", (this->trigger_time_ / 1000)); + ESP_LOGCONFIG(TAG, " Supported Features: %" PRIu32, this->get_supported_features()); #ifdef USE_BINARY_SENSOR - for (auto sensor_pair : this->sensor_map_) { - ESP_LOGCONFIG(TAG, " Binary Sesnsor:"); - ESP_LOGCONFIG(TAG, " Name: %s", sensor_pair.first->get_name().c_str()); + for (auto sensor_info : this->sensor_map_) { + ESP_LOGCONFIG(TAG, " Binary Sensor:"); + ESP_LOGCONFIG(TAG, " Name: %s", sensor_info.first->get_name().c_str()); ESP_LOGCONFIG(TAG, " Armed home bypass: %s", - TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)); + TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)); ESP_LOGCONFIG(TAG, " Armed night bypass: %s", - TRUEFALSE(sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)); + TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)); + ESP_LOGCONFIG(TAG, " Chime mode: %s", TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)); + const char *sensor_type; + switch (sensor_info.second.type) { + case ALARM_SENSOR_TYPE_INSTANT: + sensor_type = "instant"; + break; + case ALARM_SENSOR_TYPE_DELAYED_FOLLOWER: + sensor_type = "delayed_follower"; + break; + case ALARM_SENSOR_TYPE_DELAYED: + default: + sensor_type = "delayed"; + } + ESP_LOGCONFIG(TAG, " Sensor type: %s", sensor_type); } #endif } @@ -92,31 +113,80 @@ void TemplateAlarmControlPanel::loop() { (millis() - this->last_update_) > this->trigger_time_) { future_state = this->desired_state_; } - bool trigger = false; + + bool delayed_sensor_not_ready = false; + bool instant_sensor_not_ready = false; + #ifdef USE_BINARY_SENSOR - if (this->is_state_armed(future_state)) { - // TODO might be better to register change for each sensor in setup... - for (auto sensor_pair : this->sensor_map_) { - if (sensor_pair.first->state) { - if (this->current_state_ == ACP_STATE_ARMED_HOME && - (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) { - continue; + // Test all of the sensors in the list regardless of the alarm panel state + for (auto sensor_info : this->sensor_map_) { + // Check for chime zones + if ((sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)) { + // Look for the transition from closed to open + if ((!this->sensor_data_[sensor_info.second.store_index].last_chime_state) && (sensor_info.first->state)) { + // Must be disarmed to chime + if (this->current_state_ == ACP_STATE_DISARMED) { + this->chime_callback_.call(); } - if (this->current_state_ == ACP_STATE_ARMED_NIGHT && - (sensor_pair.second & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) { - continue; + } + // Record the sensor state change + this->sensor_data_[sensor_info.second.store_index].last_chime_state = sensor_info.first->state; + } + // Check for triggered sensors + if (sensor_info.first->state) { // Sensor triggered? + // Skip if bypass armed home + if (this->current_state_ == ACP_STATE_ARMED_HOME && + (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) { + continue; + } + // Skip if bypass armed night + if (this->current_state_ == ACP_STATE_ARMED_NIGHT && + (sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) { + continue; + } + + // If sensor type is of type instant + if (sensor_info.second.type == ALARM_SENSOR_TYPE_INSTANT) { + instant_sensor_not_ready = true; + break; + } + // If sensor type is of type interior follower + if (sensor_info.second.type == ALARM_SENSOR_TYPE_DELAYED_FOLLOWER) { + // Look to see if we are in the pending state + if (this->current_state_ == ACP_STATE_PENDING) { + delayed_sensor_not_ready = true; + } else { + instant_sensor_not_ready = true; } - trigger = true; + } + // If sensor type is of type delayed + if (sensor_info.second.type == ALARM_SENSOR_TYPE_DELAYED) { + delayed_sensor_not_ready = true; break; } } } + // Update all sensors not ready flag + this->sensors_ready_ = ((!instant_sensor_not_ready) && (!delayed_sensor_not_ready)); + + // Call the ready state change callback if there was a change + if (this->sensors_ready_ != this->sensors_ready_last_) { + this->ready_callback_.call(); + this->sensors_ready_last_ = this->sensors_ready_; + } + #endif - if (trigger) { - if (this->pending_time_ > 0 && this->current_state_ != ACP_STATE_TRIGGERED) { - this->publish_state(ACP_STATE_PENDING); - } else { + if (this->is_state_armed(future_state) && (!this->sensors_ready_)) { + // Instant sensors + if (instant_sensor_not_ready) { this->publish_state(ACP_STATE_TRIGGERED); + } else if (delayed_sensor_not_ready) { + // Delayed sensors + if ((this->pending_time_ > 0) && (this->current_state_ != ACP_STATE_TRIGGERED)) { + this->publish_state(ACP_STATE_PENDING); + } else { + this->publish_state(ACP_STATE_TRIGGERED); + } } } else if (future_state != this->current_state_) { this->publish_state(future_state); 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 ebd8696692bb..9ae69a04228f 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,5 +1,6 @@ #pragma once +#include #include #include "esphome/core/automation.h" @@ -20,7 +21,15 @@ enum BinarySensorFlags : uint16_t { BINARY_SENSOR_MODE_NORMAL = 1 << 0, BINARY_SENSOR_MODE_BYPASS_ARMED_HOME = 1 << 1, BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT = 1 << 2, + BINARY_SENSOR_MODE_CHIME = 1 << 3, }; + +enum AlarmSensorType : uint16_t { + ALARM_SENSOR_TYPE_DELAYED = 0, + ALARM_SENSOR_TYPE_INSTANT, + ALARM_SENSOR_TYPE_DELAYED_FOLLOWER +}; + #endif enum TemplateAlarmControlPanelRestoreMode { @@ -28,6 +37,16 @@ enum TemplateAlarmControlPanelRestoreMode { ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED, }; +struct SensorDataStore { + bool last_chime_state; +}; + +struct SensorInfo { + uint16_t flags; + AlarmSensorType type; + uint8_t store_index; +}; + class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, public Component { public: TemplateAlarmControlPanel(); @@ -37,6 +56,7 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, uint32_t get_supported_features() const override; bool get_requires_code() const override; bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; } + bool get_all_sensors_ready() { return this->sensors_ready_; }; void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } #ifdef USE_BINARY_SENSOR @@ -45,7 +65,8 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, * @param sensor The BinarySensor instance. * @param ignore_when_home if this should be ignored when armed_home mode */ - void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0); + void add_sensor(binary_sensor::BinarySensor *sensor, uint16_t flags = 0, + AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED); #endif /** add a code @@ -97,8 +118,9 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, protected: void control(const alarm_control_panel::AlarmControlPanelCall &call) override; #ifdef USE_BINARY_SENSOR - // the map of binary sensors that the alarm_panel monitors with their modes - std::map sensor_map_; + // This maps a binary sensor to its type and attribute bits + std::map sensor_map_; + #endif TemplateAlarmControlPanelRestoreMode restore_mode_{}; @@ -114,10 +136,15 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel, 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; + bool sensors_ready_last_ = false; + uint8_t next_store_index_ = 0; // check if the code is valid bool is_code_valid_(optional code); diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index 8844ddd6ab1f..43d0be99b413 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -31,6 +31,7 @@ } CONF_HAS_POSITION = "has_position" +CONF_TOGGLE_ACTION = "toggle_action" CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( { @@ -44,6 +45,7 @@ cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_TILT_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_TILT_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_TOGGLE_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( RESTORE_MODES, upper=True @@ -74,6 +76,11 @@ async def to_code(config): var.get_stop_trigger(), [], config[CONF_STOP_ACTION] ) cg.add(var.set_has_stop(True)) + if CONF_TOGGLE_ACTION in config: + await automation.build_automation( + var.get_toggle_trigger(), [], config[CONF_TOGGLE_ACTION] + ) + cg.add(var.set_has_toggle(True)) if CONF_TILT_ACTION in config: await automation.build_automation( var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION] diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index b16e4399433e..2d6c3087ae88 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -12,6 +12,7 @@ TemplateCover::TemplateCover() : open_trigger_(new Trigger<>()), close_trigger_(new Trigger<>), stop_trigger_(new Trigger<>()), + toggle_trigger_(new Trigger<>()), position_trigger_(new Trigger()), tilt_trigger_(new Trigger()) {} void TemplateCover::setup() { @@ -68,6 +69,7 @@ float TemplateCover::get_setup_priority() const { return setup_priority::HARDWAR Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; } +Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; } void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); } void TemplateCover::control(const CoverCall &call) { if (call.get_stop()) { @@ -76,6 +78,12 @@ void TemplateCover::control(const CoverCall &call) { this->prev_command_trigger_ = this->stop_trigger_; this->publish_state(); } + if (call.get_toggle().has_value()) { + this->stop_prev_trigger_(); + this->toggle_trigger_->trigger(); + this->prev_command_trigger_ = this->toggle_trigger_; + this->publish_state(); + } if (call.get_position().has_value()) { auto pos = *call.get_position(); this->stop_prev_trigger_(); @@ -110,6 +118,7 @@ CoverTraits TemplateCover::get_traits() { auto traits = CoverTraits(); traits.set_is_assumed_state(this->assumed_state_); traits.set_supports_stop(this->has_stop_); + traits.set_supports_toggle(this->has_toggle_); traits.set_supports_position(this->has_position_); traits.set_supports_tilt(this->has_tilt_); return traits; @@ -118,6 +127,7 @@ Trigger *TemplateCover::get_position_trigger() const { return this->posit Trigger *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } void TemplateCover::set_tilt_lambda(std::function()> &&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; } void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } void TemplateCover::stop_prev_trigger_() { diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index 4ff5caf1dbf2..958c94b0a66c 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -21,6 +21,7 @@ class TemplateCover : public cover::Cover, public Component { Trigger<> *get_open_trigger() const; Trigger<> *get_close_trigger() const; Trigger<> *get_stop_trigger() const; + Trigger<> *get_toggle_trigger() const; Trigger *get_position_trigger() const; Trigger *get_tilt_trigger() const; void set_optimistic(bool optimistic); @@ -29,6 +30,7 @@ class TemplateCover : public cover::Cover, public Component { void set_has_stop(bool has_stop); void set_has_position(bool has_position); void set_has_tilt(bool has_tilt); + void set_has_toggle(bool has_toggle); void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } void setup() override; @@ -50,7 +52,9 @@ class TemplateCover : public cover::Cover, public Component { Trigger<> *open_trigger_; Trigger<> *close_trigger_; bool has_stop_{false}; + bool has_toggle_{false}; Trigger<> *stop_trigger_; + Trigger<> *toggle_trigger_; Trigger<> *prev_command_trigger_{nullptr}; Trigger *position_trigger_; bool has_position_{false}; diff --git a/esphome/components/template/datetime/__init__.py b/esphome/components/template/datetime/__init__.py new file mode 100644 index 000000000000..0c9447116f82 --- /dev/null +++ b/esphome/components/template/datetime/__init__.py @@ -0,0 +1,153 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import datetime +from esphome.const import ( + CONF_INITIAL_VALUE, + CONF_LAMBDA, + CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, + CONF_SET_ACTION, + CONF_DAY, + CONF_HOUR, + CONF_MINUTE, + CONF_MONTH, + CONF_SECOND, + CONF_TYPE, + CONF_YEAR, +) + +from esphome.core import coroutine_with_priority +from .. import template_ns + +CODEOWNERS = ["@rfdarter"] + + +TemplateDate = template_ns.class_( + "TemplateDate", datetime.DateEntity, cg.PollingComponent +) + +TemplateTime = template_ns.class_( + "TemplateTime", datetime.TimeEntity, cg.PollingComponent +) + +TemplateDateTime = template_ns.class_( + "TemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent +) + + +def validate(config): + config = config.copy() + if CONF_LAMBDA in config: + if config[CONF_OPTIMISTIC]: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + else: + if CONF_RESTORE_VALUE not in config: + config[CONF_RESTORE_VALUE] = False + + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the date and time being set." + ) + return config + + +_BASE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, + } +).extend(cv.polling_component_schema("60s")) + +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + { + "DATE": datetime.date_schema(TemplateDate) + .extend(_BASE_SCHEMA) + .extend( + { + cv.Optional(CONF_INITIAL_VALUE): cv.date_time( + date=True, time=False + ), + } + ), + "TIME": datetime.time_schema(TemplateTime) + .extend(_BASE_SCHEMA) + .extend( + { + cv.Optional(CONF_INITIAL_VALUE): cv.date_time( + date=False, time=True + ), + } + ), + "DATETIME": datetime.datetime_schema(TemplateDateTime) + .extend(_BASE_SCHEMA) + .extend( + { + cv.Optional(CONF_INITIAL_VALUE): cv.date_time(date=True, time=True), + } + ), + }, + upper=True, + ), + validate, +) + + +@coroutine_with_priority(-100.0) +async def to_code(config): + var = await datetime.new_datetime(config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.ESPTime) + ) + cg.add(var.set_template(template_)) + + else: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) + + if initial_value := config.get(CONF_INITIAL_VALUE): + if config[CONF_TYPE] == "DATE": + date_struct = cg.StructInitializer( + cg.ESPTime, + ("day_of_month", initial_value[CONF_DAY]), + ("month", initial_value[CONF_MONTH]), + ("year", initial_value[CONF_YEAR]), + ) + cg.add(var.set_initial_value(date_struct)) + elif config[CONF_TYPE] == "TIME": + time_struct = cg.StructInitializer( + cg.ESPTime, + ("second", initial_value[CONF_SECOND]), + ("minute", initial_value[CONF_MINUTE]), + ("hour", initial_value[CONF_HOUR]), + ) + cg.add(var.set_initial_value(time_struct)) + elif config[CONF_TYPE] == "DATETIME": + datetime_struct = cg.StructInitializer( + cg.ESPTime, + ("second", initial_value[CONF_SECOND]), + ("minute", initial_value[CONF_MINUTE]), + ("hour", initial_value[CONF_HOUR]), + ("day_of_month", initial_value[CONF_DAY]), + ("month", initial_value[CONF_MONTH]), + ("year", initial_value[CONF_YEAR]), + ) + cg.add(var.set_initial_value(datetime_struct)) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), + [(cg.ESPTime, "x")], + config[CONF_SET_ACTION], + ) + + await cg.register_component(var, config) diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp new file mode 100644 index 000000000000..01e15e532e40 --- /dev/null +++ b/esphome/components/template/datetime/template_date.cpp @@ -0,0 +1,111 @@ +#include "template_date.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.date"; + +void TemplateDate::setup() { + if (this->f_.has_value()) + return; + + ESPTime state{}; + + if (!this->restore_value_) { + state = this->initial_value_; + } else { + datetime::DateEntityRestoreState temp; + this->pref_ = + global_preferences->make_preference(194434030U ^ this->get_object_id_hash()); + if (this->pref_.load(&temp)) { + temp.apply(this); + return; + } else { + // set to inital value if loading from pref failed + state = this->initial_value_; + } + } + + this->year_ = state.year; + this->month_ = state.month; + this->day_ = state.day_of_month; + this->publish_state(); +} + +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(); +} + +void TemplateDate::control(const datetime::DateCall &call) { + bool has_year = call.get_year().has_value(); + bool has_month = call.get_month().has_value(); + bool has_day = call.get_day().has_value(); + + ESPTime value = {}; + if (has_year) + value.year = *call.get_year(); + + if (has_month) + value.month = *call.get_month(); + + if (has_day) + value.day_of_month = *call.get_day(); + + this->set_trigger_->trigger(value); + + if (this->optimistic_) { + if (has_year) + this->year_ = *call.get_year(); + if (has_month) + this->month_ = *call.get_month(); + if (has_day) + this->day_ = *call.get_day(); + this->publish_state(); + } + + if (this->restore_value_) { + datetime::DateEntityRestoreState temp = {}; + if (has_year) { + temp.year = *call.get_year(); + } else { + temp.year = this->year_; + } + if (has_month) { + temp.month = *call.get_month(); + } else { + temp.month = this->month_; + } + if (has_day) { + temp.day = *call.get_day(); + } else { + temp.day = this->day_; + } + + this->pref_.save(&temp); + } +} + +void TemplateDate::dump_config() { + LOG_DATETIME_DATE("", "Template Date", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h new file mode 100644 index 000000000000..185c7ed49d45 --- /dev/null +++ b/esphome/components/template/datetime/template_date.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATE + +#include "esphome/components/datetime/date_entity.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace template_ { + +class TemplateDate : public datetime::DateEntity, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + void control(const datetime::DateCall &call) override; + + bool optimistic_{false}; + ESPTime initial_value_{}; + bool restore_value_{false}; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_; + + ESPPreferenceObject pref_; +}; + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATE diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp new file mode 100644 index 000000000000..3ab74e197fc0 --- /dev/null +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -0,0 +1,150 @@ +#include "template_datetime.h" + +#ifdef USE_DATETIME_DATETIME + +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.datetime"; + +void TemplateDateTime::setup() { + if (this->f_.has_value()) + return; + + ESPTime state{}; + + if (!this->restore_value_) { + state = this->initial_value_; + } else { + datetime::DateTimeEntityRestoreState temp; + this->pref_ = global_preferences->make_preference(194434090U ^ + this->get_object_id_hash()); + if (this->pref_.load(&temp)) { + temp.apply(this); + return; + } else { + // set to inital value if loading from pref failed + state = this->initial_value_; + } + } + + this->year_ = state.year; + this->month_ = state.month; + this->day_ = state.day_of_month; + this->hour_ = state.hour; + this->minute_ = state.minute; + this->second_ = state.second; + this->publish_state(); +} + +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(); +} + +void TemplateDateTime::control(const datetime::DateTimeCall &call) { + bool has_year = call.get_year().has_value(); + bool has_month = call.get_month().has_value(); + bool has_day = call.get_day().has_value(); + bool has_hour = call.get_hour().has_value(); + bool has_minute = call.get_minute().has_value(); + bool has_second = call.get_second().has_value(); + + ESPTime value = {}; + if (has_year) + value.year = *call.get_year(); + + if (has_month) + value.month = *call.get_month(); + + if (has_day) + value.day_of_month = *call.get_day(); + + if (has_hour) + value.hour = *call.get_hour(); + + if (has_minute) + value.minute = *call.get_minute(); + + if (has_second) + value.second = *call.get_second(); + + this->set_trigger_->trigger(value); + + if (this->optimistic_) { + if (has_year) + this->year_ = *call.get_year(); + if (has_month) + this->month_ = *call.get_month(); + if (has_day) + this->day_ = *call.get_day(); + if (has_hour) + this->hour_ = *call.get_hour(); + if (has_minute) + this->minute_ = *call.get_minute(); + if (has_second) + this->second_ = *call.get_second(); + this->publish_state(); + } + + if (this->restore_value_) { + datetime::DateTimeEntityRestoreState temp = {}; + if (has_year) { + temp.year = *call.get_year(); + } else { + temp.year = this->year_; + } + if (has_month) { + temp.month = *call.get_month(); + } else { + temp.month = this->month_; + } + if (has_day) { + temp.day = *call.get_day(); + } else { + temp.day = this->day_; + } + if (has_hour) { + temp.hour = *call.get_hour(); + } else { + temp.hour = this->hour_; + } + if (has_minute) { + temp.minute = *call.get_minute(); + } else { + temp.minute = this->minute_; + } + if (has_second) { + temp.second = *call.get_second(); + } else { + temp.second = this->second_; + } + + this->pref_.save(&temp); + } +} + +void TemplateDateTime::dump_config() { + LOG_DATETIME_DATETIME("", "Template DateTime", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATETIME diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h new file mode 100644 index 000000000000..ef80ded89a48 --- /dev/null +++ b/esphome/components/template/datetime/template_datetime.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_DATETIME + +#include "esphome/components/datetime/datetime_entity.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace template_ { + +class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + void control(const datetime::DateTimeCall &call) override; + + bool optimistic_{false}; + ESPTime initial_value_{}; + bool restore_value_{false}; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_; + + ESPPreferenceObject pref_; +}; + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_DATETIME diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp new file mode 100644 index 000000000000..0e4d734d16c9 --- /dev/null +++ b/esphome/components/template/datetime/template_time.cpp @@ -0,0 +1,111 @@ +#include "template_time.h" + +#ifdef USE_DATETIME_TIME + +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.time"; + +void TemplateTime::setup() { + if (this->f_.has_value()) + return; + + ESPTime state{}; + + if (!this->restore_value_) { + state = this->initial_value_; + } else { + datetime::TimeEntityRestoreState temp; + this->pref_ = + global_preferences->make_preference(194434060U ^ this->get_object_id_hash()); + if (this->pref_.load(&temp)) { + temp.apply(this); + return; + } else { + // set to inital value if loading from pref failed + state = this->initial_value_; + } + } + + this->hour_ = state.hour; + this->minute_ = state.minute; + this->second_ = state.second; + this->publish_state(); +} + +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(); +} + +void TemplateTime::control(const datetime::TimeCall &call) { + bool has_hour = call.get_hour().has_value(); + bool has_minute = call.get_minute().has_value(); + bool has_second = call.get_second().has_value(); + + ESPTime value = {}; + if (has_hour) + value.hour = *call.get_hour(); + + if (has_minute) + value.minute = *call.get_minute(); + + if (has_second) + value.second = *call.get_second(); + + this->set_trigger_->trigger(value); + + if (this->optimistic_) { + if (has_hour) + this->hour_ = *call.get_hour(); + if (has_minute) + this->minute_ = *call.get_minute(); + if (has_second) + this->second_ = *call.get_second(); + this->publish_state(); + } + + if (this->restore_value_) { + datetime::TimeEntityRestoreState temp = {}; + if (has_hour) { + temp.hour = *call.get_hour(); + } else { + temp.hour = this->hour_; + } + if (has_minute) { + temp.minute = *call.get_minute(); + } else { + temp.minute = this->minute_; + } + if (has_second) { + temp.second = *call.get_second(); + } else { + temp.second = this->second_; + } + + this->pref_.save(&temp); + } +} + +void TemplateTime::dump_config() { + LOG_DATETIME_TIME("", "Template Time", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_TIME diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h new file mode 100644 index 000000000000..4a7c0098ecd5 --- /dev/null +++ b/esphome/components/template/datetime/template_time.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_DATETIME_TIME + +#include "esphome/components/datetime/time_entity.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/core/time.h" + +namespace esphome { +namespace template_ { + +class TemplateTime : public datetime::TimeEntity, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + void control(const datetime::TimeCall &call) override; + + bool optimistic_{false}; + ESPTime initial_value_{}; + bool restore_value_{false}; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_; + + ESPPreferenceObject pref_; +}; + +} // namespace template_ +} // namespace esphome + +#endif // USE_DATETIME_TIME diff --git a/esphome/components/template/event/__init__.py b/esphome/components/template/event/__init__.py new file mode 100644 index 000000000000..2a948cfdfd89 --- /dev/null +++ b/esphome/components/template/event/__init__.py @@ -0,0 +1,24 @@ +import esphome.config_validation as cv + +from esphome.components import event + +import esphome.codegen as cg + +from esphome.const import CONF_EVENT_TYPES + +from .. import template_ns + +CODEOWNERS = ["@nohat"] + +TemplateEvent = template_ns.class_("TemplateEvent", event.Event, cg.Component) + +CONFIG_SCHEMA = event.event_schema(TemplateEvent).extend( + { + cv.Required(CONF_EVENT_TYPES): cv.ensure_list(cv.string_strict), + } +) + + +async def to_code(config): + var = await event.new_event(config, event_types=config[CONF_EVENT_TYPES]) + await cg.register_component(var, config) diff --git a/esphome/components/template/event/template_event.h b/esphome/components/template/event/template_event.h new file mode 100644 index 000000000000..251ae9299b6c --- /dev/null +++ b/esphome/components/template/event/template_event.h @@ -0,0 +1,12 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/event/event.h" + +namespace esphome { +namespace template_ { + +class TemplateEvent : public Component, public event::Event {}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/fan/__init__.py b/esphome/components/template/fan/__init__.py new file mode 100644 index 000000000000..348ebd281f60 --- /dev/null +++ b/esphome/components/template/fan/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.components.fan import validate_preset_modes +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_PRESET_MODES, + CONF_SPEED_COUNT, +) + +from .. import template_ns + +CODEOWNERS = ["@ssieb"] + +TemplateFan = template_ns.class_("TemplateFan", cg.Component, fan.Fan) + +CONF_HAS_DIRECTION = "has_direction" +CONF_HAS_OSCILLATING = "has_oscillating" + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TemplateFan), + cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean, + cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean, + cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await fan.register_fan(var, config) + + cg.add(var.set_has_direction(config[CONF_HAS_DIRECTION])) + cg.add(var.set_has_oscillating(config[CONF_HAS_OSCILLATING])) + + if CONF_SPEED_COUNT in config: + cg.add(var.set_speed_count(config[CONF_SPEED_COUNT])) + + if CONF_PRESET_MODES in config: + cg.add(var.set_preset_modes(config[CONF_PRESET_MODES])) diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp new file mode 100644 index 000000000000..5f4a2ae8f772 --- /dev/null +++ b/esphome/components/template/fan/template_fan.cpp @@ -0,0 +1,38 @@ +#include "template_fan.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.fan"; + +void TemplateFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + } + + // Construct traits + this->traits_ = + fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_); + this->traits_.set_supported_preset_modes(this->preset_modes_); +} + +void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); } + +void TemplateFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value() && (this->speed_count_ > 0)) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value() && this->has_oscillating_) + 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->publish_state(); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/fan/template_fan.h b/esphome/components/template/fan/template_fan.h new file mode 100644 index 000000000000..7f5305ca4852 --- /dev/null +++ b/esphome/components/template/fan/template_fan.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan.h" + +namespace esphome { +namespace template_ { + +class TemplateFan : public Component, public fan::Fan { + public: + TemplateFan() {} + void setup() override; + void dump_config() override; + 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; } + fan::FanTraits get_traits() override { return this->traits_; } + + protected: + void control(const fan::FanCall &call) override; + + bool has_oscillating_{false}; + bool has_direction_{false}; + int speed_count_{0}; + fan::FanTraits traits_; + std::set preset_modes_{}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index b9a507c7e919..c6ed25adda30 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -11,6 +11,7 @@ CONF_OPTIMISTIC, CONF_RESTORE_VALUE, CONF_STEP, + CONF_SET_ACTION, ) from .. import template_ns @@ -18,8 +19,6 @@ "TemplateNumber", number.Number, cg.PollingComponent ) -CONF_SET_ACTION = "set_action" - def validate_min_max(config): if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index d116cbb8aee4..75dbd4f5c5c2 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -9,6 +9,7 @@ CONF_OPTIONS, CONF_OPTIMISTIC, CONF_RESTORE_VALUE, + CONF_SET_ACTION, ) from .. import template_ns @@ -16,8 +17,6 @@ "TemplateSelect", select.Select, cg.PollingComponent ) -CONF_SET_ACTION = "set_action" - def validate(config): if CONF_LAMBDA in config: diff --git a/esphome/components/template/text/__init__.py b/esphome/components/template/text/__init__.py new file mode 100644 index 000000000000..f73b240197b2 --- /dev/null +++ b/esphome/components/template/text/__init__.py @@ -0,0 +1,92 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text +from esphome.const import ( + CONF_INITIAL_VALUE, + CONF_LAMBDA, + CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, + CONF_MAX_LENGTH, + CONF_MIN_LENGTH, + CONF_PATTERN, + CONF_SET_ACTION, +) +from .. import template_ns + +TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent) + +TextSaverBase = template_ns.class_("TemplateTextSaverBase") +TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase) + +CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" + + +def validate(config): + if CONF_LAMBDA in config: + if config[CONF_OPTIMISTIC]: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if config[CONF_RESTORE_VALUE]: + raise cv.Invalid("restore_value cannot be used with lambda") + elif CONF_INITIAL_VALUE not in config: + config[CONF_INITIAL_VALUE] = "" + + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the text input being set." + ) + + with cv.prepend_path(CONF_MIN_LENGTH): + if config[CONF_MIN_LENGTH] > config[CONF_MAX_LENGTH]: + raise cv.Invalid("min_length must be less than or equal to max_length") + return config + + +CONFIG_SCHEMA = cv.All( + text.TEXT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateText), + cv.Optional(CONF_MIN_LENGTH, default=0): cv.int_range(min=0, max=255), + cv.Optional(CONF_MAX_LENGTH, default=255): cv.int_range(min=0, max=255), + cv.Optional(CONF_PATTERN): cv.string, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, + } + ).extend(cv.polling_component_schema("60s")), + validate, +) + + +async def to_code(config): + var = await text.new_text( + config, + min_length=config[CONF_MIN_LENGTH], + max_length=config[CONF_MAX_LENGTH], + pattern=config.get(CONF_PATTERN), + ) + await cg.register_component(var, config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) + ) + cg.add(var.set_template(template_)) + + else: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if initial_value_config := config.get(CONF_INITIAL_VALUE): + cg.add(var.set_initial_value(initial_value_config)) + if config[CONF_RESTORE_VALUE]: + args = cg.TemplateArguments(config[CONF_MAX_LENGTH]) + saver = TextSaverTemplate.template(args).new() + cg.add(var.set_value_saver(saver)) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION] + ) diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp new file mode 100644 index 000000000000..fb0208e19809 --- /dev/null +++ b/esphome/components/template/text/template_text.cpp @@ -0,0 +1,64 @@ +#include "template_text.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +static const char *const TAG = "template.text"; + +void TemplateText::setup() { + if (!(this->f_ == nullptr)) { + if (this->f_.has_value()) + return; + } + + std::string value; + ESP_LOGD(TAG, "Setting up Template Text Input"); + value = this->initial_value_; + if (!this->pref_) { + ESP_LOGD(TAG, "State from initial: %s", value.c_str()); + } else { + uint32_t key = this->get_object_id_hash(); + key += this->traits.get_min_length() << 2; + key += this->traits.get_max_length() << 4; + key += fnv1_hash(this->traits.get_pattern()) << 6; + this->pref_->setup(key, value); + } + if (!value.empty()) + this->publish_state(value); +} + +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); +} + +void TemplateText::control(const std::string &value) { + this->set_trigger_->trigger(value); + + if (this->optimistic_) + this->publish_state(value); + + if (this->pref_) { + if (!this->pref_->save(value)) { + ESP_LOGW(TAG, "Text value too long to save"); + } + } +} +void TemplateText::dump_config() { + LOG_TEXT("", "Template Text Input", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h new file mode 100644 index 000000000000..bcfc54a2ba2d --- /dev/null +++ b/esphome/components/template/text/template_text.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/components/text/text.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace template_ { + +// We keep this separate so we don't have to template and duplicate +// the text input for each different size flash allocation. +class TemplateTextSaverBase { + public: + virtual bool save(const std::string &value) { return true; } + + virtual void setup(uint32_t id, std::string &value) {} + + protected: + ESPPreferenceObject pref_; + std::string prev_; +}; + +template class TextSaver : public TemplateTextSaverBase { + public: + bool save(const std::string &value) override { + int diff = value.compare(this->prev_); + if (diff != 0) { + // If string is bigger than the allocation, do not save it. + // We don't need to waste ram setting prev_value either. + int size = value.size(); + if (size <= SZ) { + // Make it into a length prefixed thing + unsigned char temp[SZ + 1]; + memcpy(temp + 1, value.c_str(), size); + // SZ should be pre checked at the schema level, it can't go past the char range. + temp[0] = ((unsigned char) size); + this->pref_.save(&temp); + this->prev_.assign(value); + return true; + } + } + return false; + } + + // Make the preference object. Fill the provided location with the saved data + // If it is available, else leave it alone + void setup(uint32_t id, std::string &value) override { + this->pref_ = global_preferences->make_preference(id); + + char temp[SZ + 1]; + bool hasdata = this->pref_.load(&temp); + + if (hasdata) { + value.assign(temp + 1, (size_t) temp[0]); + } + + this->prev_.assign(value); + } +}; + +class TemplateText : public text::Text, public PollingComponent { + public: + void set_template(std::function()> &&f) { this->f_ = f; } + + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + Trigger *get_set_trigger() const { return this->set_trigger_; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_initial_value(const std::string &initial_value) { this->initial_value_ = initial_value; } + void set_value_saver(TemplateTextSaverBase *restore_value_saver) { this->pref_ = restore_value_saver; } + + protected: + void control(const std::string &value) override; + bool optimistic_ = false; + std::string initial_value_; + Trigger *set_trigger_ = new Trigger(); + optional()>> f_{nullptr}; + + TemplateTextSaverBase *pref_ = nullptr; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/valve/__init__.py b/esphome/components/template/valve/__init__.py new file mode 100644 index 000000000000..89d776dfdda2 --- /dev/null +++ b/esphome/components/template/valve/__init__.py @@ -0,0 +1,118 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import valve +from esphome.const import ( + CONF_ASSUMED_STATE, + CONF_CLOSE_ACTION, + CONF_CURRENT_OPERATION, + CONF_ID, + CONF_LAMBDA, + CONF_OPEN_ACTION, + CONF_OPTIMISTIC, + CONF_POSITION, + CONF_POSITION_ACTION, + CONF_RESTORE_MODE, + CONF_STATE, + CONF_STOP_ACTION, +) +from .. import template_ns + +TemplateValve = template_ns.class_("TemplateValve", valve.Valve, cg.Component) + +TemplateValveRestoreMode = template_ns.enum("TemplateValveRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TemplateValveRestoreMode.VALVE_NO_RESTORE, + "RESTORE": TemplateValveRestoreMode.VALVE_RESTORE, + "RESTORE_AND_CALL": TemplateValveRestoreMode.VALVE_RESTORE_AND_CALL, +} + +CONF_HAS_POSITION = "has_position" +CONF_TOGGLE_ACTION = "toggle_action" + +CONFIG_SCHEMA = valve.VALVE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateValve), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, + cv.Optional(CONF_HAS_POSITION, default=False): cv.boolean, + cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_TOGGLE_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await valve.new_valve(config) + await cg.register_component(var, config) + if lambda_config := config.get(CONF_LAMBDA): + template_ = await cg.process_lambda( + lambda_config, [], return_type=cg.optional.template(float) + ) + cg.add(var.set_state_lambda(template_)) + if open_action_config := config.get(CONF_OPEN_ACTION): + await automation.build_automation( + var.get_open_trigger(), [], open_action_config + ) + if close_action_config := config.get(CONF_CLOSE_ACTION): + await automation.build_automation( + var.get_close_trigger(), [], close_action_config + ) + if stop_action_config := config.get(CONF_STOP_ACTION): + await automation.build_automation( + var.get_stop_trigger(), [], stop_action_config + ) + cg.add(var.set_has_stop(True)) + if toggle_action_config := config.get(CONF_TOGGLE_ACTION): + await automation.build_automation( + var.get_toggle_trigger(), [], toggle_action_config + ) + cg.add(var.set_has_toggle(True)) + if position_action_config := config.get(CONF_POSITION_ACTION): + await automation.build_automation( + var.get_position_trigger(), [(float, "pos")], position_action_config + ) + cg.add(var.set_has_position(True)) + else: + cg.add(var.set_has_position(config[CONF_HAS_POSITION])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + + +@automation.register_action( + "valve.template.publish", + valve.ValvePublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(valve.Valve), + cv.Exclusive(CONF_STATE, "pos"): cv.templatable(valve.validate_valve_state), + cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage), + cv.Optional(CONF_CURRENT_OPERATION): cv.templatable( + valve.validate_valve_operation + ), + } + ), +) +async def valve_template_publish_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) + if state_config := config.get(CONF_STATE): + template_ = await cg.templatable(state_config, args, float) + cg.add(var.set_position(template_)) + if (position_config := config.get(CONF_POSITION)) is not None: + template_ = await cg.templatable(position_config, args, float) + cg.add(var.set_position(template_)) + if current_operation_config := config.get(CONF_CURRENT_OPERATION): + template_ = await cg.templatable( + current_operation_config, args, valve.ValveOperation + ) + cg.add(var.set_current_operation(template_)) + return var diff --git a/esphome/components/template/valve/template_valve.cpp b/esphome/components/template/valve/template_valve.cpp new file mode 100644 index 000000000000..f943e19da976 --- /dev/null +++ b/esphome/components/template/valve/template_valve.cpp @@ -0,0 +1,131 @@ +#include "template_valve.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +using namespace esphome::valve; + +static const char *const TAG = "template.valve"; + +TemplateValve::TemplateValve() + : open_trigger_(new Trigger<>()), + close_trigger_(new Trigger<>), + stop_trigger_(new Trigger<>()), + toggle_trigger_(new Trigger<>()), + position_trigger_(new Trigger()) {} + +void TemplateValve::setup() { + ESP_LOGCONFIG(TAG, "Setting up template valve '%s'...", this->name_.c_str()); + switch (this->restore_mode_) { + case VALVE_NO_RESTORE: + break; + case VALVE_RESTORE: { + auto restore = this->restore_state_(); + if (restore.has_value()) + restore->apply(this); + break; + } + case VALVE_RESTORE_AND_CALL: { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->to_call(this).perform(); + } + break; + } + } +} + +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; + } + } + } + + if (changed) + this->publish_state(); +} + +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(std::function()> &&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_; } +Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; } +Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; } +Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; } + +void TemplateValve::dump_config() { + LOG_VALVE("", "Template Valve", this); + ESP_LOGCONFIG(TAG, " Has position: %s", YESNO(this->has_position_)); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); +} + +void TemplateValve::control(const ValveCall &call) { + if (call.get_stop()) { + this->stop_prev_trigger_(); + this->stop_trigger_->trigger(); + this->prev_command_trigger_ = this->stop_trigger_; + this->publish_state(); + } + if (call.get_toggle().has_value()) { + this->stop_prev_trigger_(); + this->toggle_trigger_->trigger(); + this->prev_command_trigger_ = this->toggle_trigger_; + this->publish_state(); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + this->stop_prev_trigger_(); + + if (pos == VALVE_OPEN) { + this->open_trigger_->trigger(); + this->prev_command_trigger_ = this->open_trigger_; + } else if (pos == VALVE_CLOSED) { + this->close_trigger_->trigger(); + this->prev_command_trigger_ = this->close_trigger_; + } else { + this->position_trigger_->trigger(pos); + } + + if (this->optimistic_) { + this->position = pos; + } + } + + this->publish_state(); +} + +ValveTraits TemplateValve::get_traits() { + auto traits = ValveTraits(); + traits.set_is_assumed_state(this->assumed_state_); + traits.set_supports_stop(this->has_stop_); + traits.set_supports_toggle(this->has_toggle_); + traits.set_supports_position(this->has_position_); + return traits; +} + +Trigger *TemplateValve::get_position_trigger() const { return this->position_trigger_; } + +void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } +void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } +void TemplateValve::set_has_position(bool has_position) { this->has_position_ = has_position; } + +void TemplateValve::stop_prev_trigger_() { + if (this->prev_command_trigger_ != nullptr) { + this->prev_command_trigger_->stop_action(); + this->prev_command_trigger_ = nullptr; + } +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/valve/template_valve.h b/esphome/components/template/valve/template_valve.h new file mode 100644 index 000000000000..5e3fb6aff383 --- /dev/null +++ b/esphome/components/template/valve/template_valve.h @@ -0,0 +1,60 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/valve/valve.h" + +namespace esphome { +namespace template_ { + +enum TemplateValveRestoreMode { + VALVE_NO_RESTORE, + VALVE_RESTORE, + VALVE_RESTORE_AND_CALL, +}; + +class TemplateValve : public valve::Valve, public Component { + public: + TemplateValve(); + + void set_state_lambda(std::function()> &&f); + Trigger<> *get_open_trigger() const; + Trigger<> *get_close_trigger() const; + Trigger<> *get_stop_trigger() const; + Trigger<> *get_toggle_trigger() const; + Trigger *get_position_trigger() const; + void set_optimistic(bool optimistic); + void set_assumed_state(bool assumed_state); + void set_has_stop(bool has_stop); + void set_has_position(bool has_position); + void set_has_toggle(bool has_toggle); + void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; } + + void setup() override; + void loop() override; + void dump_config() override; + + float get_setup_priority() const override; + + protected: + void control(const valve::ValveCall &call) override; + valve::ValveTraits get_traits() override; + void stop_prev_trigger_(); + + TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE}; + optional()>> state_f_; + bool assumed_state_{false}; + bool optimistic_{false}; + Trigger<> *open_trigger_; + Trigger<> *close_trigger_; + bool has_stop_{false}; + bool has_toggle_{false}; + Trigger<> *stop_trigger_; + Trigger<> *toggle_trigger_; + Trigger<> *prev_command_trigger_{nullptr}; + Trigger *position_trigger_; + bool has_position_{false}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/text/__init__.py b/esphome/components/text/__init__.py new file mode 100644 index 000000000000..c0140ff082d5 --- /dev/null +++ b/esphome/components/text/__init__.py @@ -0,0 +1,138 @@ +from typing import Optional +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import mqtt +from esphome.const import ( + CONF_ID, + CONF_MODE, + CONF_ON_VALUE, + CONF_TRIGGER_ID, + CONF_MQTT_ID, + CONF_VALUE, +) + +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +CODEOWNERS = ["@mauritskorse"] +IS_PLATFORM_COMPONENT = True + +text_ns = cg.esphome_ns.namespace("text") +Text = text_ns.class_("Text", cg.EntityBase) +TextPtr = Text.operator("ptr") + +# Triggers +TextStateTrigger = text_ns.class_( + "TextStateTrigger", automation.Trigger.template(cg.std_string) +) + +# Actions +TextSetAction = text_ns.class_("TextSetAction", automation.Action) + +# Conditions +TextMode = text_ns.enum("TextMode") + +TEXT_MODES = { + "TEXT": TextMode.TEXT_MODE_TEXT, + "PASSWORD": TextMode.TEXT_MODE_PASSWORD, # to be implemented for keys, passwords, etc. +} + +TEXT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent), + cv.GenerateID(): cv.declare_id(Text), + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger), + } + ), + cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True), + } +) + + +async def setup_text_core_( + var, + config, + *, + min_length: Optional[int], + max_length: Optional[int], + pattern: Optional[str], +): + await setup_entity(var, config) + + cg.add(var.traits.set_min_length(min_length)) + cg.add(var.traits.set_max_length(max_length)) + if pattern is not None: + cg.add(var.traits.set_pattern(pattern)) + + cg.add(var.traits.set_mode(config[CONF_MODE])) + + for conf in config.get(CONF_ON_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_text( + var, + config, + *, + min_length: Optional[int] = 0, + max_length: Optional[int] = 255, + pattern: Optional[str] = None, +): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_text(var)) + await setup_text_core_( + var, config, min_length=min_length, max_length=max_length, pattern=pattern + ) + + +async def new_text( + config, + *, + min_length: Optional[int] = 0, + max_length: Optional[int] = 255, + pattern: Optional[str] = None, +): + var = cg.new_Pvariable(config[CONF_ID]) + await register_text( + var, config, min_length=min_length, max_length=max_length, pattern=pattern + ) + return var + + +@coroutine_with_priority(40.0) +async def to_code(config): + cg.add_define("USE_TEXT") + cg.add_global(text_ns.using) + + +OPERATION_BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Text), + } +) + + +@automation.register_action( + "text.set", + TextSetAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_VALUE): cv.templatable(cv.string_strict), + } + ), +) +async def text_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) + template_ = await cg.templatable(config[CONF_VALUE], args, cg.std_string) + cg.add(var.set_value(template_)) + return var diff --git a/esphome/components/text/automation.h b/esphome/components/text/automation.h new file mode 100644 index 000000000000..f20a4f433b6c --- /dev/null +++ b/esphome/components/text/automation.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "text.h" + +namespace esphome { +namespace text { + +class TextStateTrigger : public Trigger { + public: + explicit TextStateTrigger(Text *parent) { + parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); + } +}; + +template class TextSetAction : public Action { + public: + explicit TextSetAction(Text *text) : text_(text) {} + TEMPLATABLE_VALUE(std::string, value) + + void play(Ts... x) override { + auto call = this->text_->make_call(); + call.set_value(this->value_.value(x...)); + call.perform(); + } + + protected: + Text *text_; +}; + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp new file mode 100644 index 000000000000..8f0242e74760 --- /dev/null +++ b/esphome/components/text/text.cpp @@ -0,0 +1,26 @@ +#include "text.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace text { + +static const char *const TAG = "text"; + +void Text::publish_state(const std::string &state) { + this->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()); + + } else { + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); + } + this->state_callback_.call(state); +} + +void Text::add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); +} + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h new file mode 100644 index 000000000000..f71dde69ba7d --- /dev/null +++ b/esphome/components/text/text.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include "text_call.h" +#include "text_traits.h" + +namespace esphome { +namespace text { + +#define LOG_TEXT(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + } + +/** Base-class for all text inputs. + * + * A text input can use publish_state to send out a new value. + */ +class Text : public EntityBase { + public: + std::string state; + TextTraits traits; + + void publish_state(const std::string &state); + + /// Return whether this text input has gotten a full state yet. + bool has_state() const { return has_state_; } + + /// 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); + + protected: + friend class TextCall; + + /** Set the value of the text input, this is a virtual method that each text input integration must implement. + * + * This method is called by the TextCall. + * + * @param value The value as validated by the TextCall. + */ + virtual void control(const std::string &value) = 0; + + CallbackManager state_callback_; + bool has_state_{false}; +}; + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text/text_call.cpp b/esphome/components/text/text_call.cpp new file mode 100644 index 000000000000..0d0a1d228d5f --- /dev/null +++ b/esphome/components/text/text_call.cpp @@ -0,0 +1,56 @@ +#include "text_call.h" +#include "esphome/core/log.h" +#include "text.h" + +namespace esphome { +namespace text { + +static const char *const TAG = "text"; + +TextCall &TextCall::set_value(const std::string &value) { + this->value_ = value; + return *this; +} + +void TextCall::validate_() { + const auto *name = this->parent_->get_name().c_str(); + + if (!this->value_.has_value()) { + ESP_LOGW(TAG, "'%s' - No value set for TextCall", name); + return; + } + + int sz = this->value_.value().size(); + + if (sz > this->parent_->traits.get_max_length()) { + ESP_LOGW(TAG, "'%s' - Value set for TextCall is too long", name); + this->value_.reset(); + return; + } + + if (sz < this->parent_->traits.get_min_length()) { + ESP_LOGW(TAG, "'%s' - Value set for TextCall is too short", name); + this->value_.reset(); + return; + } +} + +void TextCall::perform() { + this->validate_(); + if (!this->value_.has_value()) { + ESP_LOGW(TAG, "'%s' - No value set for TextCall", this->parent_->get_name().c_str()); + return; + } + std::string target_value = this->value_.value(); + + if (this->parent_->traits.get_mode() == TEXT_MODE_PASSWORD) { + ESP_LOGD(TAG, "'%s' - Setting password value: " LOG_SECRET("'%s'"), this->parent_->get_name().c_str(), + target_value.c_str()); + } else { + ESP_LOGD(TAG, "'%s' - Setting text value: %s", this->parent_->get_name().c_str(), target_value.c_str()); + } + this->parent_->control(target_value); +} + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text/text_call.h b/esphome/components/text/text_call.h new file mode 100644 index 000000000000..9f75a25c6bec --- /dev/null +++ b/esphome/components/text/text_call.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "text_traits.h" + +namespace esphome { +namespace text { + +class Text; + +class TextCall { + public: + explicit TextCall(Text *parent) : parent_(parent) {} + void perform(); + + TextCall &set_value(const std::string &value); + + protected: + Text *const parent_; + optional value_; + void validate_(); +}; + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text/text_traits.h b/esphome/components/text/text_traits.h new file mode 100644 index 000000000000..952afa70c792 --- /dev/null +++ b/esphome/components/text/text_traits.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "esphome/core/helpers.h" + +namespace esphome { +namespace text { + +enum TextMode : uint8_t { + TEXT_MODE_TEXT = 0, + TEXT_MODE_PASSWORD = 1, +}; + +class TextTraits { + public: + // Set/get the number value boundaries. + void set_min_length(int min_length) { this->min_length_ = min_length; } + int get_min_length() const { return this->min_length_; } + void set_max_length(int max_length) { this->max_length_ = max_length; } + 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_; } + + // Set/get the frontend mode. + void set_mode(TextMode mode) { this->mode_ = mode; } + TextMode get_mode() const { return this->mode_; } + + protected: + int min_length_; + int max_length_; + std::string pattern_; + TextMode mode_{TEXT_MODE_TEXT}; +}; + +} // namespace text +} // namespace esphome diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 3ed6b72d8f74..6c28b57b3d4f 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,6 +3,7 @@ from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, CONF_FILTERS, CONF_ICON, @@ -14,12 +15,21 @@ CONF_STATE, CONF_FROM, CONF_TO, + DEVICE_CLASS_DATE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TIMESTAMP, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome.util import Registry +DEVICE_CLASSES = [ + DEVICE_CLASS_DATE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TIMESTAMP, +] + IS_PLATFORM_COMPONENT = True @@ -112,10 +122,13 @@ async def map_filter_to_code(config, filter_id): ) +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), cv.GenerateID(): cv.declare_id(TextSensor), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -140,12 +153,21 @@ def text_sensor_schema( *, icon: str = _UNDEF, entity_category: str = _UNDEF, + device_class: str = _UNDEF, ) -> cv.Schema: schema = TEXT_SENSOR_SCHEMA if class_ is not _UNDEF: schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if icon is not _UNDEF: schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) if entity_category is not _UNDEF: schema = schema.extend( { @@ -164,6 +186,9 @@ async def build_filters(config): async def setup_text_sensor_core_(var, config): await setup_entity(var, config) + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: + cg.add(var.set_device_class(device_class)) + if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) cg.add(var.set_filters(filters)) @@ -176,8 +201,8 @@ async def setup_text_sensor_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - if CONF_MQTT_ID in config: - mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 4e36532945e9..2de9010b8816 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -28,7 +28,7 @@ class Filter { * @param value The new value. * @return An optional string, the new value that should be pushed out. */ - virtual optional new_value(std::string value); + virtual optional new_value(std::string value) = 0; /// Initialize this filter, please note this can be called more than once. virtual void initialize(TextSensor *parent, Filter *next); diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 996af02f7eef..bd72ea70e395 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -13,6 +13,9 @@ namespace text_sensor { #define LOG_TEXT_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ @@ -28,7 +31,7 @@ namespace text_sensor { public: \ void set_##name##_text_sensor(text_sensor::TextSensor *text_sensor) { this->name##_text_sensor_ = text_sensor; } -class TextSensor : public EntityBase { +class TextSensor : public EntityBase, public EntityBase_DeviceClass { public: /// Getter-syntax for .state. std::string get_state() const; diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 9a57f6a337f9..89d6b13376e2 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -35,6 +35,7 @@ CONF_HEAT_DEADBAND, CONF_HEAT_MODE, CONF_HEAT_OVERRUN, + CONF_HUMIDITY_SENSOR, CONF_ID, CONF_IDLE_ACTION, CONF_MAX_COOLING_RUN_TIME, @@ -519,6 +520,7 @@ def validate_thermostat(config): { cv.GenerateID(): cv.declare_id(ThermostatClimate), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), cv.Optional( @@ -591,11 +593,11 @@ def validate_thermostat(config): cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Optional( CONF_SET_POINT_MINIMUM_DIFFERENTIAL, default=0.5 - ): cv.temperature, - cv.Optional(CONF_COOL_DEADBAND, default=0.5): cv.temperature, - cv.Optional(CONF_COOL_OVERRUN, default=0.5): cv.temperature, - cv.Optional(CONF_HEAT_DEADBAND, default=0.5): cv.temperature, - cv.Optional(CONF_HEAT_OVERRUN, default=0.5): cv.temperature, + ): cv.temperature_delta, + cv.Optional(CONF_COOL_DEADBAND, default=0.5): cv.temperature_delta, + cv.Optional(CONF_COOL_OVERRUN, default=0.5): cv.temperature_delta, + cv.Optional(CONF_HEAT_DEADBAND, default=0.5): cv.temperature_delta, + cv.Optional(CONF_HEAT_OVERRUN, default=0.5): cv.temperature_delta, cv.Optional(CONF_MAX_COOLING_RUN_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MAX_HEATING_RUN_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MIN_COOLING_OFF_TIME): cv.positive_time_period_seconds, @@ -608,8 +610,8 @@ def validate_thermostat(config): cv.Optional(CONF_MIN_HEATING_OFF_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MIN_HEATING_RUN_TIME): cv.positive_time_period_seconds, cv.Required(CONF_MIN_IDLE_TIME): cv.positive_time_period_seconds, - cv.Optional(CONF_SUPPLEMENTAL_COOLING_DELTA): cv.temperature, - cv.Optional(CONF_SUPPLEMENTAL_HEATING_DELTA): cv.temperature, + cv.Optional(CONF_SUPPLEMENTAL_COOLING_DELTA): cv.temperature_delta, + cv.Optional(CONF_SUPPLEMENTAL_HEATING_DELTA): cv.temperature_delta, cv.Optional( CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER, default=False ): cv.boolean, @@ -658,6 +660,10 @@ async def to_code(config): ) cg.add(var.set_sensor(sens)) + if CONF_HUMIDITY_SENSOR in config: + sens = await cg.get_variable(config[CONF_HUMIDITY_SENSOR]) + cg.add(var.set_humidity_sensor(sens)) + cg.add(var.set_cool_deadband(config[CONF_COOL_DEADBAND])) cg.add(var.set_cool_overrun(config[CONF_COOL_OVERRUN])) cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 51da663a0c40..26be6ba53a2f 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -26,6 +26,15 @@ void ThermostatClimate::setup() { }); this->current_temperature = this->sensor_->state; + // register for humidity values and get initial state + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->add_on_state_callback([this](float state) { + this->current_humidity = state; + this->publish_state(); + }); + this->current_humidity = this->humidity_sensor_->state; + } + auto use_default_preset = true; if (this->on_boot_restore_from_ == thermostat::OnBootRestoreFrom::MEMORY) { @@ -53,6 +62,15 @@ void ThermostatClimate::setup() { this->publish_state(); } +void ThermostatClimate::loop() { + for (auto &timer : this->timer_) { + if (timer.active && (timer.started + timer.time < millis())) { + timer.active = false; + timer.func(); + } + } +} + float ThermostatClimate::cool_deadband() { return this->cooling_deadband_; } float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; } float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } @@ -216,6 +234,9 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); + if (this->humidity_sensor_ != nullptr) + traits.set_supports_current_humidity(true); + if (supports_auto_) traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); if (supports_heat_cool_) @@ -426,6 +447,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu this->start_timer_(thermostat::TIMER_FANNING_ON); trig_fan = this->fan_only_action_trigger_; } + this->cooling_max_runtime_exceeded_ = false; trig = this->cool_action_trigger_; ESP_LOGVV(TAG, "Switching to COOLING action"); action_ready = true; @@ -439,6 +461,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu this->start_timer_(thermostat::TIMER_FANNING_ON); trig_fan = this->fan_only_action_trigger_; } + this->heating_max_runtime_exceeded_ = false; trig = this->heat_action_trigger_; ESP_LOGVV(TAG, "Switching to HEATING action"); action_ready = true; @@ -739,15 +762,15 @@ bool ThermostatClimate::heating_action_ready_() { void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) { if (this->timer_duration_(timer_index) > 0) { - this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), - this->timer_cbf_(timer_index)); + this->timer_[timer_index].started = millis(); this->timer_[timer_index].active = true; } } bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) { + auto ret = this->timer_[timer_index].active; this->timer_[timer_index].active = false; - return this->cancel_timeout(this->timer_[timer_index].name); + return ret; } bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) { @@ -764,7 +787,6 @@ std::function ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex void ThermostatClimate::cooling_max_run_time_timer_callback_() { ESP_LOGVV(TAG, "cooling_max_run_time timer expired"); - this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].active = false; this->cooling_max_runtime_exceeded_ = true; this->trigger_supplemental_action_(); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); @@ -772,21 +794,18 @@ void ThermostatClimate::cooling_max_run_time_timer_callback_() { void ThermostatClimate::cooling_off_timer_callback_() { ESP_LOGVV(TAG, "cooling_off timer expired"); - this->timer_[thermostat::TIMER_COOLING_OFF].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::cooling_on_timer_callback_() { ESP_LOGVV(TAG, "cooling_on timer expired"); - this->timer_[thermostat::TIMER_COOLING_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); - this->timer_[thermostat::TIMER_FAN_MODE].active = false; this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); @@ -794,19 +813,16 @@ void ThermostatClimate::fan_mode_timer_callback_() { void ThermostatClimate::fanning_off_timer_callback_() { ESP_LOGVV(TAG, "fanning_off timer expired"); - this->timer_[thermostat::TIMER_FANNING_OFF].active = false; this->switch_to_action_(this->compute_action_()); } void ThermostatClimate::fanning_on_timer_callback_() { ESP_LOGVV(TAG, "fanning_on timer expired"); - this->timer_[thermostat::TIMER_FANNING_ON].active = false; this->switch_to_action_(this->compute_action_()); } void ThermostatClimate::heating_max_run_time_timer_callback_() { ESP_LOGVV(TAG, "heating_max_run_time timer expired"); - this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].active = false; this->heating_max_runtime_exceeded_ = true; this->trigger_supplemental_action_(); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); @@ -814,21 +830,18 @@ void ThermostatClimate::heating_max_run_time_timer_callback_() { void ThermostatClimate::heating_off_timer_callback_() { ESP_LOGVV(TAG, "heating_off timer expired"); - this->timer_[thermostat::TIMER_HEATING_OFF].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::heating_on_timer_callback_() { ESP_LOGVV(TAG, "heating_on timer expired"); - this->timer_[thermostat::TIMER_HEATING_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } void ThermostatClimate::idle_on_timer_callback_() { ESP_LOGVV(TAG, "idle_on timer expired"); - this->timer_[thermostat::TIMER_IDLE_ON].active = false; this->switch_to_action_(this->compute_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); } @@ -986,6 +999,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; assert(trig != nullptr); + this->preset = preset; trig->trigger(); this->refresh(); @@ -1010,6 +1024,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; assert(trig != nullptr); + this->custom_preset = custom_preset; trig->trigger(); this->refresh(); @@ -1166,6 +1181,9 @@ void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) { 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { + this->humidity_sensor_ = humidity_sensor; +} void ThermostatClimate::set_use_startup_delay(bool use_startup_delay) { this->use_startup_delay_ = use_startup_delay; } void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { this->supports_heat_cool_ = supports_heat_cool; @@ -1281,11 +1299,13 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Overrun: %.1f°C", this->cooling_overrun_); if ((this->supplemental_cool_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) > 0)) { ESP_LOGCONFIG(TAG, " Supplemental Delta: %.1f°C", this->supplemental_cool_delta_); - ESP_LOGCONFIG(TAG, " Maximum Run Time: %us", + ESP_LOGCONFIG(TAG, " Maximum Run Time: %" PRIu32 "s", this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) / 1000); } - ESP_LOGCONFIG(TAG, " Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_COOLING_OFF) / 1000); - ESP_LOGCONFIG(TAG, " Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_COOLING_ON) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Off Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_COOLING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Run Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_COOLING_ON) / 1000); } if (this->supports_heat_) { ESP_LOGCONFIG(TAG, " Heating Parameters:"); @@ -1293,24 +1313,28 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Overrun: %.1f°C", this->heating_overrun_); if ((this->supplemental_heat_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) > 0)) { ESP_LOGCONFIG(TAG, " Supplemental Delta: %.1f°C", this->supplemental_heat_delta_); - ESP_LOGCONFIG(TAG, " Maximum Run Time: %us", + ESP_LOGCONFIG(TAG, " Maximum Run Time: %" PRIu32 "s", this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) / 1000); } - ESP_LOGCONFIG(TAG, " Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_HEATING_OFF) / 1000); - ESP_LOGCONFIG(TAG, " Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_HEATING_ON) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Off Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_HEATING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Minimum Run Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_HEATING_ON) / 1000); } if (this->supports_fan_only_) { - ESP_LOGCONFIG(TAG, " Fanning Minimum Off Time: %us", this->timer_duration_(thermostat::TIMER_FANNING_OFF) / 1000); - ESP_LOGCONFIG(TAG, " Fanning Minimum Run Time: %us", this->timer_duration_(thermostat::TIMER_FANNING_ON) / 1000); + ESP_LOGCONFIG(TAG, " Fanning Minimum Off Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_FANNING_OFF) / 1000); + ESP_LOGCONFIG(TAG, " Fanning Minimum Run Time: %" PRIu32 "s", + this->timer_duration_(thermostat::TIMER_FANNING_ON) / 1000); } if (this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_ || this->supports_fan_mode_quiet_) { - ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %us", + ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s", this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000); } - ESP_LOGCONFIG(TAG, " Minimum Idle Time: %us", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000); + ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 677b4ad32457..50510cf070a0 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -1,10 +1,12 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/components/climate/climate.h" #include "esphome/components/sensor/sensor.h" +#include #include #include @@ -26,9 +28,9 @@ enum ThermostatClimateTimerIndex : size_t { enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 }; struct ThermostatClimateTimer { - const std::string name; bool active; uint32_t time; + uint32_t started; std::function func; }; @@ -59,6 +61,7 @@ class ThermostatClimate : public climate::Climate, public Component { ThermostatClimate(); void setup() override; void dump_config() override; + void loop() override; void set_default_preset(const std::string &custom_preset); void set_default_preset(climate::ClimatePreset preset); @@ -81,6 +84,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_heating_minimum_run_time_in_sec(uint32_t time); void set_idle_minimum_time_in_sec(uint32_t time); void set_sensor(sensor::Sensor *sensor); + void set_humidity_sensor(sensor::Sensor *humidity_sensor); void set_use_startup_delay(bool use_startup_delay); void set_supports_auto(bool supports_auto); void set_supports_heat_cool(bool supports_heat_cool); @@ -238,6 +242,8 @@ class ThermostatClimate : public climate::Climate, public Component { /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; + /// The sensor used for getting the current humidity + sensor::Sensor *humidity_sensor_{nullptr}; /// Whether the controller supports auto/cooling/drying/fanning/heating. /// @@ -438,16 +444,17 @@ class ThermostatClimate : public climate::Climate, public Component { /// Climate action timers std::vector timer_{ - {"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, - {"cool_off", false, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)}, - {"cool_on", false, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)}, - {"fan_mode", false, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)}, - {"fan_off", false, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)}, - {"fan_on", false, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)}, - {"heat_run", false, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)}, - {"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, - {"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, - {"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}}; + {false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, + {false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}, + }; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) std::map preset_config_{}; diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index b2be11611d29..c888705ba263 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -37,7 +37,6 @@ RealTimeClock = time_ns.class_("RealTimeClock", cg.PollingComponent) CronTrigger = time_ns.class_("CronTrigger", automation.Trigger.template(), cg.Component) SyncTrigger = time_ns.class_("SyncTrigger", automation.Trigger.template(), cg.Component) -ESPTime = time_ns.struct("ESPTime") TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition) @@ -50,7 +49,7 @@ def _load_tzdata(iana_key: str) -> Optional[bytes]: package = "tzdata.zoneinfo." + package_loc.replace("/", ".") try: - return resources.read_binary(package, resource) + return (resources.files(package) / resource).read_bytes() except (FileNotFoundError, ModuleNotFoundError): return None diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 10fa9597b963..2b9a95c6bd89 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -1,6 +1,10 @@ #include "real_time_clock.h" #include "esphome/core/log.h" +#ifdef USE_HOST +#include +#else #include "lwip/opt.h" +#endif #ifdef USE_ESP8266 #include "sys/time.h" #endif @@ -9,6 +13,8 @@ #endif #include +#include + namespace esphome { namespace time { @@ -24,8 +30,8 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { struct timeval timev { .tv_sec = static_cast(epoch), .tv_usec = 0, }; - ESP_LOGVV(TAG, "Got epoch %u", epoch); - timezone tz = {0, 0}; + ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch); + struct timezone tz = {0, 0}; int ret = settimeofday(&timev, &tz); if (ret == EINVAL) { // Some ESP8266 frameworks abort when timezone parameter is not NULL diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 50376224a9c9..e1936d5ee1c3 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -96,6 +96,9 @@ void TimeBasedCover::control(const CoverCall &call) { } } else { auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; + if (this->manual_control_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + this->position = pos == COVER_CLOSED ? COVER_OPEN : COVER_CLOSED; + } this->target_position_ = pos; this->start_direction_(op); } diff --git a/esphome/components/time_based/time_based_cover.h b/esphome/components/time_based/time_based_cover.h index b7a826d237d6..42cf66c2ab79 100644 --- a/esphome/components/time_based/time_based_cover.h +++ b/esphome/components/time_based/time_based_cover.h @@ -23,6 +23,7 @@ class TimeBasedCover : public cover::Cover, public Component { void set_has_built_in_endstop(bool value) { this->has_built_in_endstop_ = value; } void set_manual_control(bool value) { this->manual_control_ = value; } void set_assumed_state(bool value) { this->assumed_state_ = value; } + cover::CoverOperation get_last_operation() const { return this->last_operation_; } protected: void control(const cover::CoverCall &call) override; diff --git a/esphome/components/tlc5947/__init__.py b/esphome/components/tlc5947/__init__.py index 84380bdace6d..528d690fab53 100644 --- a/esphome/components/tlc5947/__init__.py +++ b/esphome/components/tlc5947/__init__.py @@ -9,12 +9,11 @@ CONF_DATA_PIN, CONF_ID, CONF_NUM_CHIPS, + CONF_OE_PIN, ) CONF_LAT_PIN = "lat_pin" -CONF_OE_PIN = "oe_pin" -AUTO_LOAD = ["output"] CODEOWNERS = ["@rnauber"] tlc5947_ns = cg.esphome_ns.namespace("tlc5947") diff --git a/esphome/components/tlc5947/output.py b/esphome/components/tlc5947/output/__init__.py similarity index 69% rename from esphome/components/tlc5947/output.py rename to esphome/components/tlc5947/output/__init__.py index ece47fa63d7d..1b5dff18541d 100644 --- a/esphome/components/tlc5947/output.py +++ b/esphome/components/tlc5947/output/__init__.py @@ -2,18 +2,19 @@ import esphome.config_validation as cv from esphome.components import output from esphome.const import CONF_CHANNEL, CONF_ID -from . import TLC5947 +from .. import TLC5947, tlc5947_ns DEPENDENCIES = ["tlc5947"] -CODEOWNERS = ["@rnauber"] -Channel = TLC5947.class_("Channel", output.FloatOutput) +TLC5947Channel = tlc5947_ns.class_( + "TLC5947Channel", output.FloatOutput, cg.Parented.template(TLC5947) +) CONF_TLC5947_ID = "tlc5947_id" CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.GenerateID(CONF_TLC5947_ID): cv.use_id(TLC5947), - cv.Required(CONF_ID): cv.declare_id(Channel), + cv.Required(CONF_ID): cv.declare_id(TLC5947Channel), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), } ).extend(cv.COMPONENT_SCHEMA) @@ -22,7 +23,5 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await output.register_output(var, config) - - parent = await cg.get_variable(config[CONF_TLC5947_ID]) - cg.add(var.set_parent(parent)) + await cg.register_parented(var, config[CONF_TLC5947_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/tlc5947/output/tlc5947_output.cpp b/esphome/components/tlc5947/output/tlc5947_output.cpp new file mode 100644 index 000000000000..9630fb8c1e8d --- /dev/null +++ b/esphome/components/tlc5947/output/tlc5947_output.cpp @@ -0,0 +1,12 @@ +#include "tlc5947_output.h" + +namespace esphome { +namespace tlc5947 { + +void TLC5947Channel::write_state(float state) { + auto amount = static_cast(state * 0xfff); + this->parent_->set_channel_value(this->channel_, amount); +} + +} // namespace tlc5947 +} // namespace esphome diff --git a/esphome/components/tlc5947/output/tlc5947_output.h b/esphome/components/tlc5947/output/tlc5947_output.h new file mode 100644 index 000000000000..5b2c51020c76 --- /dev/null +++ b/esphome/components/tlc5947/output/tlc5947_output.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/helpers.h" + +#include "esphome/components/output/float_output.h" + +#include "../tlc5947.h" + +namespace esphome { +namespace tlc5947 { + +class TLC5947Channel : public output::FloatOutput, public Parented { + public: + void set_channel(uint8_t channel) { this->channel_ = channel; } + + protected: + void write_state(float state) override; + uint8_t channel_; +}; + +} // namespace tlc5947 +} // namespace esphome diff --git a/esphome/components/tlc5947/tlc5947.cpp b/esphome/components/tlc5947/tlc5947.cpp index 8f3f60f08757..5a5c0c17c004 100644 --- a/esphome/components/tlc5947/tlc5947.cpp +++ b/esphome/components/tlc5947/tlc5947.cpp @@ -60,5 +60,14 @@ void TLC5947::loop() { this->update_ = false; } +void TLC5947::set_channel_value(uint16_t channel, uint16_t value) { + if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP) + return; + if (this->pwm_amounts_[channel] != value) { + this->update_ = true; + } + this->pwm_amounts_[channel] = value; +} + } // namespace tlc5947 } // namespace esphome diff --git a/esphome/components/tlc5947/tlc5947.h b/esphome/components/tlc5947/tlc5947.h index 0eb7f1060414..95d76408c9c6 100644 --- a/esphome/components/tlc5947/tlc5947.h +++ b/esphome/components/tlc5947/tlc5947.h @@ -2,18 +2,16 @@ // TLC5947 24-Channel, 12-Bit PWM LED Driver // https://www.ti.com/lit/ds/symlink/tlc5947.pdf +#include +#include "esphome/components/output/float_output.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" -#include "esphome/components/output/float_output.h" -#include namespace esphome { namespace tlc5947 { class TLC5947 : public Component { public: - class Channel; - const uint8_t N_CHANNELS_PER_CHIP = 24; void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; } @@ -31,31 +29,9 @@ class TLC5947 : public Component { /// Send new values if they were updated. void loop() override; - class Channel : public output::FloatOutput { - public: - void set_parent(TLC5947 *parent) { parent_ = parent; } - void set_channel(uint8_t channel) { channel_ = channel; } - - protected: - void write_state(float state) override { - auto amount = static_cast(state * 0xfff); - this->parent_->set_channel_value_(this->channel_, amount); - } - - TLC5947 *parent_; - uint8_t channel_; - }; + void set_channel_value(uint16_t channel, uint16_t value); protected: - void set_channel_value_(uint16_t channel, uint16_t value) { - if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP) - return; - if (this->pwm_amounts_[channel] != value) { - this->update_ = true; - } - this->pwm_amounts_[channel] = value; - } - GPIOPin *data_pin_; GPIOPin *clock_pin_; GPIOPin *lat_pin_; diff --git a/esphome/components/tlc5971/__init__.py b/esphome/components/tlc5971/__init__.py new file mode 100644 index 000000000000..0ff2a5d17625 --- /dev/null +++ b/esphome/components/tlc5971/__init__.py @@ -0,0 +1,40 @@ +# this component is for the "TLC5971 12-Channel, 12-Bit PWM LED Driver" [https://www.ti.com/lit/ds/symlink/tlc5971.pdf], +# which is used e.g. on [https://www.adafruit.com/product/1455]. The code is based on the TLC5947 component by @rnauber. + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import ( + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_ID, + CONF_NUM_CHIPS, +) + + +CODEOWNERS = ["@IJIJI"] + +tlc5971_ns = cg.esphome_ns.namespace("tlc5971") +TLC5971 = tlc5971_ns.class_("TLC5971", cg.Component) + +MULTI_CONF = True +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(TLC5971), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=85), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + data = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data)) + clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN]) + cg.add(var.set_clock_pin(clock)) + + cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) diff --git a/esphome/components/tlc5971/output/__init__.py b/esphome/components/tlc5971/output/__init__.py new file mode 100644 index 000000000000..9fe7b18294c6 --- /dev/null +++ b/esphome/components/tlc5971/output/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID +from .. import TLC5971, tlc5971_ns + +DEPENDENCIES = ["tlc5971"] + +TLC5971Channel = tlc5971_ns.class_( + "TLC5971Channel", output.FloatOutput, cg.Parented.template(TLC5971) +) + +CONF_TLC5971_ID = "tlc5971_id" +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(CONF_TLC5971_ID): cv.use_id(TLC5971), + cv.Required(CONF_ID): cv.declare_id(TLC5971Channel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await output.register_output(var, config) + await cg.register_parented(var, config[CONF_TLC5971_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/tlc5971/output/tlc5971_output.cpp b/esphome/components/tlc5971/output/tlc5971_output.cpp new file mode 100644 index 000000000000..b4378890721f --- /dev/null +++ b/esphome/components/tlc5971/output/tlc5971_output.cpp @@ -0,0 +1,12 @@ +#include "tlc5971_output.h" + +namespace esphome { +namespace tlc5971 { + +void TLC5971Channel::write_state(float state) { + auto amount = static_cast(state * 0xffff); + this->parent_->set_channel_value(this->channel_, amount); +} + +} // namespace tlc5971 +} // namespace esphome diff --git a/esphome/components/tlc5971/output/tlc5971_output.h b/esphome/components/tlc5971/output/tlc5971_output.h new file mode 100644 index 000000000000..944ee19b2d6e --- /dev/null +++ b/esphome/components/tlc5971/output/tlc5971_output.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/helpers.h" + +#include "esphome/components/output/float_output.h" + +#include "../tlc5971.h" + +namespace esphome { +namespace tlc5971 { + +class TLC5971Channel : public output::FloatOutput, public Parented { + public: + void set_channel(uint8_t channel) { this->channel_ = channel; } + + protected: + void write_state(float state) override; + uint8_t channel_; +}; + +} // namespace tlc5971 +} // namespace esphome diff --git a/esphome/components/tlc5971/tlc5971.cpp b/esphome/components/tlc5971/tlc5971.cpp new file mode 100644 index 000000000000..ebcc3af361ce --- /dev/null +++ b/esphome/components/tlc5971/tlc5971.cpp @@ -0,0 +1,101 @@ +#include "tlc5971.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tlc5971 { + +static const char *const TAG = "tlc5971"; + +void TLC5971::setup() { + this->data_pin_->setup(); + this->data_pin_->digital_write(true); + this->clock_pin_->setup(); + this->clock_pin_->digital_write(true); + + this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); + + ESP_LOGCONFIG(TAG, "Done setting up TLC5971 output component."); +} +void TLC5971::dump_config() { + ESP_LOGCONFIG(TAG, "TLC5971:"); + 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() { + if (!this->update_) + return; + + uint32_t command; + + // Magic word for write + command = 0x25; + + command <<= 5; + // OUTTMG = 1, EXTGCK = 0, TMGRST = 1, DSPRPT = 1, BLANK = 0 -> 0x16 + command |= 0x16; + + command <<= 7; + command |= 0x7F; // default 100% brigthness + + command <<= 7; + command |= 0x7F; // default 100% brigthness + + command <<= 7; + command |= 0x7F; // default 100% brigthness + + for (uint8_t n = 0; n < num_chips_; n++) { + this->transfer_(command >> 24); + this->transfer_(command >> 16); + this->transfer_(command >> 8); + this->transfer_(command); + + // 12 channels per TLC59711 + for (int8_t c = 11; c >= 0; c--) { + // 16 bits per channel, send MSB first + this->transfer_(pwm_amounts_.at(n * 12 + c) >> 8); + this->transfer_(pwm_amounts_.at(n * 12 + c)); + } + } + + this->update_ = false; +} + +void TLC5971::transfer_(uint8_t send) { + uint8_t startbit = 0x80; + + bool towrite, lastmosi = !(send & startbit); + uint8_t bitdelay_us = (1000000 / 1000000) / 2; + + for (uint8_t b = startbit; b != 0; b = b >> 1) { + if (bitdelay_us) { + delayMicroseconds(bitdelay_us); + } + + towrite = send & b; + if ((lastmosi != towrite)) { + this->data_pin_->digital_write(towrite); + lastmosi = towrite; + } + + this->clock_pin_->digital_write(true); + + if (bitdelay_us) { + delayMicroseconds(bitdelay_us); + } + + this->clock_pin_->digital_write(false); + } +} +void TLC5971::set_channel_value(uint16_t channel, uint16_t value) { + if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP) + return; + if (this->pwm_amounts_[channel] != value) { + this->update_ = true; + } + this->pwm_amounts_[channel] = value; +} + +} // namespace tlc5971 +} // namespace esphome diff --git a/esphome/components/tlc5971/tlc5971.h b/esphome/components/tlc5971/tlc5971.h new file mode 100644 index 000000000000..6b0daf10d16f --- /dev/null +++ b/esphome/components/tlc5971/tlc5971.h @@ -0,0 +1,43 @@ +#pragma once +// TLC5971 12-Channel, 16-Bit PWM LED Driver +// https://www.ti.com/lit/ds/symlink/tlc5971.pdf + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/output/float_output.h" +#include + +namespace esphome { +namespace tlc5971 { + +class TLC5971 : public Component { + public: + const uint8_t N_CHANNELS_PER_CHIP = 12; + + void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; } + void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; } + void set_num_chips(uint8_t num_chips) { num_chips_ = num_chips; } + + void setup() override; + + void dump_config() override; + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + /// Send new values if they were updated. + void loop() override; + + void set_channel_value(uint16_t channel, uint16_t value); + + protected: + void transfer_(uint8_t send); + + GPIOPin *data_pin_; + GPIOPin *clock_pin_; + uint8_t num_chips_; + + std::vector pwm_amounts_; + bool update_{true}; +}; +} // namespace tlc5971 +} // namespace esphome diff --git a/esphome/components/tm1621/display.py b/esphome/components/tm1621/display.py index edbc5f692872..a82b680f62fe 100644 --- a/esphome/components/tm1621/display.py +++ b/esphome/components/tm1621/display.py @@ -28,7 +28,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) cs = await cg.gpio_pin_expression(config[CONF_CS_PIN]) diff --git a/esphome/components/tm1637/display.py b/esphome/components/tm1637/display.py index 609c62fd106f..dcbc64332a98 100644 --- a/esphome/components/tm1637/display.py +++ b/esphome/components/tm1637/display.py @@ -34,7 +34,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 8d7630bd1d51..2f2d4b707a89 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -225,7 +225,7 @@ void TM1637Display::display() { // Write display CTRL CMND + brightness this->start_(); - this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | 0x08)); + this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | (this->on_ ? 0x08 : 0x00))); this->stop_(); } bool TM1637Display::send_byte_(uint8_t b) { diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index aba0071b128f..d44680c62318 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -49,6 +49,7 @@ class TM1637Display : public PollingComponent { void set_intensity(uint8_t intensity) { this->intensity_ = intensity; } void set_inverted(bool inverted) { this->inverted_ = inverted; } void set_length(uint8_t length) { this->length_ = length; } + void set_on(bool on) { this->on_ = on; } void display(); @@ -76,6 +77,7 @@ class TM1637Display : public PollingComponent { uint8_t intensity_; uint8_t length_; bool inverted_; + bool on_{true}; optional writer_{}; uint8_t buffer_[6] = {0}; #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/tm1638/display.py b/esphome/components/tm1638/display.py index 633998367478..2fb8dc7a550a 100644 --- a/esphome/components/tm1638/display.py +++ b/esphome/components/tm1638/display.py @@ -33,7 +33,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) await display.register_display(var, config) clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index a6b2189eb68b..4ef88425710f 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -13,6 +13,7 @@ CODEOWNERS = ["@freekode"] tm1651_ns = cg.esphome_ns.namespace("tm1651") +TM1651Brightness = tm1651_ns.enum("TM1651Brightness") TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component) SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) @@ -24,9 +25,9 @@ CONF_LEVEL_PERCENT = "level_percent" TM1651_BRIGHTNESS_OPTIONS = { - 1: TM1651Display.TM1651_BRIGHTNESS_LOW, - 2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM, - 3: TM1651Display.TM1651_BRIGHTNESS_HIGH, + 1: TM1651Brightness.TM1651_BRIGHTNESS_LOW, + 2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM, + 3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH, } CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index c6bb1bc0256d..89807f556597 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display"; static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100; static const uint8_t TM1651_MAX_LEVEL = 7; -static const uint8_t TM1651_BRIGHTNESS_LOW = 0; -static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2; -static const uint8_t TM1651_BRIGHTNESS_HIGH = 7; +static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0; +static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2; +static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7; void TM1651Display::setup() { ESP_LOGCONFIG(TAG, "Setting up TM1651..."); @@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) { uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) { if (new_brightness <= 1) { - return TM1651_BRIGHTNESS_LOW; + return TM1651_BRIGHTNESS_LOW_HW; } else if (new_brightness == 2) { - return TM1651_BRIGHTNESS_MEDIUM; + return TM1651_BRIGHTNESS_MEDIUM_HW; } else if (new_brightness >= 3) { - return TM1651_BRIGHTNESS_HIGH; + return TM1651_BRIGHTNESS_HIGH_HW; } - return TM1651_BRIGHTNESS_LOW; + return TM1651_BRIGHTNESS_LOW_HW; } } // namespace tm1651 diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index eb65ed186dd9..fe7b7d9c6f18 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -13,6 +13,12 @@ namespace esphome { namespace tm1651 { +enum TM1651Brightness : uint8_t { + TM1651_BRIGHTNESS_LOW = 1, + TM1651_BRIGHTNESS_MEDIUM = 2, + TM1651_BRIGHTNESS_HIGH = 3, +}; + class TM1651Display : public Component { public: void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; } @@ -24,6 +30,7 @@ class TM1651Display : public Component { void set_level_percent(uint8_t new_level); void set_level(uint8_t new_level); void set_brightness(uint8_t new_brightness); + void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast(new_brightness)); } void turn_on(); void turn_off(); diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index 57d0afd5a181..2cb1a6d1f5af 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -7,6 +7,7 @@ https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf """ + import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor diff --git a/esphome/components/tmp102/tmp102.cpp b/esphome/components/tmp102/tmp102.cpp index f6bb9a05c000..f35fbf5d4bb0 100644 --- a/esphome/components/tmp102/tmp102.cpp +++ b/esphome/components/tmp102/tmp102.cpp @@ -28,24 +28,24 @@ void TMP102Component::dump_config() { } void TMP102Component::update() { - uint16_t raw_temperature; if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } - delay(50); // NOLINT - if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_temperature = i2c::i2ctohs(raw_temperature); - - raw_temperature = raw_temperature >> 4; - float temperature = raw_temperature * TMP102_CONVERSION_FACTOR; - ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature); - - this->publish_state(temperature); - this->status_clear_warning(); + this->set_timeout("read_temp", 50, [this]() { + int16_t raw_temperature; + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); + raw_temperature = raw_temperature >> 4; + float temperature = raw_temperature * TMP102_CONVERSION_FACTOR; + ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature); + + this->publish_state(temperature); + this->status_clear_warning(); + }); } float TMP102Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index fb97258bc1df..82d099cf1202 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -30,37 +30,37 @@ def determine_config_register(polling_period): - if polling_period >= 16.0: + if polling_period >= 16000: # 64 averaged conversions, max conversion time # 0000 00 111 11 00000 # 0000 0011 1110 0000 return 0x03E0 - if polling_period >= 8.0: + if polling_period >= 8000: # 64 averaged conversions, high conversion time # 0000 00 110 11 00000 # 0000 0011 0110 0000 return 0x0360 - if polling_period >= 4.0: + if polling_period >= 4000: # 64 averaged conversions, mid conversion time # 0000 00 101 11 00000 # 0000 0010 1110 0000 return 0x02E0 - if polling_period >= 1.0: + if polling_period >= 1000: # 64 averaged conversions, min conversion time # 0000 00 000 11 00000 # 0000 0000 0110 0000 return 0x0060 - if polling_period >= 0.5: + if polling_period >= 500: # 32 averaged conversions, min conversion time # 0000 00 000 10 00000 # 0000 0000 0100 0000 return 0x0040 - if polling_period >= 0.25: + if polling_period >= 250: # 8 averaged conversions, mid conversion time # 0000 00 010 01 00000 # 0000 0001 0010 0000 return 0x0120 - if polling_period >= 0.125: + if polling_period >= 125: # 8 averaged conversions, min conversion time # 0000 00 000 01 00000 # 0000 0000 0010 0000 @@ -76,5 +76,5 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - update_period = config[CONF_UPDATE_INTERVAL].total_seconds + update_period = config[CONF_UPDATE_INTERVAL].total_milliseconds cg.add(var.set_config(determine_config_register(update_period))) diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 5cd086938ec7..32cd604be93a 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -1,6 +1,7 @@ #include "tof10120_sensor.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include // Very basic support for TOF10120 distance sensor @@ -44,7 +45,7 @@ void TOF10120Sensor::update() { } uint32_t distance_mm = (data[0] << 8) | data[1]; - ESP_LOGI(TAG, "Data read: %dmm", distance_mm); + ESP_LOGI(TAG, "Data read: %" PRIu32 "mm", distance_mm); if (distance_mm == TOF10120_OUT_OF_RANGE_VALUE) { ESP_LOGW(TAG, "Distance measurement out of range"); diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index a4bdc8cafd26..b2d3f60d2b5e 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -3,44 +3,163 @@ from esphome.components import display from esphome import automation -from esphome.const import CONF_ON_TOUCH + +from esphome.const import ( + CONF_DISPLAY, + CONF_ON_TOUCH, + CONF_ON_RELEASE, + CONF_ON_UPDATE, + CONF_SWAP_XY, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_TRANSFORM, + CONF_CALIBRATION, +) + from esphome.core import coroutine_with_priority -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@nielsnl68"] DEPENDENCIES = ["display"] IS_PLATFORM_COMPONENT = True touchscreen_ns = cg.esphome_ns.namespace("touchscreen") -Touchscreen = touchscreen_ns.class_("Touchscreen") +Touchscreen = touchscreen_ns.class_("Touchscreen", cg.PollingComponent) TouchRotation = touchscreen_ns.enum("TouchRotation") TouchPoint = touchscreen_ns.struct("TouchPoint") +TouchPoints_t = cg.std_vector.template(TouchPoint) +TouchPoints_t_const_ref = TouchPoints_t.operator("ref").operator("const") TouchListener = touchscreen_ns.class_("TouchListener") -CONF_DISPLAY = "display" CONF_TOUCHSCREEN_ID = "touchscreen_id" +CONF_REPORT_INTERVAL = "report_interval" # not used yet: +CONF_TOUCH_TIMEOUT = "touch_timeout" -TOUCHSCREEN_SCHEMA = cv.Schema( - { - cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer), - cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), - } -) +CONF_X_MIN = "x_min" +CONF_X_MAX = "x_max" +CONF_Y_MIN = "y_min" +CONF_Y_MAX = "y_max" + + +def validate_calibration(config): + if CONF_CALIBRATION in config: + calibration_config = config[CONF_CALIBRATION] + if ( + cv.int_([CONF_X_MIN]) != 0 + and cv.int_(calibration_config[CONF_X_MAX]) != 0 + and abs( + cv.int_(calibration_config[CONF_X_MIN]) + - cv.int_(calibration_config[CONF_X_MAX]) + ) + < 10 + ): + raise cv.Invalid("Calibration X values difference must be more than 10") + + if ( + cv.int_(calibration_config[CONF_Y_MIN]) != 0 + and cv.int_(calibration_config[CONF_Y_MAX]) != 0 + and abs( + cv.int_(calibration_config[CONF_Y_MIN]) + - cv.int_(calibration_config[CONF_Y_MAX]) + ) + < 10 + ): + raise cv.Invalid("Calibration Y values difference must be more than 10") + + return config + + +def calibration_schema(default_max_values): + return cv.Schema( + { + cv.Optional(CONF_X_MIN, default=0): cv.int_range(min=0, max=4095), + cv.Optional(CONF_X_MAX, default=default_max_values): cv.int_range( + min=0, max=4095 + ), + cv.Optional(CONF_Y_MIN, default=0): cv.int_range(min=0, max=4095), + cv.Optional(CONF_Y_MAX, default=default_max_values): cv.int_range( + min=0, max=4095 + ), + }, + validate_calibration, + ) + + +def touchscreen_schema(default_touch_timeout): + return cv.Schema( + { + cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display), + cv.Optional(CONF_TRANSFORM): cv.Schema( + { + cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, + cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, + } + ), + cv.Optional(CONF_TOUCH_TIMEOUT, default=default_touch_timeout): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=cv.TimePeriod(milliseconds=65535)), + ), + cv.Optional(CONF_CALIBRATION): calibration_schema(0), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True), + cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True), + } + ).extend(cv.polling_component_schema("50ms")) + + +TOUCHSCREEN_SCHEMA = touchscreen_schema(cv.UNDEFINED) async def register_touchscreen(var, config): + await cg.register_component(var, config) + disp = await cg.get_variable(config[CONF_DISPLAY]) cg.add(var.set_display(disp)) + if CONF_TOUCH_TIMEOUT in config: + cg.add(var.set_touch_timeout(config[CONF_TOUCH_TIMEOUT])) + + if CONF_TRANSFORM in config: + transform = config[CONF_TRANSFORM] + cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) + cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) + cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) + + if CONF_CALIBRATION in config: + calibration_config = config[CONF_CALIBRATION] + cg.add( + var.set_calibration( + calibration_config[CONF_X_MIN], + calibration_config[CONF_X_MAX], + calibration_config[CONF_Y_MIN], + calibration_config[CONF_Y_MAX], + ) + ) + if CONF_ON_TOUCH in config: await automation.build_automation( var.get_touch_trigger(), - [(TouchPoint, "touch")], + [(TouchPoint, "touch"), (TouchPoints_t_const_ref, "touches")], config[CONF_ON_TOUCH], ) + if CONF_ON_UPDATE in config: + await automation.build_automation( + var.get_update_trigger(), + [(TouchPoints_t_const_ref, "touches")], + config[CONF_ON_UPDATE], + ) + + if CONF_ON_RELEASE in config: + await automation.build_automation( + var.get_release_trigger(), + [], + config[CONF_ON_RELEASE], + ) + @coroutine_with_priority(100.0) async def to_code(config): diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index 66df78b62a20..6c26ae3626ef 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -14,11 +14,10 @@ void TouchscreenBinarySensor::touch(TouchPoint tp) { if (this->page_ != nullptr) { touched &= this->page_ == this->parent_->get_display()->get_active_page(); } - if (touched) { this->publish_state(true); } else { - release(); + this->release(); } } diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp index 2eaa73617166..b9498de15214 100644 --- a/esphome/components/touchscreen/touchscreen.cpp +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -7,22 +7,158 @@ namespace touchscreen { static const char *const TAG = "touchscreen"; -void Touchscreen::set_display(display::Display *display) { - this->display_ = display; - this->display_width_ = display->get_width(); - this->display_height_ = display->get_height(); - this->rotation_ = static_cast(display->get_rotation()); +void TouchscreenInterrupt::gpio_intr(TouchscreenInterrupt *store) { store->touched = true; } - if (this->rotation_ == ROTATE_90_DEGREES || this->rotation_ == ROTATE_270_DEGREES) { - std::swap(this->display_width_, this->display_height_); +void Touchscreen::attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type) { + irq_pin->attach_interrupt(TouchscreenInterrupt::gpio_intr, &this->store_, type); + this->store_.init = true; + this->store_.touched = false; + ESP_LOGD(TAG, "Attach Touch Interupt"); +} + +void Touchscreen::call_setup() { + if (this->display_ != nullptr) { + this->display_width_ = this->display_->get_native_width(); + this->display_height_ = this->display_->get_native_height(); + } + PollingComponent::call_setup(); +} + +void Touchscreen::update() { + if (!this->store_.init) { + this->store_.touched = true; + } else { + // no need to poll if we have interrupts. + ESP_LOGW(TAG, "Touch Polling Stopped. You can safely remove the 'update_interval:' variable from the YAML file."); + this->stop_poller(); + } +} + +void Touchscreen::loop() { + if (this->store_.touched) { + ESP_LOGVV(TAG, "<< Do Touch loop >>"); + this->first_touch_ = this->touches_.empty(); + this->need_update_ = false; + this->is_touched_ = false; + this->skip_update_ = false; + for (auto &tp : this->touches_) { + if (tp.second.state == STATE_PRESSED || tp.second.state == STATE_UPDATED) { + tp.second.state |= STATE_RELEASING; + } else { + tp.second.state = STATE_RELEASED; + } + tp.second.x_prev = tp.second.x; + tp.second.y_prev = tp.second.y; + } + this->update_touches(); + if (this->skip_update_) { + for (auto &tp : this->touches_) { + tp.second.state &= ~STATE_RELEASING; + } + } else { + this->store_.touched = false; + this->defer([this]() { this->send_touches_(); }); + if (this->touch_timeout_ > 0) { + // Simulate a touch after touch_timeout_> ms. This will reset any existing timeout operation. + // This is to detect touch release. + if (this->is_touched_) { + this->set_timeout(TAG, this->touch_timeout_, [this]() { this->store_.touched = true; }); + } else { + this->cancel_timeout(TAG); + } + } + } } } -void Touchscreen::send_touch_(TouchPoint tp) { - ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); - this->touch_trigger_.trigger(tp); - for (auto *listener : this->touch_listeners_) - listener->touch(tp); +void Touchscreen::add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw) { + TouchPoint tp; + uint16_t x, y; + if (this->touches_.count(id) == 0) { + tp.state = STATE_PRESSED; + tp.id = id; + } else { + tp = this->touches_[id]; + tp.state = STATE_UPDATED; + tp.y_prev = tp.y; + tp.x_prev = tp.x; + } + tp.x_raw = x_raw; + tp.y_raw = y_raw; + tp.z_raw = z_raw; + if (this->x_raw_max_ != this->x_raw_min_ and this->y_raw_max_ != this->y_raw_min_) { + x = this->normalize_(x_raw, this->x_raw_min_, this->x_raw_max_, this->invert_x_); + y = this->normalize_(y_raw, this->y_raw_min_, this->y_raw_max_, this->invert_y_); + + if (this->swap_x_y_) { + std::swap(x, y); + } + + tp.x = (uint16_t) ((int) x * this->display_width_ / 0x1000); + tp.y = (uint16_t) ((int) y * this->display_height_ / 0x1000); + } else { + tp.state |= STATE_CALIBRATE; + } + if (tp.state == STATE_PRESSED) { + tp.x_org = tp.x; + tp.y_org = tp.y; + } + + this->touches_[id] = tp; + + this->is_touched_ = true; + if ((tp.x != tp.x_prev) || (tp.y != tp.y_prev)) { + this->need_update_ = true; + } +} + +void Touchscreen::send_touches_() { + TouchPoints_t touches; + ESP_LOGV(TAG, "Touch status: is_touched=%d, was_touched=%d", this->is_touched_, this->was_touched_); + for (auto tp : this->touches_) { + ESP_LOGV(TAG, "Touch status: %d/%d: raw:(%4d,%4d,%4d) calc:(%3d,%4d)", tp.second.id, tp.second.state, + tp.second.x_raw, tp.second.y_raw, tp.second.z_raw, tp.second.x, tp.second.y); + touches.push_back(tp.second); + } + if (this->need_update_ || (!this->is_touched_ && this->was_touched_)) { + this->update_trigger_.trigger(touches); + for (auto *listener : this->touch_listeners_) { + listener->update(touches); + } + } + if (!this->is_touched_) { + if (this->was_touched_) { + this->release_trigger_.trigger(); + for (auto *listener : this->touch_listeners_) + listener->release(); + this->touches_.clear(); + } + } else { + if (this->first_touch_) { + TouchPoint tp = this->touches_.begin()->second; + this->touch_trigger_.trigger(tp, touches); + for (auto *listener : this->touch_listeners_) { + listener->touch(tp); + } + } + } + this->was_touched_ = this->is_touched_; +} + +int16_t Touchscreen::normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted) { + int16_t ret; + + if (val <= min_val) { + ret = 0; + } else if (val >= max_val) { + ret = 0xfff; + } else { + ret = (int16_t) ((int) 0xfff * (val - min_val) / (max_val - min_val)); + } + + ret = (inverted) ? 0xfff - ret : ret; + + return ret; } } // namespace touchscreen diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 24b31918805f..21111f87b3ae 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -1,53 +1,122 @@ #pragma once -#include "esphome/components/display/display_buffer.h" +#include "esphome/core/defines.h" +#include "esphome/components/display/display.h" + #include "esphome/core/automation.h" #include "esphome/core/hal.h" #include +#include namespace esphome { namespace touchscreen { +static const uint8_t STATE_RELEASED = 0x00; +static const uint8_t STATE_PRESSED = 0x01; +static const uint8_t STATE_UPDATED = 0x02; +static const uint8_t STATE_RELEASING = 0x04; +static const uint8_t STATE_CALIBRATE = 0x07; + struct TouchPoint { - uint16_t x; - uint16_t y; uint8_t id; - uint8_t state; + int16_t x_raw{0}, y_raw{0}, z_raw{0}; + uint16_t x_prev{0}, y_prev{0}; + uint16_t x_org{0}, y_org{0}; + uint16_t x{0}, y{0}; + int8_t state{0}; +}; + +using TouchPoints_t = std::vector; + +struct TouchscreenInterrupt { + volatile bool touched{true}; + bool init{false}; + static void gpio_intr(TouchscreenInterrupt *store); }; class TouchListener { public: - virtual void touch(TouchPoint tp) = 0; + virtual void touch(TouchPoint tp) {} + virtual void update(const TouchPoints_t &tpoints) {} virtual void release() {} }; -enum TouchRotation { - ROTATE_0_DEGREES = 0, - ROTATE_90_DEGREES = 90, - ROTATE_180_DEGREES = 180, - ROTATE_270_DEGREES = 270, -}; - -class Touchscreen { +class Touchscreen : public PollingComponent { public: - void set_display(display::Display *display); + void set_display(display::Display *display) { this->display_ = display; } display::Display *get_display() const { return this->display_; } - Trigger *get_touch_trigger() { return &this->touch_trigger_; } + void set_touch_timeout(uint16_t val) { this->touch_timeout_ = val; } + void set_mirror_x(bool invert_x) { this->invert_x_ = invert_x; } + void set_mirror_y(bool invert_y) { this->invert_y_ = invert_y; } + void set_swap_xy(bool swap) { this->swap_x_y_ = swap; } + + void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { + this->x_raw_min_ = std::min(x_min, x_max); + this->x_raw_max_ = std::max(x_min, x_max); + this->y_raw_min_ = std::min(y_min, y_max); + this->y_raw_max_ = std::max(y_min, y_max); + if (x_min > x_max) + this->invert_x_ = true; + if (y_min > y_max) + this->invert_y_ = true; + } + + Trigger *get_touch_trigger() { return &this->touch_trigger_; } + Trigger *get_update_trigger() { return &this->update_trigger_; } + Trigger<> *get_release_trigger() { return &this->release_trigger_; } void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } + optional get_touch() { return this->touches_.begin()->second; } + + TouchPoints_t get_touches() { + TouchPoints_t touches; + for (auto i : this->touches_) { + touches.push_back(i.second); + } + return touches; + } + + void update() override; + void loop() override; + void call_setup() override; + protected: /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. - void send_touch_(TouchPoint tp); - uint16_t display_width_; - uint16_t display_height_; - display::Display *display_; - TouchRotation rotation_; - Trigger touch_trigger_; + void attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type); + + void add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw = 0); + + virtual void update_touches() = 0; + + void send_touches_(); + + int16_t normalize_(int16_t val, int16_t min_val, int16_t max_val, bool inverted = false); + + display::Display *display_{nullptr}; + + int16_t x_raw_min_{0}, x_raw_max_{0}, y_raw_min_{0}, y_raw_max_{0}; + int16_t display_width_{0}, display_height_{0}; + + uint16_t touch_timeout_{0}; + bool invert_x_{false}, invert_y_{false}, swap_x_y_{false}; + + Trigger touch_trigger_; + Trigger update_trigger_; + Trigger<> release_trigger_; std::vector touch_listeners_; + + std::map touches_; + TouchscreenInterrupt store_; + + bool first_touch_{true}; + bool need_update_{false}; + bool is_touched_{false}; + bool was_touched_{false}; + bool skip_update_{false}; }; } // namespace touchscreen diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 5086a3840837..977048364ce2 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -142,8 +142,8 @@ void TSL2591Component::process_update_() { uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined); float lux = this->get_calculated_lux(full, infrared); uint16_t actual_gain = this->get_actual_gain(); - ESP_LOGD(TAG, "Got illuminance: combined 0x%X, full %d, IR %d, vis %d. Calc lux: %f. Actual gain: %d.", combined, - full, infrared, visible, lux, actual_gain); + ESP_LOGD(TAG, "Got illuminance: combined 0x%" PRIX32 ", full %d, IR %d, vis %d. Calc lux: %f. Actual gain: %d.", + combined, full, infrared, visible, lux, actual_gain); if (this->full_spectrum_sensor_ != nullptr) { this->full_spectrum_sensor_->publish_state(full); } diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d7c523027615..fa302b14b0f2 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -4,6 +4,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include + namespace esphome { namespace tsl2591 { diff --git a/esphome/components/tt21100/touchscreen/__init__.py b/esphome/components/tt21100/touchscreen/__init__.py index d96d389e6943..510ca2df3a11 100644 --- a/esphome/components/tt21100/touchscreen/__init__.py +++ b/esphome/components/tt21100/touchscreen/__init__.py @@ -12,7 +12,6 @@ TT21100Touchscreen = tt21100_ns.class_( "TT21100Touchscreen", touchscreen.Touchscreen, - cg.Component, i2c.I2CDevice, ) TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener") @@ -21,23 +20,21 @@ cv.Schema( { cv.GenerateID(): cv.declare_id(TT21100Touchscreen), - cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, } - ) - .extend(i2c.i2c_device_schema(0x24)) - .extend(cv.COMPONENT_SCHEMA) + ).extend(i2c.i2c_device_schema(0x24)) ) 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 touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) - interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) - cg.add(var.set_interrupt_pin(interrupt_pin)) + if CONF_INTERRUPT_PIN in config: + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) if CONF_RESET_PIN in config: rts_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) diff --git a/esphome/components/tt21100/touchscreen/tt21100.cpp b/esphome/components/tt21100/touchscreen/tt21100.cpp index 28a8c2d7545c..2bea72a59e53 100644 --- a/esphome/components/tt21100/touchscreen/tt21100.cpp +++ b/esphome/components/tt21100/touchscreen/tt21100.cpp @@ -44,19 +44,17 @@ struct TT21100TouchReport { TT21100TouchRecord touch_record[MAX_TOUCH_POINTS]; } __attribute__((packed)); -void TT21100TouchscreenStore::gpio_intr(TT21100TouchscreenStore *store) { store->touch = true; } - float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } void TT21100Touchscreen::setup() { ESP_LOGCONFIG(TAG, "Setting up TT21100 Touchscreen..."); // Register interrupt pin - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - this->interrupt_pin_->setup(); - this->store_.pin = this->interrupt_pin_->to_isr(); - this->interrupt_pin_->attach_interrupt(TT21100TouchscreenStore::gpio_intr, &this->store_, - gpio::INTERRUPT_FALLING_EDGE); + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } // Perform reset if necessary if (this->reset_pin_ != nullptr) { @@ -65,19 +63,20 @@ void TT21100Touchscreen::setup() { } // Update display dimensions if they were updated during display setup - this->display_width_ = this->display_->get_width(); - this->display_height_ = this->display_->get_height(); - this->rotation_ = static_cast(this->display_->get_rotation()); + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->x_raw_max_ = this->display_->get_native_height(); + } + } // Trigger initial read to activate the interrupt - this->store_.touch = true; + this->store_.touched = true; } -void TT21100Touchscreen::loop() { - if (!this->store_.touch) - return; - this->store_.touch = false; - +void TT21100Touchscreen::update_touches() { // Read report length uint16_t data_len; this->read((uint8_t *) &data_len, sizeof(data_len)); @@ -111,12 +110,6 @@ void TT21100Touchscreen::loop() { uint8_t touch_count = (data_len - (sizeof(*report) - sizeof(report->touch_record))) / sizeof(TT21100TouchRecord); - if (touch_count == 0) { - for (auto *listener : this->touch_listeners_) - listener->release(); - return; - } - for (int i = 0; i < touch_count; i++) { auto *touch = &report->touch_record[i]; @@ -126,30 +119,7 @@ void TT21100Touchscreen::loop() { i, touch->touch_type, touch->tip, touch->event_id, touch->touch_id, touch->x, touch->y, touch->pressure, touch->major_axis_length, touch->orientation); - TouchPoint tp; - switch (this->rotation_) { - case ROTATE_0_DEGREES: - // Origin is top right, so mirror X by default - tp.x = this->display_width_ - touch->x; - tp.y = touch->y; - break; - case ROTATE_90_DEGREES: - tp.x = touch->y; - tp.y = touch->x; - break; - case ROTATE_180_DEGREES: - tp.x = touch->x; - tp.y = this->display_height_ - touch->y; - break; - case ROTATE_270_DEGREES: - tp.x = this->display_height_ - touch->y; - tp.y = this->display_width_ - touch->x; - break; - } - tp.id = touch->tip; - tp.state = touch->pressure; - - this->defer([this, tp]() { this->send_touch_(tp); }); + this->add_raw_touch_position_(touch->tip, touch->x, touch->y, touch->pressure); } } } diff --git a/esphome/components/tt21100/touchscreen/tt21100.h b/esphome/components/tt21100/touchscreen/tt21100.h index 306360975fa2..5d1b2efe3cff 100644 --- a/esphome/components/tt21100/touchscreen/tt21100.h +++ b/esphome/components/tt21100/touchscreen/tt21100.h @@ -5,27 +5,21 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include + namespace esphome { namespace tt21100 { using namespace touchscreen; -struct TT21100TouchscreenStore { - volatile bool touch; - ISRInternalGPIOPin pin; - - static void gpio_intr(TT21100TouchscreenStore *store); -}; - class TT21100ButtonListener { public: virtual void update_button(uint8_t index, uint16_t state) = 0; }; -class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { +class TT21100Touchscreen : public Touchscreen, public i2c::I2CDevice { public: void setup() override; - void loop() override; void dump_config() override; float get_setup_priority() const override; @@ -37,7 +31,7 @@ class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2C protected: void reset_(); - TT21100TouchscreenStore store_; + void update_touches() override; InternalGPIOPin *interrupt_pin_; GPIOPin *reset_pin_{nullptr}; diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 199c2eabebe9..56eb377ed7d0 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -7,15 +7,22 @@ CONF_SWITCH_DATAPOINT, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, + CONF_PRESET, + CONF_SWING_MODE, + CONF_FAN_MODE, + CONF_TEMPERATURE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] CODEOWNERS = ["@jesserockz"] -CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint" -CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value" -CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value" +CONF_ACTIVE_STATE = "active_state" +CONF_DATAPOINT = "datapoint" +CONF_HEATING_VALUE = "heating_value" +CONF_COOLING_VALUE = "cooling_value" +CONF_DRYING_VALUE = "drying_value" +CONF_FANONLY_VALUE = "fanonly_value" CONF_HEATING_STATE_PIN = "heating_state_pin" CONF_COOLING_STATE_PIN = "cooling_state_pin" CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" @@ -23,9 +30,17 @@ CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier" CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier" -CONF_ECO_DATAPOINT = "eco_datapoint" -CONF_ECO_TEMPERATURE = "eco_temperature" +CONF_ECO = "eco" +CONF_SLEEP = "sleep" +CONF_SLEEP_DATAPOINT = "sleep_datapoint" CONF_REPORTS_FAHRENHEIT = "reports_fahrenheit" +CONF_VERTICAL_DATAPOINT = "vertical_datapoint" +CONF_HORIZONTAL_DATAPOINT = "horizontal_datapoint" +CONF_LOW_VALUE = "low_value" +CONF_MEDIUM_VALUE = "medium_value" +CONF_MIDDLE_VALUE = "middle_value" +CONF_HIGH_VALUE = "high_value" +CONF_AUTO_VALUE = "auto_value" TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) @@ -67,29 +82,72 @@ def validate_temperature_multipliers(value): return value -def validate_active_state_values(value): - if CONF_ACTIVE_STATE_DATAPOINT not in value: - if CONF_ACTIVE_STATE_COOLING_VALUE in value: - raise cv.Invalid( - f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " - f"{CONF_ACTIVE_STATE_COOLING_VALUE}" - ) - else: - if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: - raise cv.Invalid( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" - ) +def validate_cooling_values(value): + if CONF_SUPPORTS_COOL in value: + cooling_supported = value[CONF_SUPPORTS_COOL] + if not cooling_supported and CONF_ACTIVE_STATE in value: + active_state_config = value[CONF_ACTIVE_STATE] + if ( + CONF_COOLING_VALUE in active_state_config + or CONF_COOLING_STATE_PIN in value + ): + raise cv.Invalid( + f"Device does not support cooling, but {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} specified." + f" Please add '{CONF_SUPPORTS_COOL}: true' to your configuration." + ) + elif cooling_supported and CONF_ACTIVE_STATE in value: + active_state_config = value[CONF_ACTIVE_STATE] + if ( + CONF_COOLING_VALUE not in active_state_config + and CONF_COOLING_STATE_PIN not in value + ): + raise cv.Invalid( + f"Either {CONF_ACTIVE_STATE} {CONF_COOLING_VALUE} or {CONF_COOLING_STATE_PIN} is required if" + f" {CONF_SUPPORTS_COOL}: true' is in your configuration." + ) return value -def validate_eco_values(value): - if CONF_ECO_TEMPERATURE in value and CONF_ECO_DATAPOINT not in value: - raise cv.Invalid( - f"{CONF_ECO_DATAPOINT} required if using {CONF_ECO_TEMPERATURE}" - ) - return value +ACTIVE_STATES = cv.Schema( + { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_HEATING_VALUE, default=1): cv.uint8_t, + cv.Optional(CONF_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_DRYING_VALUE): cv.uint8_t, + cv.Optional(CONF_FANONLY_VALUE): cv.uint8_t, + }, +) + + +PRESETS = cv.Schema( + { + cv.Optional(CONF_ECO): { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_TEMPERATURE): cv.temperature, + }, + cv.Optional(CONF_SLEEP): { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + }, + }, +) + +FAN_MODES = cv.Schema( + { + cv.Required(CONF_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_AUTO_VALUE): cv.uint8_t, + cv.Optional(CONF_LOW_VALUE): cv.uint8_t, + cv.Optional(CONF_MEDIUM_VALUE): cv.uint8_t, + cv.Optional(CONF_MIDDLE_VALUE): cv.uint8_t, + cv.Optional(CONF_HIGH_VALUE): cv.uint8_t, + } +) +SWING_MODES = cv.Schema( + { + cv.Optional(CONF_VERTICAL_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_HORIZONTAL_DATAPOINT): cv.uint8_t, + }, +) CONFIG_SCHEMA = cv.All( climate.CLIMATE_SCHEMA.extend( @@ -99,9 +157,7 @@ def validate_eco_values(value): cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, cv.Optional(CONF_SUPPORTS_COOL, default=False): cv.boolean, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t, - cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_ACTIVE_STATE): ACTIVE_STATES, cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, @@ -109,17 +165,32 @@ def validate_eco_values(value): cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, - cv.Optional(CONF_ECO_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_ECO_TEMPERATURE): cv.temperature, cv.Optional(CONF_REPORTS_FAHRENHEIT, default=False): cv.boolean, + cv.Optional(CONF_PRESET): PRESETS, + cv.Optional(CONF_FAN_MODE): FAN_MODES, + cv.Optional(CONF_SWING_MODE): SWING_MODES, + cv.Optional("active_state_datapoint"): cv.invalid( + "'active_state_datapoint' has been moved inside of the 'active_state' config block as 'datapoint'" + ), + cv.Optional("active_state_heating_value"): cv.invalid( + "'active_state_heating_value' has been moved inside of the 'active_state' config block as 'heating_value'" + ), + cv.Optional("active_state_cooling_value"): cv.invalid( + "'active_state_cooling_value' has been moved inside of the 'active_state' config block as 'cooling_value'" + ), + cv.Optional("eco_datapoint"): cv.invalid( + "'eco_datapoint' has been moved inside of the 'eco' config block under 'preset' as 'datapoint'" + ), + cv.Optional("eco_temperature"): cv.invalid( + "'eco_temperature' has been moved inside of the 'eco' config block under 'preset' as 'temperature'" + ), } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, - validate_active_state_values, - cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), - cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), - validate_eco_values, + validate_cooling_values, + cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_HEATING_STATE_PIN), + cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_COOLING_STATE_PIN), ) @@ -133,61 +204,74 @@ async def to_code(config): cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) - if CONF_SWITCH_DATAPOINT in config: - cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) - if CONF_ACTIVE_STATE_DATAPOINT in config: - cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT])) - if CONF_ACTIVE_STATE_HEATING_VALUE in config: - cg.add( - var.set_active_state_heating_value( - config[CONF_ACTIVE_STATE_HEATING_VALUE] - ) - ) - if CONF_ACTIVE_STATE_COOLING_VALUE in config: - cg.add( - var.set_active_state_cooling_value( - config[CONF_ACTIVE_STATE_COOLING_VALUE] - ) - ) + if switch_datapoint := config.get(CONF_SWITCH_DATAPOINT): + cg.add(var.set_switch_id(switch_datapoint)) + + if active_state_config := config.get(CONF_ACTIVE_STATE): + cg.add(var.set_active_state_id(active_state_config.get(CONF_DATAPOINT))) + if (heating_value := active_state_config.get(CONF_HEATING_VALUE)) is not None: + cg.add(var.set_active_state_heating_value(heating_value)) + if (cooling_value := active_state_config.get(CONF_COOLING_VALUE)) is not None: + cg.add(var.set_active_state_cooling_value(cooling_value)) + if (drying_value := active_state_config.get(CONF_DRYING_VALUE)) is not None: + cg.add(var.set_active_state_drying_value(drying_value)) + if (fanonly_value := active_state_config.get(CONF_FANONLY_VALUE)) is not None: + cg.add(var.set_active_state_fanonly_value(fanonly_value)) else: - if CONF_HEATING_STATE_PIN in config: - heating_state_pin = await cg.gpio_pin_expression( - config[CONF_HEATING_STATE_PIN] - ) + if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN): + heating_state_pin = await cg.gpio_pin_expression(heating_state_pin_config) cg.add(var.set_heating_state_pin(heating_state_pin)) - if CONF_COOLING_STATE_PIN in config: - cooling_state_pin = await cg.gpio_pin_expression( - config[CONF_COOLING_STATE_PIN] - ) + if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN): + cooling_state_pin = await cg.gpio_pin_expression(cooling_state_pin_config) cg.add(var.set_cooling_state_pin(cooling_state_pin)) - if CONF_TARGET_TEMPERATURE_DATAPOINT in config: - cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) - if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: - cg.add( - var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT]) - ) - if CONF_TEMPERATURE_MULTIPLIER in config: - cg.add( - var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) - ) - cg.add( - var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) - ) + + if target_temperature_datapoint := config.get(CONF_TARGET_TEMPERATURE_DATAPOINT): + cg.add(var.set_target_temperature_id(target_temperature_datapoint)) + if current_temperature_datapoint := config.get(CONF_CURRENT_TEMPERATURE_DATAPOINT): + cg.add(var.set_current_temperature_id(current_temperature_datapoint)) + + if temperature_multiplier := config.get(CONF_TEMPERATURE_MULTIPLIER): + cg.add(var.set_target_temperature_multiplier(temperature_multiplier)) + cg.add(var.set_current_temperature_multiplier(temperature_multiplier)) else: - cg.add( - var.set_current_temperature_multiplier( - config[CONF_CURRENT_TEMPERATURE_MULTIPLIER] - ) - ) - cg.add( - var.set_target_temperature_multiplier( - config[CONF_TARGET_TEMPERATURE_MULTIPLIER] + if current_temperature_multiplier := config.get( + CONF_CURRENT_TEMPERATURE_MULTIPLIER + ): + cg.add( + var.set_current_temperature_multiplier(current_temperature_multiplier) ) - ) - if CONF_ECO_DATAPOINT in config: - cg.add(var.set_eco_id(config[CONF_ECO_DATAPOINT])) - if CONF_ECO_TEMPERATURE in config: - cg.add(var.set_eco_temperature(config[CONF_ECO_TEMPERATURE])) + if target_temperature_multiplier := config.get( + CONF_TARGET_TEMPERATURE_MULTIPLIER + ): + cg.add(var.set_target_temperature_multiplier(target_temperature_multiplier)) if config[CONF_REPORTS_FAHRENHEIT]: cg.add(var.set_reports_fahrenheit()) + + if preset_config := config.get(CONF_PRESET, {}): + if eco_config := preset_config.get(CONF_ECO, {}): + cg.add(var.set_eco_id(eco_config.get(CONF_DATAPOINT))) + if eco_temperature := eco_config.get(CONF_TEMPERATURE): + cg.add(var.set_eco_temperature(eco_temperature)) + if sleep_config := preset_config.get(CONF_SLEEP, {}): + cg.add(var.set_sleep_id(sleep_config.get(CONF_DATAPOINT))) + + if swing_mode_config := config.get(CONF_SWING_MODE): + if swing_vertical_datapoint := swing_mode_config.get(CONF_VERTICAL_DATAPOINT): + cg.add(var.set_swing_vertical_id(swing_vertical_datapoint)) + if swing_horizontal_datapoint := swing_mode_config.get( + CONF_HORIZONTAL_DATAPOINT + ): + cg.add(var.set_swing_horizontal_id(swing_horizontal_datapoint)) + if fan_mode_config := config.get(CONF_FAN_MODE): + cg.add(var.set_fan_speed_id(fan_mode_config.get(CONF_DATAPOINT))) + if (fan_auto_value := fan_mode_config.get(CONF_AUTO_VALUE)) is not None: + cg.add(var.set_fan_speed_auto_value(fan_auto_value)) + if (fan_low_value := fan_mode_config.get(CONF_LOW_VALUE)) is not None: + cg.add(var.set_fan_speed_low_value(fan_low_value)) + if (fan_medium_value := fan_mode_config.get(CONF_MEDIUM_VALUE)) is not None: + cg.add(var.set_fan_speed_medium_value(fan_medium_value)) + if (fan_middle_value := fan_mode_config.get(CONF_MIDDLE_VALUE)) is not None: + cg.add(var.set_fan_speed_middle_value(fan_middle_value)) + if (fan_high_value := fan_mode_config.get(CONF_HIGH_VALUE)) is not None: + cg.add(var.set_fan_speed_high_value(fan_high_value)) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 687764e30f50..274e19a69e54 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -75,6 +75,41 @@ void TuyaClimate::setup() { this->publish_state(); }); } + if (this->sleep_id_.has_value()) { + this->parent_->register_listener(*this->sleep_id_, [this](const TuyaDatapoint &datapoint) { + this->sleep_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported sleep is: %s", ONOFF(this->sleep_)); + this->compute_preset_(); + this->compute_target_temperature_(); + this->publish_state(); + }); + } + if (this->swing_vertical_id_.has_value()) { + this->parent_->register_listener(*this->swing_vertical_id_, [this](const TuyaDatapoint &datapoint) { + this->swing_vertical_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported vertical swing is: %s", ONOFF(datapoint.value_bool)); + this->compute_swingmode_(); + this->publish_state(); + }); + } + + if (this->swing_horizontal_id_.has_value()) { + this->parent_->register_listener(*this->swing_horizontal_id_, [this](const TuyaDatapoint &datapoint) { + this->swing_horizontal_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported horizontal swing is: %s", ONOFF(datapoint.value_bool)); + this->compute_swingmode_(); + this->publish_state(); + }); + } + + if (this->fan_speed_id_.has_value()) { + this->parent_->register_listener(*this->fan_speed_id_, [this](const TuyaDatapoint &datapoint) { + ESP_LOGV(TAG, "MCU reported Fan Speed Mode is: %u", datapoint.value_enum); + this->fan_state_ = datapoint.value_enum; + this->compute_fanmode_(); + this->publish_state(); + }); + } } void TuyaClimate::loop() { @@ -110,8 +145,22 @@ void TuyaClimate::control(const climate::ClimateCall &call) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state)); this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state); + const climate::ClimateMode new_mode = *call.get_mode(); + + if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_); + } else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_); + } else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_); + } else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_); + } } + control_swing_mode_(call); + control_fan_mode_(call); + if (call.get_target_temperature().has_value()) { float target_temperature = *call.get_target_temperature(); if (this->reports_fahrenheit_) @@ -129,6 +178,106 @@ void TuyaClimate::control(const climate::ClimateCall &call) { ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco)); this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco); } + if (this->sleep_id_.has_value()) { + const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP; + ESP_LOGV(TAG, "Setting sleep: %s", ONOFF(sleep)); + this->parent_->set_boolean_datapoint_value(*this->sleep_id_, sleep); + } + } +} + +void TuyaClimate::control_swing_mode_(const climate::ClimateCall &call) { + bool vertical_swing_changed = false; + bool horizontal_swing_changed = false; + + if (call.get_swing_mode().has_value()) { + const auto swing_mode = *call.get_swing_mode(); + + switch (swing_mode) { + case climate::CLIMATE_SWING_OFF: + if (swing_vertical_ || swing_horizontal_) { + this->swing_vertical_ = false; + this->swing_horizontal_ = false; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_BOTH: + if (!swing_vertical_ || !swing_horizontal_) { + this->swing_vertical_ = true; + this->swing_horizontal_ = true; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_VERTICAL: + if (!swing_vertical_ || swing_horizontal_) { + this->swing_vertical_ = true; + this->swing_horizontal_ = false; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + case climate::CLIMATE_SWING_HORIZONTAL: + if (swing_vertical_ || !swing_horizontal_) { + this->swing_vertical_ = false; + this->swing_horizontal_ = true; + vertical_swing_changed = true; + horizontal_swing_changed = true; + } + break; + + default: + break; + } + } + + if (vertical_swing_changed && this->swing_vertical_id_.has_value()) { + ESP_LOGV(TAG, "Setting vertical swing: %s", ONOFF(swing_vertical_)); + this->parent_->set_boolean_datapoint_value(*this->swing_vertical_id_, swing_vertical_); + } + + if (horizontal_swing_changed && this->swing_horizontal_id_.has_value()) { + ESP_LOGV(TAG, "Setting horizontal swing: %s", ONOFF(swing_horizontal_)); + this->parent_->set_boolean_datapoint_value(*this->swing_horizontal_id_, swing_horizontal_); + } + + // Publish the state after updating the swing mode + this->publish_state(); +} + +void TuyaClimate::control_fan_mode_(const climate::ClimateCall &call) { + if (call.get_fan_mode().has_value()) { + climate::ClimateFanMode fan_mode = *call.get_fan_mode(); + + uint8_t tuya_fan_speed; + switch (fan_mode) { + case climate::CLIMATE_FAN_LOW: + tuya_fan_speed = *fan_speed_low_value_; + break; + case climate::CLIMATE_FAN_MEDIUM: + tuya_fan_speed = *fan_speed_medium_value_; + break; + case climate::CLIMATE_FAN_MIDDLE: + tuya_fan_speed = *fan_speed_middle_value_; + break; + case climate::CLIMATE_FAN_HIGH: + tuya_fan_speed = *fan_speed_high_value_; + break; + case climate::CLIMATE_FAN_AUTO: + tuya_fan_speed = *fan_speed_auto_value_; + break; + default: + tuya_fan_speed = 0; + break; + } + + if (this->fan_speed_id_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->fan_speed_id_, tuya_fan_speed); + } } } @@ -140,10 +289,46 @@ climate::ClimateTraits TuyaClimate::traits() { traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); if (supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (this->active_state_drying_value_.has_value()) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (this->active_state_fanonly_value_.has_value()) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); if (this->eco_id_.has_value()) { - traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); } + if (this->sleep_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + } + if (this->sleep_id_.has_value() || this->eco_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); + } + if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) { + std::set supported_swing_modes = { + climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } else if (this->swing_vertical_id_.has_value()) { + std::set supported_swing_modes = {climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } else if (this->swing_horizontal_id_.has_value()) { + std::set supported_swing_modes = {climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_HORIZONTAL}; + traits.set_supported_swing_modes(std::move(supported_swing_modes)); + } + + if (fan_speed_id_) { + if (fan_speed_low_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (fan_speed_medium_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (fan_speed_middle_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (fan_speed_high_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (fan_speed_auto_value_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + } return traits; } @@ -166,16 +351,56 @@ void TuyaClimate::dump_config() { if (this->eco_id_.has_value()) { ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_); } + if (this->sleep_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Sleep has datapoint ID %u", *this->sleep_id_); + } + if (this->swing_vertical_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Swing Vertical has datapoint ID %u", *this->swing_vertical_id_); + } + if (this->swing_horizontal_id_.has_value()) { + ESP_LOGCONFIG(TAG, " Swing Horizontal has datapoint ID %u", *this->swing_horizontal_id_); + } } void TuyaClimate::compute_preset_() { if (this->eco_) { this->preset = climate::CLIMATE_PRESET_ECO; + } else if (this->sleep_) { + this->preset = climate::CLIMATE_PRESET_SLEEP; } else { this->preset = climate::CLIMATE_PRESET_NONE; } } +void TuyaClimate::compute_swingmode_() { + if (this->swing_vertical_ && this->swing_horizontal_) { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } else if (this->swing_vertical_) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else if (this->swing_horizontal_) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } +} + +void TuyaClimate::compute_fanmode_() { + if (this->fan_speed_id_.has_value()) { + // Use state from MCU datapoint + if (this->fan_speed_auto_value_.has_value() && this->fan_state_ == this->fan_speed_auto_value_) { + this->fan_mode = climate::CLIMATE_FAN_AUTO; + } else if (this->fan_speed_high_value_.has_value() && this->fan_state_ == this->fan_speed_high_value_) { + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } else if (this->fan_speed_medium_value_.has_value() && this->fan_state_ == this->fan_speed_medium_value_) { + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + } else if (this->fan_speed_middle_value_.has_value() && this->fan_state_ == this->fan_speed_middle_value_) { + this->fan_mode = climate::CLIMATE_FAN_MIDDLE; + } else if (this->fan_speed_low_value_.has_value() && this->fan_state_ == this->fan_speed_low_value_) { + this->fan_mode = climate::CLIMATE_FAN_LOW; + } + } +} + void TuyaClimate::compute_target_temperature_() { if (this->eco_ && this->eco_temperature_.has_value()) { this->target_temperature = *this->eco_temperature_; @@ -202,16 +427,28 @@ void TuyaClimate::compute_state_() { if (this->supports_heat_ && this->active_state_heating_value_.has_value() && this->active_state_ == this->active_state_heating_value_) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() && this->active_state_ == this->active_state_cooling_value_) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; + } else if (this->active_state_drying_value_.has_value() && + this->active_state_ == this->active_state_drying_value_) { + target_action = climate::CLIMATE_ACTION_DRYING; + this->mode = climate::CLIMATE_MODE_DRY; + } else if (this->active_state_fanonly_value_.has_value() && + this->active_state_ == this->active_state_fanonly_value_) { + target_action = climate::CLIMATE_ACTION_FAN; + this->mode = climate::CLIMATE_MODE_FAN_ONLY; } } else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { // Use state from input pins if (this->heating_state_) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->cooling_state_) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; } } else { // Fallback to active state calc based on temp and hysteresis @@ -219,8 +456,10 @@ void TuyaClimate::compute_state_() { if (std::abs(temp_diff) > this->hysteresis_) { if (this->supports_heat_ && temp_diff > 0) { target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_ && temp_diff < 0) { target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; } } } diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index 7c18625c4e35..d6258c21e1c7 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -18,8 +18,22 @@ class TuyaClimate : public climate::Climate, public Component { void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; } void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; } void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; } + void set_active_state_drying_value(uint8_t value) { this->active_state_drying_value_ = value; } + void set_active_state_fanonly_value(uint8_t value) { this->active_state_fanonly_value_ = value; } void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; } void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; } + void set_swing_vertical_id(uint8_t swing_vertical_id) { this->swing_vertical_id_ = swing_vertical_id; } + void set_swing_horizontal_id(uint8_t swing_horizontal_id) { this->swing_horizontal_id_ = swing_horizontal_id; } + void set_fan_speed_id(uint8_t fan_speed_id) { this->fan_speed_id_ = fan_speed_id; } + void set_fan_speed_low_value(uint8_t fan_speed_low_value) { this->fan_speed_low_value_ = fan_speed_low_value; } + void set_fan_speed_medium_value(uint8_t fan_speed_medium_value) { + this->fan_speed_medium_value_ = fan_speed_medium_value; + } + void set_fan_speed_middle_value(uint8_t fan_speed_middle_value) { + this->fan_speed_middle_value_ = fan_speed_middle_value; + } + void set_fan_speed_high_value(uint8_t fan_speed_high_value) { this->fan_speed_high_value_ = fan_speed_high_value; } + void set_fan_speed_auto_value(uint8_t fan_speed_auto_value) { this->fan_speed_auto_value_ = fan_speed_auto_value; } void set_target_temperature_id(uint8_t target_temperature_id) { this->target_temperature_id_ = target_temperature_id; } @@ -34,6 +48,7 @@ class TuyaClimate : public climate::Climate, public Component { } void set_eco_id(uint8_t eco_id) { this->eco_id_ = eco_id; } void set_eco_temperature(float eco_temperature) { this->eco_temperature_ = eco_temperature; } + void set_sleep_id(uint8_t sleep_id) { this->sleep_id_ = sleep_id; } void set_reports_fahrenheit() { this->reports_fahrenheit_ = true; } @@ -43,6 +58,12 @@ class TuyaClimate : public climate::Climate, public Component { /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; + /// Override control to change settings of swing mode. + void control_swing_mode_(const climate::ClimateCall &call); + + /// Override control to change settings of fan mode. + void control_fan_mode_(const climate::ClimateCall &call); + /// Return the traits of this controller. climate::ClimateTraits traits() override; @@ -55,6 +76,12 @@ class TuyaClimate : public climate::Climate, public Component { /// Re-compute the state of this climate controller. void compute_state_(); + /// Re-Compute the swing mode of this climate controller. + void compute_swingmode_(); + + /// Re-Compute the fan mode of this climate controller. + void compute_fanmode_(); + /// Switch the climate device to the given climate mode. void switch_to_action_(climate::ClimateAction action); @@ -65,6 +92,8 @@ class TuyaClimate : public climate::Climate, public Component { optional active_state_id_{}; optional active_state_heating_value_{}; optional active_state_cooling_value_{}; + optional active_state_drying_value_{}; + optional active_state_fanonly_value_{}; GPIOPin *heating_state_pin_{nullptr}; GPIOPin *cooling_state_pin_{nullptr}; optional target_temperature_id_{}; @@ -73,12 +102,25 @@ class TuyaClimate : public climate::Climate, public Component { float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; optional eco_id_{}; + optional sleep_id_{}; optional eco_temperature_{}; uint8_t active_state_; + uint8_t fan_state_; + optional swing_vertical_id_{}; + optional swing_horizontal_id_{}; + optional fan_speed_id_{}; + optional fan_speed_low_value_{}; + optional fan_speed_medium_value_{}; + optional fan_speed_middle_value_{}; + optional fan_speed_high_value_{}; + optional fan_speed_auto_value_{}; + bool swing_vertical_{false}; + bool swing_horizontal_{false}; bool heating_state_{false}; bool cooling_state_{false}; float manual_temperature_; bool eco_; + bool sleep_; bool reports_fahrenheit_{false}; }; diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 1b03ea50fad9..8a613d0baecb 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -9,13 +9,20 @@ static const char *const TAG = "tuya.fan"; void TuyaFan::setup() { if (this->speed_id_.has_value()) { this->parent_->register_listener(*this->speed_id_, [this](const TuyaDatapoint &datapoint) { - ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_enum); - if (datapoint.value_enum >= this->speed_count_) { - ESP_LOGE(TAG, "Speed has invalid value %d", datapoint.value_enum); - } else { - this->speed = datapoint.value_enum + 1; + if (datapoint.type == TuyaDatapointType::ENUM) { + ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_enum); + if (datapoint.value_enum >= this->speed_count_) { + ESP_LOGE(TAG, "Speed has invalid value %d", datapoint.value_enum); + } else { + this->speed = datapoint.value_enum + 1; + this->publish_state(); + } + } else if (datapoint.type == TuyaDatapointType::INTEGER) { + ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_int); + this->speed = datapoint.value_int; this->publish_state(); } + this->speed_type_ = datapoint.type; }); } if (this->switch_id_.has_value()) { @@ -27,9 +34,13 @@ void TuyaFan::setup() { } if (this->oscillation_id_.has_value()) { this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) { + // Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both + // scenarios ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); this->oscillating = datapoint.value_bool; this->publish_state(); + + this->oscillation_type_ = datapoint.type; }); } if (this->direction_id_.has_value()) { @@ -73,14 +84,22 @@ void TuyaFan::control(const fan::FanCall &call) { this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state()); } if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { - this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + if (this->oscillation_type_ == TuyaDatapointType::ENUM) { + this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + } else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) { + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); + } } if (this->direction_id_.has_value() && call.get_direction().has_value()) { bool enable = *call.get_direction() == fan::FanDirection::REVERSE; this->parent_->set_enum_datapoint_value(*this->direction_id_, enable); } if (this->speed_id_.has_value() && call.get_speed().has_value()) { - this->parent_->set_enum_datapoint_value(*this->speed_id_, *call.get_speed() - 1); + if (this->speed_type_ == TuyaDatapointType::ENUM) { + this->parent_->set_enum_datapoint_value(*this->speed_id_, *call.get_speed() - 1); + } else if (this->speed_type_ == TuyaDatapointType::INTEGER) { + this->parent_->set_integer_datapoint_value(*this->speed_id_, *call.get_speed()); + } } } diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index 4aba1e1c0775..527efa8246dc 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -28,6 +28,8 @@ class TuyaFan : public Component, public fan::Fan { optional oscillation_id_{}; optional direction_id_{}; int speed_count_{}; + TuyaDatapointType speed_type_{}; + TuyaDatapointType oscillation_type_{}; }; } // namespace tuya diff --git a/esphome/components/tuya/number/__init__.py b/esphome/components/tuya/number/__init__.py index 42ac9fcfbe27..4dae6d8d6036 100644 --- a/esphome/components/tuya/number/__init__.py +++ b/esphome/components/tuya/number/__init__.py @@ -6,6 +6,7 @@ CONF_NUMBER_DATAPOINT, CONF_MAX_VALUE, CONF_MIN_VALUE, + CONF_MULTIPLY, CONF_STEP, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya @@ -31,6 +32,7 @@ def validate_min_max(config): cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, + cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } ) .extend(cv.COMPONENT_SCHEMA), @@ -49,7 +51,8 @@ async def to_code(config): step=config[CONF_STEP], ) - paren = await cg.get_variable(config[CONF_TUYA_ID]) - cg.add(var.set_tuya_parent(paren)) + cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) + parent = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(parent)) cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT])) diff --git a/esphome/components/tuya/number/tuya_number.cpp b/esphome/components/tuya/number/tuya_number.cpp index 5c7cafbf7aba..e883c72d3d89 100644 --- a/esphome/components/tuya/number/tuya_number.cpp +++ b/esphome/components/tuya/number/tuya_number.cpp @@ -10,7 +10,7 @@ void TuyaNumber::setup() { this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { if (datapoint.type == TuyaDatapointType::INTEGER) { ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int); - this->publish_state(datapoint.value_int); + this->publish_state(datapoint.value_int / multiply_by_); } else if (datapoint.type == TuyaDatapointType::ENUM) { ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum); this->publish_state(datapoint.value_enum); @@ -22,7 +22,8 @@ void TuyaNumber::setup() { void TuyaNumber::control(float value) { ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value); if (this->type_ == TuyaDatapointType::INTEGER) { - this->parent_->set_integer_datapoint_value(this->number_id_, value); + int integer_value = lround(value * multiply_by_); + this->parent_->set_integer_datapoint_value(this->number_id_, integer_value); } else if (this->type_ == TuyaDatapointType::ENUM) { this->parent_->set_enum_datapoint_value(this->number_id_, value); } diff --git a/esphome/components/tuya/number/tuya_number.h b/esphome/components/tuya/number/tuya_number.h index 7cca9fc6469d..f64dac895772 100644 --- a/esphome/components/tuya/number/tuya_number.h +++ b/esphome/components/tuya/number/tuya_number.h @@ -12,6 +12,7 @@ class TuyaNumber : public number::Number, public Component { void setup() override; void dump_config() override; void set_number_id(uint8_t number_id) { this->number_id_ = number_id; } + void set_write_multiply(float factor) { multiply_by_ = factor; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } @@ -20,6 +21,7 @@ class TuyaNumber : public number::Number, public Component { Tuya *parent_; uint8_t number_id_{0}; + float multiply_by_{1.0}; TuyaDatapointType type_{}; }; diff --git a/esphome/components/tuya/sensor/tuya_sensor.cpp b/esphome/components/tuya/sensor/tuya_sensor.cpp index 1e39c1bc3573..673471a6cec2 100644 --- a/esphome/components/tuya/sensor/tuya_sensor.cpp +++ b/esphome/components/tuya/sensor/tuya_sensor.cpp @@ -1,5 +1,6 @@ #include "esphome/core/log.h" #include "tuya_sensor.h" +#include namespace esphome { namespace tuya { @@ -18,7 +19,7 @@ void TuyaSensor::setup() { ESP_LOGV(TAG, "MCU reported sensor %u is: %u", datapoint.id, datapoint.value_enum); this->publish_state(datapoint.value_enum); } else if (datapoint.type == TuyaDatapointType::BITMASK) { - ESP_LOGV(TAG, "MCU reported sensor %u is: %x", datapoint.id, datapoint.value_bitmask); + ESP_LOGV(TAG, "MCU reported sensor %u is: %" PRIx32, datapoint.id, datapoint.value_bitmask); this->publish_state(datapoint.value_bitmask); } }); diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index 602595e89d32..fbe511811f2b 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -1,5 +1,5 @@ -#include "esphome/core/log.h" #include "tuya_text_sensor.h" +#include "esphome/core/log.h" namespace esphome { namespace tuya { @@ -19,6 +19,12 @@ void TuyaTextSensor::setup() { this->publish_state(data); 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); + break; + } default: ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, (uint8_t) datapoint.type); break; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index daf5080e7a27..1cc9681d091d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -23,8 +23,8 @@ static const int MAX_RETRIES = 5; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); - if (this->status_pin_.has_value()) { - this->status_pin_.value()->digital_write(false); + if (this->status_pin_ != nullptr) { + this->status_pin_->digital_write(false); } } @@ -61,7 +61,7 @@ void Tuya::dump_config() { } else if (info.type == TuyaDatapointType::ENUM) { ESP_LOGCONFIG(TAG, " Datapoint %u: enum (value: %d)", info.id, info.value_enum); } else if (info.type == TuyaDatapointType::BITMASK) { - ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %x)", info.id, info.value_bitmask); + ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %" PRIx32 ")", info.id, info.value_bitmask); } else { ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id); } @@ -70,9 +70,7 @@ void Tuya::dump_config() { ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_, this->reset_pin_reported_); } - if (this->status_pin_.has_value()) { - LOG_PIN(" Status Pin: ", this->status_pin_.value()); - } + LOG_PIN(" Status Pin: ", this->status_pin_); ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str()); } @@ -194,7 +192,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->init_state_ = TuyaInitState::INIT_DATAPOINT; this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); bool is_pin_equals = - this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_; + this->status_pin_ != nullptr && this->status_pin_->get_pin() == this->status_pin_reported_; // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send if (is_pin_equals) { ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_); @@ -244,13 +242,12 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff break; case TuyaCommandType::LOCAL_TIME_QUERY: #ifdef USE_TIME - if (this->time_id_.has_value()) { + if (this->time_id_ != nullptr) { this->send_local_time_(); if (!this->time_sync_callback_registered_) { // tuya mcu supports time, so we let them know when our time changed - auto *time_id = *this->time_id_; - time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); + this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); }); this->time_sync_callback_registered_ = true; } } else @@ -342,7 +339,7 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size); return; } - ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); + ESP_LOGD(TAG, "Datapoint %u update to %#08" PRIX32, datapoint.id, datapoint.value_bitmask); break; default: ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); @@ -463,7 +460,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) { void Tuya::set_status_pin_() { bool is_network_ready = network::is_connected() && remote_is_connected(); - this->status_pin_.value()->digital_write(is_network_ready); + this->status_pin_->digital_write(is_network_ready); } uint8_t Tuya::get_wifi_status_code_() { @@ -511,8 +508,7 @@ void Tuya::send_wifi_status_() { #ifdef USE_TIME void Tuya::send_local_time_() { std::vector payload; - auto *time_id = *this->time_id_; - ESPTime now = time_id->now(); + ESPTime now = this->time_id_->now(); if (now.is_valid()) { uint8_t year = now.year - 2000; uint8_t month = now.month; @@ -594,7 +590,7 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, uint8_t length, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); + ESP_LOGD(TAG, "Setting datapoint %u to %" PRIu32, datapoint_id, value); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 26f6f6591258..7dc405e3dd9f 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" @@ -10,8 +13,6 @@ #include "esphome/core/time.h" #endif -#include - namespace esphome { namespace tuya { @@ -129,14 +130,14 @@ class Tuya : public Component, public uart::UARTDevice { #ifdef USE_TIME void send_local_time_(); - optional time_id_{}; + time::RealTimeClock *time_id_{nullptr}; bool time_sync_callback_registered_{false}; #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; bool init_failed_{false}; int init_retries_{0}; uint8_t protocol_version_ = -1; - optional status_pin_{}; + InternalGPIOPin *status_pin_{nullptr}; int status_pin_reported_ = -1; int reset_pin_reported_ = -1; uint32_t last_command_timestamp_ = 0; diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 36f2bb585129..088227afe5dd 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -46,11 +46,20 @@ "LibreTinyUARTComponent", UARTComponent, cg.Component ) +NATIVE_UART_CLASSES = ( + str(IDFUARTComponent), + str(ESP32ArduinoUARTComponent), + str(ESP8266UartComponent), + str(RP2040UartComponent), + str(LibreTinyUARTComponent), +) + UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action) UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component) MULTI_CONF = True +MULTI_CONF_NO_DEFAULT = True def validate_raw_data(value): @@ -75,12 +84,13 @@ def validate_rx_pin(value): def validate_invert_esp32(config): if ( CORE.is_esp32 + and CORE.using_arduino and CONF_TX_PIN in config and CONF_RX_PIN in config and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] ): raise cv.Invalid( - "Different invert values for TX and RX pin are not (yet) supported for ESP32." + "Different invert values for TX and RX pin are not supported for ESP32 when using Arduino." ) return config @@ -298,17 +308,18 @@ def validate_stop_bits(value): def validate_hub(hub_config): hub_schema = {} uart_id = hub_config[CONF_ID] + uart_id_type_str = str(uart_id.type) devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) device = devices.setdefault(uart_id, {}) - if require_tx: + if require_tx and uart_id_type_str in NATIVE_UART_CLASSES: hub_schema[ cv.Required( CONF_TX_PIN, msg=f"Component {name} requires this uart bus to declare a tx_pin", ) ] = validate_pin(CONF_TX_PIN, device) - if require_rx: + if require_rx and uart_id_type_str in NATIVE_UART_CLASSES: hub_schema[ cv.Required( CONF_RX_PIN, diff --git a/esphome/components/uart/button/__init__.py b/esphome/components/uart/button/__init__.py new file mode 100644 index 000000000000..05909516a0bd --- /dev/null +++ b/esphome/components/uart/button/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button, uart +from esphome.const import CONF_DATA +from esphome.core import HexInt +from .. import uart_ns, validate_raw_data + +CODEOWNERS = ["@ssieb"] + +DEPENDENCIES = ["uart"] + +UARTButton = uart_ns.class_("UARTButton", button.Button, uart.UARTDevice, cg.Component) + + +CONFIG_SCHEMA = ( + button.button_schema(UARTButton) + .extend( + { + cv.Required(CONF_DATA): validate_raw_data, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await button.new_button(config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + data = config[CONF_DATA] + if isinstance(data, bytes): + data = [HexInt(x) for x in data] + cg.add(var.set_data(data)) diff --git a/esphome/components/uart/button/uart_button.cpp b/esphome/components/uart/button/uart_button.cpp new file mode 100644 index 000000000000..4db164c400d9 --- /dev/null +++ b/esphome/components/uart/button/uart_button.cpp @@ -0,0 +1,17 @@ +#include "uart_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.button"; + +void UARTButton::press_action() { + ESP_LOGD(TAG, "'%s': Sending data...", this->get_name().c_str()); + this->write_array(this->data_.data(), this->data_.size()); +} + +void UARTButton::dump_config() { LOG_BUTTON("", "UART Button", this); } + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/button/uart_button.h b/esphome/components/uart/button/uart_button.h new file mode 100644 index 000000000000..2d600b199ae6 --- /dev/null +++ b/esphome/components/uart/button/uart_button.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/button/button.h" + +#include + +namespace esphome { +namespace uart { + +class UARTButton : public button::Button, public UARTDevice, public Component { + public: + void set_data(const std::vector &data) { this->data_ = data; } + + void dump_config() override; + + protected: + void press_action() override; + std::vector data_; +}; + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/switch/__init__.py b/esphome/components/uart/switch/__init__.py index 60f5ddaf0d52..8853a61ae306 100644 --- a/esphome/components/uart/switch/__init__.py +++ b/esphome/components/uart/switch/__init__.py @@ -9,14 +9,24 @@ UARTSwitch = uart_ns.class_("UARTSwitch", switch.Switch, uart.UARTDevice, cg.Component) +CONF_TURN_OFF = "turn_off" +CONF_TURN_ON = "turn_on" CONFIG_SCHEMA = ( switch.switch_schema(UARTSwitch, block_inverted=True) .extend( { - cv.Required(CONF_DATA): validate_raw_data, + cv.Required(CONF_DATA): cv.Any( + validate_raw_data, + cv.Schema( + { + cv.Optional(CONF_TURN_OFF): validate_raw_data, + cv.Optional(CONF_TURN_ON): validate_raw_data, + } + ), + ), cv.Optional(CONF_SEND_EVERY): cv.positive_time_period_milliseconds, - } + }, ) .extend(uart.UART_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) @@ -29,9 +39,20 @@ async def to_code(config): await uart.register_uart_device(var, config) data = config[CONF_DATA] - if isinstance(data, bytes): - data = [HexInt(x) for x in data] - cg.add(var.set_data(data)) - + if isinstance(data, dict): + if data_on := data.get(CONF_TURN_ON): + if isinstance(data_on, bytes): + data_on = [HexInt(x) for x in data_on] + cg.add(var.set_data_on(data_on)) + if data_off := data.get(CONF_TURN_OFF): + if isinstance(data_off, bytes): + data_off = [HexInt(x) for x in data_off] + cg.add(var.set_data_off(data_off)) + else: + data = config[CONF_DATA] + if isinstance(data, bytes): + data = [HexInt(x) for x in data] + cg.add(var.set_data_on(data)) + cg.add(var.set_single_state(True)) if CONF_SEND_EVERY in config: cg.add(var.set_send_every(config[CONF_SEND_EVERY])) diff --git a/esphome/components/uart/switch/uart_switch.cpp b/esphome/components/uart/switch/uart_switch.cpp index ffed08c731ae..1edb54641b04 100644 --- a/esphome/components/uart/switch/uart_switch.cpp +++ b/esphome/components/uart/switch/uart_switch.cpp @@ -7,28 +7,41 @@ namespace uart { static const char *const TAG = "uart.switch"; void UARTSwitch::loop() { - if (this->state && this->send_every_) { + if (this->send_every_) { const uint32_t now = millis(); if (now - this->last_transmission_ > this->send_every_) { - this->write_command_(); + this->write_command_(this->state); this->last_transmission_ = now; } } } -void UARTSwitch::write_command_() { - ESP_LOGD(TAG, "'%s': Sending data...", this->get_name().c_str()); - this->write_array(this->data_.data(), this->data_.size()); +void UARTSwitch::write_command_(bool state) { + if (state && !this->data_on_.empty()) { + ESP_LOGD(TAG, "'%s': Sending on data...", this->get_name().c_str()); + this->write_array(this->data_on_.data(), this->data_on_.size()); + } + if (!state && !this->data_off_.empty()) { + ESP_LOGD(TAG, "'%s': Sending off data...", this->get_name().c_str()); + this->write_array(this->data_off_.data(), this->data_off_.size()); + } } void UARTSwitch::write_state(bool state) { + if (!this->single_state_) { + this->publish_state(state); + this->write_command_(state); + this->last_transmission_ = millis(); + return; + } + if (!state) { this->publish_state(false); return; } this->publish_state(true); - this->write_command_(); + this->write_command_(true); if (this->send_every_ == 0) { this->publish_state(false); @@ -36,10 +49,11 @@ void UARTSwitch::write_state(bool state) { this->last_transmission_ = millis(); } } + void UARTSwitch::dump_config() { LOG_SWITCH("", "UART Switch", this); if (this->send_every_) { - ESP_LOGCONFIG(TAG, " Send Every: %u", this->send_every_); + ESP_LOGCONFIG(TAG, " Send Every: %" PRIu32, this->send_every_); } } diff --git a/esphome/components/uart/switch/uart_switch.h b/esphome/components/uart/switch/uart_switch.h index 4f24d76d0c1f..4ef5b6da4b03 100644 --- a/esphome/components/uart/switch/uart_switch.h +++ b/esphome/components/uart/switch/uart_switch.h @@ -4,6 +4,7 @@ #include "esphome/components/uart/uart.h" #include "esphome/components/switch/switch.h" +#include #include namespace esphome { @@ -13,15 +14,19 @@ class UARTSwitch : public switch_::Switch, public UARTDevice, public Component { public: void loop() override; - void set_data(const std::vector &data) { data_ = data; } + void set_data_on(const std::vector &data) { this->data_on_ = data; } + void set_data_off(const std::vector &data) { this->data_off_ = data; } void set_send_every(uint32_t send_every) { this->send_every_ = send_every; } + void set_single_state(bool single) { this->single_state_ = single; } void dump_config() override; protected: - void write_command_(); + void write_command_(bool state); void write_state(bool state) override; - std::vector data_; + std::vector data_on_; + std::vector data_off_; + bool single_state_{false}; uint32_t send_every_; uint32_t last_transmission_; }; diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 42702cf5b8f0..a57910c1a10d 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -31,38 +31,124 @@ const LogString *parity_to_str(UARTParityOptions parity); class UARTComponent { public: + // Writes an array of bytes to the UART bus. + // @param data A vector of bytes to be written. void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } + + // Writes a single byte to the UART bus. + // @param data The byte to be written. void write_byte(uint8_t data) { this->write_array(&data, 1); }; + + // Writes a null-terminated string to the UART bus. + // @param str Pointer to the null-terminated string. void write_str(const char *str) { const auto *data = reinterpret_cast(str); this->write_array(data, strlen(str)); }; + // Pure virtual method to write an array of bytes to the UART bus. + // @param data Pointer to the array of bytes. + // @param len Length of the array. virtual void write_array(const uint8_t *data, size_t len) = 0; + // Reads a single byte from the UART bus. + // @param data Pointer to the byte where the read data will be stored. + // @return True if a byte was successfully read, false otherwise. bool read_byte(uint8_t *data) { return this->read_array(data, 1); }; + + // Pure virtual method to peek the next byte in the UART buffer without removing it. + // @param data Pointer to the byte where the peeked data will be stored. + // @return True if a byte is available to peek, false otherwise. virtual bool peek_byte(uint8_t *data) = 0; + + // Pure virtual method to read an array of bytes from the UART bus. + // @param data Pointer to the array where the read data will be stored. + // @param len Number of bytes to read. + // @return True if the specified number of bytes were successfully read, false otherwise. virtual bool read_array(uint8_t *data, size_t len) = 0; - /// Return available number of bytes. + // Pure virtual method to return the number of bytes available for reading. + // @return Number of available bytes. virtual int available() = 0; - /// Block until all bytes have been written to the UART bus. + + // Pure virtual method to block until all bytes have been written to the UART bus. virtual void flush() = 0; + // Sets the TX (transmit) pin for the UART bus. + // @param tx_pin Pointer to the internal GPIO pin used for transmission. void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } + + // Sets the RX (receive) pin for the UART bus. + // @param rx_pin Pointer to the internal GPIO pin used for reception. void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } + + // Sets the size of the RX buffer. + // @param rx_buffer_size Size of the RX buffer in bytes. void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + + // Gets the size of the RX buffer. + // @return Size of the RX buffer in bytes. size_t get_rx_buffer_size() { return this->rx_buffer_size_; } + // Sets the number of stop bits used in UART communication. + // @param stop_bits Number of stop bits. void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } + + // Gets the number of stop bits used in UART communication. + // @return Number of stop bits. uint8_t get_stop_bits() const { return this->stop_bits_; } + + // Set the number of data bits used in UART communication. + // @param data_bits Number of data bits. void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } + + // Get the number of data bits used in UART communication. + // @return Number of data bits. uint8_t get_data_bits() const { return this->data_bits_; } + + // Set the parity used in UART communication. + // @param parity Parity option. void set_parity(UARTParityOptions parity) { this->parity_ = parity; } + + // Get the parity used in UART communication. + // @return Parity option. UARTParityOptions get_parity() const { return this->parity_; } + + // Set the baud rate for UART communication. + // @param baud_rate Baud rate in bits per second. void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + + // Get the baud rate for UART communication. + // @return Baud rate in bits per second. uint32_t get_baud_rate() const { return baud_rate_; } +#if defined(USE_ESP8266) || defined(USE_ESP32) + /** + * Load the UART settings. + * @param dump_config If true (default), output the new settings to logs; otherwise, change settings quietly. + * + * Example: + * ```cpp + * id(uart1).load_settings(false); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + virtual void load_settings(bool dump_config){}; + + /** + * Load the UART settings. + * + * Example: + * ```cpp + * id(uart1).load_settings(); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + virtual void load_settings(){}; +#endif // USE_ESP8266 || USE_ESP32 + #ifdef USE_UART_DEBUGGER void add_debug_callback(std::function &&callback) { this->debug_callback_.add(std::move(callback)); diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 7306dd2f3143..f77783e20e82 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -88,7 +88,11 @@ void ESP32ArduinoUARTComponent::setup() { #endif static uint8_t next_uart_num = 0; if (is_default_tx && is_default_rx && next_uart_num == 0) { +#if ARDUINO_USB_CDC_ON_BOOT + this->hw_serial_ = &Serial0; +#else this->hw_serial_ = &Serial; +#endif next_uart_num++; } else { #ifdef USE_LOGGER @@ -109,6 +113,11 @@ void ESP32ArduinoUARTComponent::setup() { this->number_ = next_uart_num; this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) } + + this->load_settings(false); +} + +void ESP32ArduinoUARTComponent::load_settings(bool dump_config) { 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; bool invert = false; @@ -118,6 +127,10 @@ void ESP32ArduinoUARTComponent::setup() { invert = true; this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); + if (dump_config) { + ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->number_); + this->dump_config(); + } } void ESP32ArduinoUARTComponent::dump_config() { diff --git a/esphome/components/uart/uart_component_esp32_arduino.h b/esphome/components/uart/uart_component_esp32_arduino.h index 02dfd0531e56..de17d9718b86 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.h +++ b/esphome/components/uart/uart_component_esp32_arduino.h @@ -32,6 +32,21 @@ class ESP32ArduinoUARTComponent : public UARTComponent, public Component { HardwareSerial *get_hw_serial() { return this->hw_serial_; } uint8_t get_hw_serial_number() { return this->number_; } + /** + * Load the UART with the current settings. + * @param dump_config (Optional, default `true`): True for displaying new settings or + * false to change it quitely + * + * Example: + * ```cpp + * id(uart1).load_settings(); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + void load_settings(bool dump_config) override; + void load_settings() override { this->load_settings(true); } + protected: void check_logger_conflict() override; diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 529108f439f3..fa8dc3fb1703 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -98,10 +98,26 @@ void ESP8266UartComponent::setup() { } } +void ESP8266UartComponent::load_settings(bool dump_config) { + ESP_LOGCONFIG(TAG, "Loading UART bus settings..."); + if (this->hw_serial_ != nullptr) { + SerialConfig config = static_cast(get_config()); + this->hw_serial_->begin(this->baud_rate_, config); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + } else { + this->sw_serial_->setup(this->tx_pin_, this->rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, + this->parity_, this->rx_buffer_size_); + } + if (dump_config) { + ESP_LOGCONFIG(TAG, "UART bus was reloaded."); + this->dump_config(); + } +} + void ESP8266UartComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - LOG_PIN(" TX Pin: ", tx_pin_); - LOG_PIN(" RX Pin: ", rx_pin_); + LOG_PIN(" TX Pin: ", this->tx_pin_); + LOG_PIN(" RX Pin: ", this->rx_pin_); if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT } diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h index eed14f3265f5..749dd4c61ef1 100644 --- a/esphome/components/uart/uart_component_esp8266.h +++ b/esphome/components/uart/uart_component_esp8266.h @@ -63,6 +63,21 @@ class ESP8266UartComponent : public UARTComponent, public Component { uint32_t get_config(); + /** + * Load the UART with the current settings. + * @param dump_config (Optional, default `true`): True for displaying new settings or + * false to change it quitely + * + * Example: + * ```cpp + * id(uart1).load_settings(); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + void load_settings(bool dump_config) override; + void load_settings() override { this->load_settings(true); } + protected: void check_logger_conflict() override; diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index ae772fa8f8ca..2dd6ab105fa1 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -48,7 +48,11 @@ uart_config_t IDFUARTComponent::get_config_() { uart_config.parity = parity; uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + uart_config.source_clk = UART_SCLK_DEFAULT; +#else uart_config.source_clk = UART_SCLK_APB; +#endif uart_config.rx_flow_ctrl_thresh = 122; return uart_config; @@ -80,21 +84,22 @@ void IDFUARTComponent::setup() { 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); + 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; + + uint32_t invert = 0; + 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()) + invert |= UART_SIGNAL_RXD_INV; + + err = uart_set_line_inverse(this->uart_num_, invert); if (err != ESP_OK) { - ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); this->mark_failed(); 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; - err = uart_set_pin(this->uart_num_, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err)); @@ -102,15 +107,14 @@ void IDFUARTComponent::setup() { return; } - uint32_t invert = 0; - 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()) - invert |= UART_SIGNAL_RXD_INV; - - err = uart_set_line_inverse(this->uart_num_, invert); + 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_set_line_inverse failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } @@ -118,9 +122,21 @@ void IDFUARTComponent::setup() { xSemaphoreGive(this->lock_); } +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); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } else if (dump_config) { + ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_); + this->dump_config(); + } +} + void IDFUARTComponent::dump_config() { - ESP_LOGCONFIG(TAG, "UART Bus:"); - ESP_LOGCONFIG(TAG, " Number: %u", this->uart_num_); + ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_); LOG_PIN(" TX Pin: ", tx_pin_); LOG_PIN(" RX Pin: ", rx_pin_); if (this->rx_pin_ != nullptr) { diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index fdaa4da9a79c..215641ebe231 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -26,6 +26,21 @@ class IDFUARTComponent : public UARTComponent, public Component { uint8_t get_hw_serial_number() { return this->uart_num_; } QueueHandle_t *get_uart_event_queue() { return &this->uart_event_queue_; } + /** + * Load the UART with the current settings. + * @param dump_config (Optional, default `true`): True for displaying new settings or + * false to change it quitely + * + * Example: + * ```cpp + * id(uart1).load_settings(); + * ``` + * + * This will load the current UART interface with the latest settings (baud_rate, parity, etc). + */ + void load_settings(bool dump_config) override; + void load_settings() override { this->load_settings(true); } + protected: void check_logger_conflict() override; uart_port_t uart_num_; diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index 9f47f9f6b967..604e78d6f8a5 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -30,14 +30,14 @@ void UltrasonicSensorComponent::update() { ; const uint32_t pulse_end = micros(); - ESP_LOGV(TAG, "Echo took %uµs", pulse_end - pulse_start); + 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); - ESP_LOGD(TAG, "'%s' - Got distance: %.2f m", this->name_.c_str(), result); + ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result); this->publish_state(result); } } @@ -45,8 +45,8 @@ 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: %u µs", this->pulse_time_us_); - ESP_LOGCONFIG(TAG, " Timeout: %u µs", this->timeout_us_); + ESP_LOGCONFIG(TAG, " Pulse time: %" PRIu32 " µs", this->pulse_time_us_); + ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " µs", this->timeout_us_); LOG_UPDATE_INTERVAL(this); } float UltrasonicSensorComponent::us_to_m(uint32_t us) { diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index e0d71b99ef72..1a255d612270 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -4,6 +4,8 @@ #include "esphome/core/gpio.h" #include "esphome/components/sensor/sensor.h" +#include + namespace esphome { namespace ultrasonic { diff --git a/esphome/components/uponor_smatrix/__init__.py b/esphome/components/uponor_smatrix/__init__.py new file mode 100644 index 000000000000..35c4c4cecd4e --- /dev/null +++ b/esphome/components/uponor_smatrix/__init__.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, time +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_TIME_ID, +) + +CODEOWNERS = ["@kroimon"] + +DEPENDENCIES = ["uart"] + +MULTI_CONF = True + +uponor_smatrix_ns = cg.esphome_ns.namespace("uponor_smatrix") +UponorSmatrixComponent = uponor_smatrix_ns.class_( + "UponorSmatrixComponent", cg.Component, uart.UARTDevice +) +UponorSmatrixDevice = uponor_smatrix_ns.class_( + "UponorSmatrixDevice", cg.Parented.template(UponorSmatrixComponent) +) + +CONF_UPONOR_SMATRIX_ID = "uponor_smatrix_id" +CONF_TIME_DEVICE_ADDRESS = "time_device_address" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixComponent), + cv.Optional(CONF_ADDRESS): cv.hex_uint16_t, + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional(CONF_TIME_DEVICE_ADDRESS): cv.hex_uint16_t, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "uponor_smatrix", + baud_rate=19200, + require_tx=True, + require_rx=True, + data_bits=8, + parity=None, + stop_bits=1, +) + +# A schema to use for all Uponor Smatrix devices +UPONOR_SMATRIX_DEVICE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_UPONOR_SMATRIX_ID): cv.use_id(UponorSmatrixComponent), + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + } +) + + +async def to_code(config): + cg.add_global(uponor_smatrix_ns.using) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if address := config.get(CONF_ADDRESS): + cg.add(var.set_system_address(address)) + if time_id := config.get(CONF_TIME_ID): + time_ = await cg.get_variable(time_id) + cg.add(var.set_time_id(time_)) + if time_device_address := config.get(CONF_TIME_DEVICE_ADDRESS): + cg.add(var.set_time_device_address(time_device_address)) + + +async def register_uponor_smatrix_device(var, config): + parent = await cg.get_variable(config[CONF_UPONOR_SMATRIX_ID]) + cg.add(var.set_parent(parent)) + cg.add(var.set_device_address(config[CONF_ADDRESS])) + cg.add(parent.register_device(var)) diff --git a/esphome/components/uponor_smatrix/climate/__init__.py b/esphome/components/uponor_smatrix/climate/__init__.py new file mode 100644 index 000000000000..0becec2624c2 --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate +from esphome.const import CONF_ID + +from .. import ( + uponor_smatrix_ns, + UponorSmatrixDevice, + UPONOR_SMATRIX_DEVICE_SCHEMA, + register_uponor_smatrix_device, +) + +DEPENDENCIES = ["uponor_smatrix"] + +UponorSmatrixClimate = uponor_smatrix_ns.class_( + "UponorSmatrixClimate", + climate.Climate, + cg.Component, + UponorSmatrixDevice, +) + +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixClimate), + } +).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await climate.register_climate(var, config) + await register_uponor_smatrix_device(var, config) diff --git a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp new file mode 100644 index 000000000000..5afc628db33e --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp @@ -0,0 +1,101 @@ +#include "uponor_smatrix_climate.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix.climate"; + +void UponorSmatrixClimate::dump_config() { + LOG_CLIMATE("", "Uponor Smatrix Climate", this); + ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_); +} + +void UponorSmatrixClimate::loop() { + const uint32_t now = millis(); + + // Publish state after all update packets are processed + if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) { + float temp = raw_to_celsius((this->preset == climate::CLIMATE_PRESET_ECO) + ? (this->target_temperature_raw_ - this->eco_setback_value_raw_) + : this->target_temperature_raw_); + float step = this->get_traits().get_visual_target_temperature_step(); + this->target_temperature = roundf(temp / step) * step; + this->publish_state(); + this->last_data_ = 0; + } +} + +climate::ClimateTraits UponorSmatrixClimate::traits() { + auto traits = climate::ClimateTraits(); + traits.set_supports_current_temperature(true); + traits.set_supports_current_humidity(true); + traits.set_supported_modes({climate::CLIMATE_MODE_HEAT}); + traits.set_supports_action(true); + traits.set_supported_presets({climate::CLIMATE_PRESET_ECO}); + traits.set_visual_min_temperature(this->min_temperature_); + traits.set_visual_max_temperature(this->max_temperature_); + traits.set_visual_current_temperature_step(0.1f); + traits.set_visual_target_temperature_step(0.5f); + return traits; +} + +void UponorSmatrixClimate::control(const climate::ClimateCall &call) { + if (call.get_target_temperature().has_value()) { + uint16_t temp = celsius_to_raw(*call.get_target_temperature()); + if (this->preset == climate::CLIMATE_PRESET_ECO) { + // During ECO mode, the thermostat automatically substracts the setback value from the setpoint, + // so we need to add it here first + temp += this->eco_setback_value_raw_; + } + + // For unknown reasons, we need to send a null setpoint first for the thermostat to react + UponorSmatrixData data[] = {{UPONOR_ID_TARGET_TEMP, 0}, {UPONOR_ID_TARGET_TEMP, temp}}; + this->send(data, sizeof(data) / sizeof(data[0])); + } +} + +void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) { + for (int i = 0; i < data_len; i++) { + switch (data[i].id) { + case UPONOR_ID_TARGET_TEMP_MIN: + this->min_temperature_ = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_TARGET_TEMP_MAX: + this->max_temperature_ = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_TARGET_TEMP: + // Ignore invalid values here as they are used by the controller to explicitely request the setpoint from a + // thermostat + if (data[i].value != UPONOR_INVALID_VALUE) + this->target_temperature_raw_ = data[i].value; + break; + case UPONOR_ID_ECO_SETBACK: + this->eco_setback_value_raw_ = data[i].value; + break; + case UPONOR_ID_DEMAND: + if (data[i].value & 0x1000) { + this->mode = climate::CLIMATE_MODE_COOL; + this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_COOLING : climate::CLIMATE_ACTION_IDLE; + } else { + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_HEATING : climate::CLIMATE_ACTION_IDLE; + } + break; + case UPONOR_ID_MODE1: + this->set_preset_((data[i].value & 0x0008) ? climate::CLIMATE_PRESET_ECO : climate::CLIMATE_PRESET_NONE); + break; + case UPONOR_ID_ROOM_TEMP: + this->current_temperature = raw_to_celsius(data[i].value); + break; + case UPONOR_ID_HUMIDITY: + this->current_humidity = data[i].value & 0x00FF; + } + } + + this->last_data_ = millis(); +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h new file mode 100644 index 000000000000..b8458045c61a --- /dev/null +++ b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/climate/climate.h" +#include "esphome/components/uponor_smatrix/uponor_smatrix.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace uponor_smatrix { + +class UponorSmatrixClimate : public climate::Climate, public Component, public UponorSmatrixDevice { + public: + void dump_config() override; + void loop() override; + + protected: + climate::ClimateTraits traits() override; + void control(const climate::ClimateCall &call) override; + void on_device_data(const UponorSmatrixData *data, size_t data_len) override; + + uint32_t last_data_; + float min_temperature_{5.0f}; + float max_temperature_{35.0f}; + uint16_t eco_setback_value_raw_{0x0048}; + uint16_t target_temperature_raw_; +}; + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/sensor/__init__.py b/esphome/components/uponor_smatrix/sensor/__init__.py new file mode 100644 index 000000000000..89097aef18d4 --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_EXTERNAL_TEMPERATURE, + CONF_HUMIDITY, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +from .. import ( + uponor_smatrix_ns, + UponorSmatrixDevice, + UPONOR_SMATRIX_DEVICE_SCHEMA, + register_uponor_smatrix_device, +) + +DEPENDENCIES = ["uponor_smatrix"] + +UponorSmatrixSensor = uponor_smatrix_ns.class_( + "UponorSmatrixSensor", + sensor.Sensor, + cg.Component, + UponorSmatrixDevice, +) + +CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(UponorSmatrixSensor), + 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, + ), + } +).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await register_uponor_smatrix_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): + sens = await sensor.new_sensor(external_temperature_config) + cg.add(var.set_external_temperature_sensor(sens)) + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp new file mode 100644 index 000000000000..2fd2a36efc50 --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp @@ -0,0 +1,37 @@ +#include "uponor_smatrix_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix.sensor"; + +void UponorSmatrixSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Uponor Smatrix Sensor"); + ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "External Temperature", this->external_temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) { + for (int i = 0; i < data_len; i++) { + switch (data[i].id) { + case UPONOR_ID_ROOM_TEMP: + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(raw_to_celsius(data[i].value)); + break; + case UPONOR_ID_EXTERNAL_TEMP: + if (this->external_temperature_sensor_ != nullptr) + this->external_temperature_sensor_->publish_state(raw_to_celsius(data[i].value)); + break; + case UPONOR_ID_HUMIDITY: + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(data[i].value & 0x00FF); + break; + } + } +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h new file mode 100644 index 000000000000..5e38117a219e --- /dev/null +++ b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uponor_smatrix/uponor_smatrix.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace uponor_smatrix { + +class UponorSmatrixSensor : public sensor::Sensor, public Component, public UponorSmatrixDevice { + SUB_SENSOR(temperature) + SUB_SENSOR(external_temperature) + SUB_SENSOR(humidity) + + public: + void dump_config() override; + + protected: + void on_device_data(const UponorSmatrixData *data, size_t data_len) override; +}; + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp new file mode 100644 index 000000000000..a7014dc96c6b --- /dev/null +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -0,0 +1,229 @@ +#include "uponor_smatrix.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uponor_smatrix { + +static const char *const TAG = "uponor_smatrix"; + +void UponorSmatrixComponent::setup() { +#ifdef USE_TIME + if (this->time_id_ != nullptr) { + this->time_id_->add_on_time_sync_callback([this] { this->send_time(); }); + } +#endif +} + +void UponorSmatrixComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Uponor Smatrix"); + ESP_LOGCONFIG(TAG, " System address: 0x%04X", this->address_); +#ifdef USE_TIME + if (this->time_id_ != nullptr) { + ESP_LOGCONFIG(TAG, " Time synchronization: YES"); + ESP_LOGCONFIG(TAG, " Time master device address: 0x%04X", this->time_device_address_); + } +#endif + + this->check_uart_settings(19200); + + if (!this->unknown_devices_.empty()) { + ESP_LOGCONFIG(TAG, " Detected unknown device addresses:"); + for (auto device_address : this->unknown_devices_) { + ESP_LOGCONFIG(TAG, " 0x%04X", device_address); + } + } +} + +void UponorSmatrixComponent::loop() { + const uint32_t now = millis(); + + // Discard stale data + if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) { + ESP_LOGD(TAG, "Discarding %d bytes of unparsed data", this->rx_buffer_.size()); + this->rx_buffer_.clear(); + } + + // Read incoming data + while (this->available()) { + // The controller polls devices every 10 seconds, with around 200 ms between devices. + // Remember timestamps so we can send our own packets when the bus is expected to be silent. + if (now - this->last_rx_ > 500) { + this->last_poll_start_ = now; + } + this->last_rx_ = now; + + uint8_t byte; + this->read_byte(&byte); + if (this->parse_byte_(byte)) { + this->rx_buffer_.clear(); + } + } + + // Send packets during bus silence + if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) { +#ifdef USE_TIME + // Only build time packet when bus is silent and queue is empty to make sure we can send it right away + if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_()) + this->send_time_requested_ = false; +#endif + // Send the next packet in the queue + if (!this->tx_queue_.empty()) { + auto packet = std::move(this->tx_queue_.front()); + this->tx_queue_.pop(); + + this->write_array(packet); + this->flush(); + + this->last_tx_ = now; + } + } +} + +bool UponorSmatrixComponent::parse_byte_(uint8_t byte) { + this->rx_buffer_.push_back(byte); + const uint8_t *packet = this->rx_buffer_.data(); + size_t packet_len = this->rx_buffer_.size(); + + if (packet_len < 7) { + // Minimum packet size is 7 bytes, wait for more + return false; + } + + uint16_t system_address = encode_uint16(packet[0], packet[1]); + uint16_t device_address = encode_uint16(packet[2], packet[3]); + uint16_t crc = encode_uint16(packet[packet_len - 1], packet[packet_len - 2]); + + uint16_t computed_crc = crc16(packet, packet_len - 2); + if (crc != computed_crc) { + // CRC did not match, more data might be coming + return false; + } + + ESP_LOGV(TAG, "Received packet: sys=%04X, dev=%04X, data=%s, crc=%04X", system_address, device_address, + format_hex(&packet[4], packet_len - 6).c_str(), crc); + + // Detect or check system address + if (this->address_ == 0) { + ESP_LOGI(TAG, "Using detected system address 0x%04X", system_address); + this->address_ = system_address; + } else if (this->address_ != system_address) { + // This should never happen except if the system address was set or detected incorrectly, so warn the user. + ESP_LOGW(TAG, "Received packet from unknown system address 0x%04X", system_address); + return true; + } + + // Handle packet + size_t data_len = (packet_len - 6) / 3; + if (data_len == 0) { + if (packet[4] == UPONOR_ID_REQUEST) + ESP_LOGVV(TAG, "Ignoring request packet for device 0x%04X", device_address); + return true; + } + + // Decode packet payload data for easy access + UponorSmatrixData data[data_len]; + for (int i = 0; i < data_len; i++) { + data[i].id = packet[(i * 3) + 4]; + data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]); + } + +#ifdef USE_TIME + // Detect device that acts as time master if not set explicitely + if (this->time_device_address_ == 0 && data_len >= 2) { + // The first thermostat paired to the controller will act as the time master. Time can only be manually adjusted at + // this first thermostat. To synchronize time, we need to know its address, so we search for packets coming from a + // thermostat sending both room temperature and time information. + bool found_temperature = false; + bool found_time = false; + for (int i = 0; i < data_len; i++) { + if (data[i].id == UPONOR_ID_ROOM_TEMP) + found_temperature = true; + if (data[i].id == UPONOR_ID_DATETIME1) + found_time = true; + if (found_temperature && found_time) { + ESP_LOGI(TAG, "Using detected time device address 0x%04X", device_address); + this->time_device_address_ = device_address; + break; + } + } + } +#endif + + // Forward data to device components + bool found = false; + for (auto *device : this->devices_) { + if (device->address_ == device_address) { + found = true; + device->on_device_data(data, data_len); + } + } + + // Log unknown device addresses + if (!found && !this->unknown_devices_.count(device_address)) { + ESP_LOGI(TAG, "Received packet for unknown device address 0x%04X ", device_address); + this->unknown_devices_.insert(device_address); + } + + // Return true to reset buffer + return true; +} + +bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len) { + if (this->address_ == 0 || device_address == 0 || data == nullptr || data_len == 0) + return false; + + // Assemble packet for send queue. All fields are big-endian except for the little-endian checksum. + std::vector packet; + packet.reserve(6 + 3 * data_len); + + packet.push_back(this->address_ >> 8); + packet.push_back(this->address_ >> 0); + packet.push_back(device_address >> 8); + packet.push_back(device_address >> 0); + + for (int i = 0; i < data_len; i++) { + packet.push_back(data[i].id); + packet.push_back(data[i].value >> 8); + packet.push_back(data[i].value >> 0); + } + + auto crc = crc16(packet.data(), packet.size()); + packet.push_back(crc >> 0); + packet.push_back(crc >> 8); + + this->tx_queue_.push(packet); + return true; +} + +#ifdef USE_TIME +bool UponorSmatrixComponent::do_send_time_() { + if (this->time_device_address_ == 0 || this->time_id_ == nullptr) + return false; + + ESPTime now = this->time_id_->now(); + if (!now.is_valid()) + return false; + + uint8_t year = now.year - 2000; + uint8_t month = now.month; + // ESPHome days are [1-7] starting with Sunday, Uponor days are [0-6] starting with Monday + uint8_t day_of_week = (now.day_of_week == 1) ? 6 : (now.day_of_week - 2); + uint8_t day_of_month = now.day_of_month; + uint8_t hour = now.hour; + uint8_t minute = now.minute; + uint8_t second = now.second; + + uint16_t time1 = (year & 0x7F) << 7 | (month & 0x0F) << 3 | (day_of_week & 0x07); + uint16_t time2 = (day_of_month & 0x1F) << 11 | (hour & 0x1F) << 6 | (minute & 0x3F); + uint16_t time3 = second; + + ESP_LOGI(TAG, "Sending local time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour, + now.minute, now.second); + + UponorSmatrixData data[] = {{UPONOR_ID_DATETIME1, time1}, {UPONOR_ID_DATETIME2, time2}, {UPONOR_ID_DATETIME3, time3}}; + return this->send(this->time_device_address_, data, sizeof(data) / sizeof(data[0])); +} +#endif + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.h b/esphome/components/uponor_smatrix/uponor_smatrix.h new file mode 100644 index 000000000000..b7667b5b87cf --- /dev/null +++ b/esphome/components/uponor_smatrix/uponor_smatrix.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +#include "esphome/core/defines.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#include "esphome/core/time.h" +#endif + +#include +#include +#include + +namespace esphome { +namespace uponor_smatrix { + +/// Date/Time Part 1 (year, month, day of week) +static const uint8_t UPONOR_ID_DATETIME1 = 0x08; +/// Date/Time Part 2 (day of month, hour, minute) +static const uint8_t UPONOR_ID_DATETIME2 = 0x09; +/// Date/Time Part 3 (seconds) +static const uint8_t UPONOR_ID_DATETIME3 = 0x0A; +/// Unknown (observed values: 0x0342, 0x0024) +static const uint8_t UPONOR_ID_UNKNOWN1 = 0x0C; +/// Outdoor Temperature? (sent by controller) +static const uint8_t UPONOR_ID_OUTDOOR_TEMP = 0x2D; +/// Unknown (observed values: 0x8000) +static const uint8_t UPONOR_ID_UNKNOWN2 = 0x35; +/// Room Temperature Setpoint Minimum +static const uint8_t UPONOR_ID_TARGET_TEMP_MIN = 0x37; +/// Room Temperature Setpoint Maximum +static const uint8_t UPONOR_ID_TARGET_TEMP_MAX = 0x38; +/// Room Temperature Setpoint +static const uint8_t UPONOR_ID_TARGET_TEMP = 0x3B; +/// Room Temperature Setpoint Setback for ECO Mode +static const uint8_t UPONOR_ID_ECO_SETBACK = 0x3C; +/// Heating/Cooling Demand +static const uint8_t UPONOR_ID_DEMAND = 0x3D; +/// Thermostat Operating Mode 1 (ECO state, program schedule state) +static const uint8_t UPONOR_ID_MODE1 = 0x3E; +/// Thermostat Operating Mode 2 (sensor configuration, heating/cooling allowed) +static const uint8_t UPONOR_ID_MODE2 = 0x3F; +/// Current Room Temperature +static const uint8_t UPONOR_ID_ROOM_TEMP = 0x40; +/// Current External (Floor/Outdoor) Sensor Temperature +static const uint8_t UPONOR_ID_EXTERNAL_TEMP = 0x41; +/// Current Room Humidity +static const uint8_t UPONOR_ID_HUMIDITY = 0x42; +/// Data Request (sent by controller) +static const uint8_t UPONOR_ID_REQUEST = 0xFF; + +/// Indicating an invalid/missing value +static const uint16_t UPONOR_INVALID_VALUE = 0x7FFF; + +struct UponorSmatrixData { + uint8_t id; + uint16_t value; +}; + +class UponorSmatrixDevice; + +class UponorSmatrixComponent : public uart::UARTDevice, public Component { + public: + UponorSmatrixComponent() = default; + + void setup() override; + void dump_config() override; + void loop() override; + + void set_system_address(uint16_t address) { this->address_ = address; } + void register_device(UponorSmatrixDevice *device) { this->devices_.push_back(device); } + + bool send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len); + +#ifdef USE_TIME + void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } + void set_time_device_address(uint16_t address) { this->time_device_address_ = address; } + void send_time() { this->send_time_requested_ = true; } +#endif + + protected: + bool parse_byte_(uint8_t byte); + + uint16_t address_; + std::vector devices_; + std::set unknown_devices_; + + std::vector rx_buffer_; + std::queue> tx_queue_; + uint32_t last_rx_; + uint32_t last_tx_; + uint32_t last_poll_start_; + +#ifdef USE_TIME + time::RealTimeClock *time_id_{nullptr}; + uint16_t time_device_address_; + bool send_time_requested_; + bool do_send_time_(); +#endif +}; + +class UponorSmatrixDevice : public Parented { + public: + void set_device_address(uint16_t address) { this->address_ = address; } + + virtual void on_device_data(const UponorSmatrixData *data, size_t data_len) = 0; + bool send(const UponorSmatrixData *data, size_t data_len) { + return this->parent_->send(this->address_, data, data_len); + } + + protected: + friend UponorSmatrixComponent; + uint16_t address_; +}; + +inline float raw_to_celsius(uint16_t raw) { + return (raw == UPONOR_INVALID_VALUE) ? NAN : fahrenheit_to_celsius(raw / 10.0f); +} + +inline uint16_t celsius_to_raw(float celsius) { + return std::isnan(celsius) ? UPONOR_INVALID_VALUE + : static_cast(lroundf(celsius_to_fahrenheit(celsius) * 10.0f)); +} + +} // namespace uponor_smatrix +} // namespace esphome diff --git a/esphome/components/valve/__init__.py b/esphome/components/valve/__init__.py new file mode 100644 index 000000000000..22d617cc36d7 --- /dev/null +++ b/esphome/components/valve/__init__.py @@ -0,0 +1,186 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id, Condition +from esphome.components import mqtt +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ID, + CONF_MQTT_ID, + CONF_ON_OPEN, + CONF_POSITION, + CONF_POSITION_COMMAND_TOPIC, + CONF_POSITION_STATE_TOPIC, + CONF_STATE, + CONF_STOP, + CONF_TRIGGER_ID, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +IS_PLATFORM_COMPONENT = True + +CODEOWNERS = ["@esphome/core"] + +valve_ns = cg.esphome_ns.namespace("valve") + +Valve = valve_ns.class_("Valve", cg.EntityBase) + +VALVE_OPEN = valve_ns.VALVE_OPEN +VALVE_CLOSED = valve_ns.VALVE_CLOSED + +VALVE_STATES = { + "OPEN": VALVE_OPEN, + "CLOSED": VALVE_CLOSED, +} +validate_valve_state = cv.enum(VALVE_STATES, upper=True) + +ValveOperation = valve_ns.enum("ValveOperation") +VALVE_OPERATIONS = { + "IDLE": ValveOperation.VALVE_OPERATION_IDLE, + "OPENING": ValveOperation.VALVE_OPERATION_OPENING, + "CLOSING": ValveOperation.VALVE_OPERATION_CLOSING, +} +validate_valve_operation = cv.enum(VALVE_OPERATIONS, upper=True) + +# Actions +OpenAction = valve_ns.class_("OpenAction", automation.Action) +CloseAction = valve_ns.class_("CloseAction", automation.Action) +StopAction = valve_ns.class_("StopAction", automation.Action) +ToggleAction = valve_ns.class_("ToggleAction", automation.Action) +ControlAction = valve_ns.class_("ControlAction", automation.Action) +ValvePublishAction = valve_ns.class_("ValvePublishAction", automation.Action) +ValveIsOpenCondition = valve_ns.class_("ValveIsOpenCondition", Condition) +ValveIsClosedCondition = valve_ns.class_("ValveIsClosedCondition", Condition) + +# Triggers +ValveOpenTrigger = valve_ns.class_("ValveOpenTrigger", automation.Trigger.template()) +ValveClosedTrigger = valve_ns.class_( + "ValveClosedTrigger", automation.Trigger.template() +) + +CONF_ON_CLOSED = "on_closed" + +VALVE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(Valve), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent), + cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger), + } + ), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger), + } + ), + } +) + + +async def setup_valve_core_(var, config): + await setup_entity(var, config) + + if device_class_config := config.get(CONF_DEVICE_CLASS): + cg.add(var.set_device_class(device_class_config)) + + for conf in config.get(CONF_ON_OPEN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CLOSED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + if mqtt_id_config := config.get(CONF_MQTT_ID): + mqtt_ = cg.new_Pvariable(mqtt_id_config, var) + await mqtt.register_mqtt_component(mqtt_, config) + + if position_state_topic_config := config.get(CONF_POSITION_STATE_TOPIC): + cg.add(mqtt_.set_custom_position_state_topic(position_state_topic_config)) + if position_command_topic_config := config.get(CONF_POSITION_COMMAND_TOPIC): + cg.add( + mqtt_.set_custom_position_command_topic(position_command_topic_config) + ) + + +async def register_valve(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_valve(var)) + await setup_valve_core_(var, config) + + +async def new_valve(config, *args): + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_valve(var, config) + return var + + +VALVE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Valve), + } +) + + +@automation.register_action("valve.open", OpenAction, VALVE_ACTION_SCHEMA) +async def valve_open_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("valve.close", CloseAction, VALVE_ACTION_SCHEMA) +async def valve_close_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("valve.stop", StopAction, VALVE_ACTION_SCHEMA) +async def valve_stop_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("valve.toggle", ToggleAction, VALVE_ACTION_SCHEMA) +def valve_toggle_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + yield cg.new_Pvariable(action_id, template_arg, paren) + + +VALVE_CONTROL_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Valve), + cv.Optional(CONF_STOP): cv.templatable(cv.boolean), + cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_valve_state), + cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage), + } +) + + +@automation.register_action("valve.control", ControlAction, VALVE_CONTROL_ACTION_SCHEMA) +async def valve_control_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) + if stop_config := config.get(CONF_STOP): + template_ = await cg.templatable(stop_config, args, bool) + cg.add(var.set_stop(template_)) + if state_config := config.get(CONF_STATE): + template_ = await cg.templatable(state_config, args, float) + cg.add(var.set_position(template_)) + if (position_config := config.get(CONF_POSITION)) is not None: + template_ = await cg.templatable(position_config, args, float) + cg.add(var.set_position(template_)) + return var + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_define("USE_VALVE") + cg.add_global(valve_ns.using) diff --git a/esphome/components/valve/automation.h b/esphome/components/valve/automation.h new file mode 100644 index 000000000000..24c94a5570a4 --- /dev/null +++ b/esphome/components/valve/automation.h @@ -0,0 +1,129 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "valve.h" + +namespace esphome { +namespace valve { + +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(); } + + protected: + Valve *valve_; +}; + +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(); } + + protected: + Valve *valve_; +}; + +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(); } + + protected: + Valve *valve_; +}; + +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(); } + + protected: + Valve *valve_; +}; + +template class ControlAction : public Action { + public: + explicit ControlAction(Valve *valve) : valve_(valve) {} + + TEMPLATABLE_VALUE(bool, stop) + TEMPLATABLE_VALUE(float, position) + + void play(Ts... x) override { + auto call = this->valve_->make_call(); + if (this->stop_.has_value()) + call.set_stop(this->stop_.value(x...)); + if (this->position_.has_value()) + call.set_position(this->position_.value(x...)); + call.perform(); + } + + protected: + Valve *valve_; +}; + +template class ValvePublishAction : public Action { + public: + ValvePublishAction(Valve *valve) : valve_(valve) {} + TEMPLATABLE_VALUE(float, position) + TEMPLATABLE_VALUE(ValveOperation, current_operation) + + void play(Ts... x) override { + if (this->position_.has_value()) + this->valve_->position = this->position_.value(x...); + if (this->current_operation_.has_value()) + this->valve_->current_operation = this->current_operation_.value(x...); + this->valve_->publish_state(); + } + + protected: + Valve *valve_; +}; + +template class ValveIsOpenCondition : public Condition { + public: + ValveIsOpenCondition(Valve *valve) : valve_(valve) {} + bool check(Ts... x) override { return this->valve_->is_fully_open(); } + + protected: + Valve *valve_; +}; + +template class ValveIsClosedCondition : public Condition { + public: + ValveIsClosedCondition(Valve *valve) : valve_(valve) {} + bool check(Ts... x) override { return this->valve_->is_fully_closed(); } + + protected: + Valve *valve_; +}; + +class ValveOpenTrigger : public Trigger<> { + public: + ValveOpenTrigger(Valve *a_valve) { + a_valve->add_on_state_callback([this, a_valve]() { + if (a_valve->is_fully_open()) { + this->trigger(); + } + }); + } +}; + +class ValveClosedTrigger : public Trigger<> { + public: + ValveClosedTrigger(Valve *a_valve) { + a_valve->add_on_state_callback([this, a_valve]() { + if (a_valve->is_fully_closed()) { + this->trigger(); + } + }); + } +}; + +} // namespace valve +} // namespace esphome diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp new file mode 100644 index 000000000000..d1ec17945a76 --- /dev/null +++ b/esphome/components/valve/valve.cpp @@ -0,0 +1,179 @@ +#include "valve.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace valve { + +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) { + if (pos == VALVE_OPEN) { + return "OPEN"; + } else if (pos == VALVE_CLOSED) { + return "CLOSE"; + } else { + return "UNKNOWN"; + } +} +const char *valve_operation_to_str(ValveOperation op) { + switch (op) { + case VALVE_OPERATION_IDLE: + return "IDLE"; + case VALVE_OPERATION_OPENING: + return "OPENING"; + case VALVE_OPERATION_CLOSING: + return "CLOSING"; + default: + return "UNKNOWN"; + } +} + +Valve::Valve() : position{VALVE_OPEN} {} + +ValveCall::ValveCall(Valve *parent) : parent_(parent) {} +ValveCall &ValveCall::set_command(const char *command) { + if (strcasecmp(command, "OPEN") == 0) { + this->set_command_open(); + } else if (strcasecmp(command, "CLOSE") == 0) { + this->set_command_close(); + } else if (strcasecmp(command, "STOP") == 0) { + this->set_command_stop(); + } else if (strcasecmp(command, "TOGGLE") == 0) { + this->set_command_toggle(); + } else { + ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); + } + return *this; +} +ValveCall &ValveCall::set_command_open() { + this->position_ = VALVE_OPEN; + return *this; +} +ValveCall &ValveCall::set_command_close() { + this->position_ = VALVE_CLOSED; + return *this; +} +ValveCall &ValveCall::set_command_stop() { + this->stop_ = true; + return *this; +} +ValveCall &ValveCall::set_command_toggle() { + this->toggle_ = true; + return *this; +} +ValveCall &ValveCall::set_position(float position) { + this->position_ = position; + return *this; +} +void ValveCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + auto traits = this->parent_->get_traits(); + this->validate_(); + if (this->stop_) { + ESP_LOGD(TAG, " Command: STOP"); + } + if (this->position_.has_value()) { + 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_)); + } + } + if (this->toggle_.has_value()) { + ESP_LOGD(TAG, " Command: TOGGLE"); + } + this->parent_->control(*this); +} +const optional &ValveCall::get_position() const { return this->position_; } +const optional &ValveCall::get_toggle() const { return this->toggle_; } +void ValveCall::validate_() { + auto traits = this->parent_->get_traits(); + if (this->position_.has_value()) { + auto pos = *this->position_; + if (!traits.get_supports_position() && pos != VALVE_OPEN && pos != VALVE_CLOSED) { + ESP_LOGW(TAG, "'%s' - This valve device does not support setting position!", this->parent_->get_name().c_str()); + this->position_.reset(); + } else if (pos < 0.0f || pos > 1.0f) { + ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos); + this->position_ = clamp(pos, 0.0f, 1.0f); + } + } + if (this->toggle_.has_value()) { + if (!traits.get_supports_toggle()) { + ESP_LOGW(TAG, "'%s' - This valve device does not support toggle!", this->parent_->get_name().c_str()); + this->toggle_.reset(); + } + } + if (this->stop_) { + if (this->position_.has_value()) { + ESP_LOGW(TAG, "Cannot set position when stopping a valve!"); + this->position_.reset(); + } + if (this->toggle_.has_value()) { + ESP_LOGW(TAG, "Cannot set toggle when stopping a valve!"); + this->toggle_.reset(); + } + } +} +ValveCall &ValveCall::set_stop(bool stop) { + this->stop_ = stop; + return *this; +} +bool ValveCall::get_stop() const { return this->stop_; } + +ValveCall Valve::make_call() { return {this}; } + +void Valve::add_on_state_callback(std::function &&f) { this->state_callback_.add(std::move(f)); } +void Valve::publish_state(bool save) { + this->position = clamp(this->position, 0.0f, 1.0f); + + ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str()); + auto traits = this->get_traits(); + if (traits.get_supports_position()) { + ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f); + } else { + if (this->position == VALVE_OPEN) { + ESP_LOGD(TAG, " State: OPEN"); + } else if (this->position == VALVE_CLOSED) { + ESP_LOGD(TAG, " State: CLOSED"); + } else { + ESP_LOGD(TAG, " State: UNKNOWN"); + } + } + ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); + + this->state_callback_.call(); + + if (save) { + ValveRestoreState restore{}; + memset(&restore, 0, sizeof(restore)); + restore.position = this->position; + this->rtc_.save(&restore); + } +} +optional Valve::restore_state_() { + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + ValveRestoreState recovered{}; + if (!this->rtc_.load(&recovered)) + return {}; + return recovered; +} + +bool Valve::is_fully_open() const { return this->position == VALVE_OPEN; } +bool Valve::is_fully_closed() const { return this->position == VALVE_CLOSED; } + +ValveCall ValveRestoreState::to_call(Valve *valve) { + auto call = valve->make_call(); + call.set_position(this->position); + return call; +} +void ValveRestoreState::apply(Valve *valve) { + valve->position = this->position; + valve->publish_state(); +} + +} // namespace valve +} // namespace esphome diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h new file mode 100644 index 000000000000..0e14a8d8f0a9 --- /dev/null +++ b/esphome/components/valve/valve.h @@ -0,0 +1,152 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" +#include "valve_traits.h" + +namespace esphome { +namespace valve { + +const extern float VALVE_OPEN; +const extern float VALVE_CLOSED; + +#define LOG_VALVE(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + auto traits_ = (obj)->get_traits(); \ + if (traits_.get_is_assumed_state()) { \ + ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \ + } \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ + } + +class Valve; + +class ValveCall { + public: + ValveCall(Valve *parent); + + /// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE". + ValveCall &set_command(const char *command); + /// Set the command to open the valve. + ValveCall &set_command_open(); + /// Set the command to close the valve. + ValveCall &set_command_close(); + /// Set the command to stop the valve. + ValveCall &set_command_stop(); + /// Set the command to toggle the valve. + ValveCall &set_command_toggle(); + /// Set the call to a certain target position. + ValveCall &set_position(float position); + /// Set whether this valve call should stop the valve. + ValveCall &set_stop(bool stop); + + /// Perform the valve call. + void perform(); + + const optional &get_position() const; + bool get_stop() const; + const optional &get_toggle() const; + + protected: + void validate_(); + + Valve *parent_; + bool stop_{false}; + optional position_{}; + optional toggle_{}; +}; + +/// Struct used to store the restored state of a valve +struct ValveRestoreState { + float position; + + /// Convert this struct to a valve call that can be performed. + ValveCall to_call(Valve *valve); + /// Apply these settings to the valve + void apply(Valve *valve); +} __attribute__((packed)); + +/// Enum encoding the current operation of a valve. +enum ValveOperation : uint8_t { + /// The valve is currently idle (not moving) + VALVE_OPERATION_IDLE = 0, + /// The valve is currently opening. + VALVE_OPERATION_OPENING, + /// The valve is currently closing. + VALVE_OPERATION_CLOSING, +}; + +const char *valve_operation_to_str(ValveOperation op); + +/** Base class for all valve devices. + * + * Valves currently have three properties: + * - position - The current position of the valve from 0.0 (fully closed) to 1.0 (fully open). + * For valves with only binary OPEN/CLOSED position this will always be either 0.0 or 1.0 + * - current_operation - The operation the valve is currently performing, this can + * be one of IDLE, OPENING and CLOSING. + * + * For users: All valve operations must be performed over the .make_call() interface. + * To command a valve, use .make_call() to create a call object, set all properties + * you wish to set, and activate the command with .perform(). + * For reading out the current values of the valve, use the public .position, etc. + * properties (these are read-only for users) + * + * For integrations: Integrations must implement two methods: control() and get_traits(). + * Control will be called with the arguments supplied by the user and should be used + * to control all values of the valve. Also implement get_traits() to return what operations + * the valve supports. + */ +class Valve : public EntityBase, public EntityBase_DeviceClass { + public: + explicit Valve(); + + /// The current operation of the valve (idle, opening, closing). + ValveOperation current_operation{VALVE_OPERATION_IDLE}; + /** The position of the valve from 0.0 (fully closed) to 1.0 (fully open). + * + * For binary valves this is always equals to 0.0 or 1.0 (see also VALVE_OPEN and + * VALVE_CLOSED constants). + */ + float position; + + /// Construct a new valve call used to control the valve. + ValveCall make_call(); + + void add_on_state_callback(std::function &&f); + + /** Publish the current state of the valve. + * + * First set the .position, etc. values and then call this method + * to publish the state of the valve. + * + * @param save Whether to save the updated values in RTC area. + */ + void publish_state(bool save = true); + + virtual ValveTraits get_traits() = 0; + + /// Helper method to check if the valve is fully open. Equivalent to comparing .position against 1.0 + bool is_fully_open() const; + /// Helper method to check if the valve is fully closed. Equivalent to comparing .position against 0.0 + bool is_fully_closed() const; + + protected: + friend ValveCall; + + virtual void control(const ValveCall &call) = 0; + + optional restore_state_(); + + CallbackManager state_callback_{}; + + ESPPreferenceObject rtc_; +}; + +} // namespace valve +} // namespace esphome diff --git a/esphome/components/valve/valve_traits.h b/esphome/components/valve/valve_traits.h new file mode 100644 index 000000000000..7e9aab2f26f8 --- /dev/null +++ b/esphome/components/valve/valve_traits.h @@ -0,0 +1,27 @@ +#pragma once + +namespace esphome { +namespace valve { + +class ValveTraits { + public: + ValveTraits() = default; + + bool get_is_assumed_state() const { return this->is_assumed_state_; } + void set_is_assumed_state(bool is_assumed_state) { this->is_assumed_state_ = is_assumed_state; } + bool get_supports_position() const { return this->supports_position_; } + void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; } + bool get_supports_toggle() const { return this->supports_toggle_; } + void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } + bool get_supports_stop() const { return this->supports_stop_; } + void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; } + + protected: + bool is_assumed_state_{false}; + bool supports_position_{false}; + bool supports_toggle_{false}; + bool supports_stop_{false}; +}; + +} // namespace valve +} // namespace esphome diff --git a/esphome/components/vbus/sensor/__init__.py b/esphome/components/vbus/sensor/__init__.py index 2ad9da424e79..2b89da6d3249 100644 --- a/esphome/components/vbus/sensor/__init__.py +++ b/esphome/components/vbus/sensor/__init__.py @@ -22,6 +22,7 @@ ICON_THERMOMETER, ICON_TIMER, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, UNIT_HOUR, UNIT_MINUTE, @@ -128,7 +129,7 @@ icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, @@ -209,7 +210,7 @@ icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, @@ -290,7 +291,7 @@ icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, @@ -353,7 +354,7 @@ icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_VERSION): sensor.sensor_schema( accuracy_decimals=2, @@ -433,7 +434,7 @@ icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, diff --git a/esphome/components/vbus/vbus.cpp b/esphome/components/vbus/vbus.cpp index c9758891cc5d..e474dcfe1766 100644 --- a/esphome/components/vbus/vbus.cpp +++ b/esphome/components/vbus/vbus.cpp @@ -1,6 +1,7 @@ #include "vbus.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include namespace esphome { namespace vbus { @@ -64,8 +65,8 @@ void VBus::loop() { uint16_t id = (this->buffer_[8] << 8) + this->buffer_[7]; uint32_t value = (this->buffer_[12] << 24) + (this->buffer_[11] << 16) + (this->buffer_[10] << 8) + this->buffer_[9]; - ESP_LOGV(TAG, "P1 C%04x %04x->%04x: %04x %04x (%d)", this->command_, this->source_, this->dest_, id, value, - value); + ESP_LOGV(TAG, "P1 C%04x %04x->%04x: %04x %04" PRIx32 " (%" PRIu32 ")", this->command_, this->source_, + this->dest_, id, value, value); } else if ((this->protocol_ == 0x10) && (this->buffer_.size() == 9)) { if (!checksum(this->buffer_.data(), 0, 9)) { ESP_LOGE(TAG, "P1 checksum failed"); diff --git a/esphome/components/veml3235/__init__.py b/esphome/components/veml3235/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/veml3235/sensor.py b/esphome/components/veml3235/sensor.py new file mode 100644 index 000000000000..79ba510e41fc --- /dev/null +++ b/esphome/components/veml3235/sensor.py @@ -0,0 +1,84 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_GAIN, + CONF_INTEGRATION_TIME, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["i2c"] + +CONF_AUTO_GAIN = "auto_gain" +CONF_AUTO_GAIN_THRESHOLD_HIGH = "auto_gain_threshold_high" +CONF_AUTO_GAIN_THRESHOLD_LOW = "auto_gain_threshold_low" +CONF_DIGITAL_GAIN = "digital_gain" + +veml3235_ns = cg.esphome_ns.namespace("veml3235") + +VEML3235Sensor = veml3235_ns.class_( + "VEML3235Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) +VEML3235IntegrationTime = veml3235_ns.enum("VEML3235IntegrationTime") +VEML3235_INTEGRATION_TIMES = { + "50ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_50MS, + "100ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_100MS, + "200ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_200MS, + "400ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_400MS, + "800ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_800MS, +} +VEML3235ComponentDigitalGain = veml3235_ns.enum("VEML3235ComponentDigitalGain") +DIGITAL_GAINS = { + "1X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_1X, + "2X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_2X, +} +VEML3235ComponentGain = veml3235_ns.enum("VEML3235ComponentGain") +GAINS = { + "1X": VEML3235ComponentGain.VEML3235_GAIN_1X, + "2X": VEML3235ComponentGain.VEML3235_GAIN_2X, + "4X": VEML3235ComponentGain.VEML3235_GAIN_4X, + "AUTO": VEML3235ComponentGain.VEML3235_GAIN_AUTO, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + VEML3235Sensor, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_DIGITAL_GAIN, default="1X"): cv.enum( + DIGITAL_GAINS, upper=True + ), + cv.Optional(CONF_AUTO_GAIN, default=True): cv.boolean, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_HIGH, default="90%"): cv.percentage, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_LOW, default="20%"): cv.percentage, + cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_INTEGRATION_TIME, default="50ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.enum(VEML3235_INTEGRATION_TIMES, lower=True), + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x10)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN])) + cg.add(var.set_auto_gain_threshold_high(config[CONF_AUTO_GAIN_THRESHOLD_HIGH])) + cg.add(var.set_auto_gain_threshold_low(config[CONF_AUTO_GAIN_THRESHOLD_LOW])) + cg.add(var.set_digital_gain(DIGITAL_GAINS[config[CONF_DIGITAL_GAIN]])) + cg.add(var.set_gain(GAINS[config[CONF_GAIN]])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) diff --git a/esphome/components/veml3235/veml3235.cpp b/esphome/components/veml3235/veml3235.cpp new file mode 100644 index 000000000000..2410bfdda259 --- /dev/null +++ b/esphome/components/veml3235/veml3235.cpp @@ -0,0 +1,230 @@ +#include "veml3235.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace veml3235 { + +static const char *const TAG = "veml3235.sensor"; + +void VEML3235Sensor::setup() { + uint8_t device_id[] = {0, 0}; + + ESP_LOGCONFIG(TAG, "Setting up VEML3235 '%s'...", this->name_.c_str()); + + if (!this->refresh_config_reg()) { + ESP_LOGE(TAG, "Unable to write configuration"); + this->mark_failed(); + return; + } + if ((this->write(&ID_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(device_id, 2)) { + ESP_LOGE(TAG, "Unable to read ID"); + this->mark_failed(); + return; + } else if (device_id[0] != DEVICE_ID) { + ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]); + this->mark_failed(); + return; + } +} + +bool VEML3235Sensor::refresh_config_reg(bool force_on) { + uint16_t data = this->power_on_ || force_on ? 0 : SHUTDOWN_BITS; + + data |= (uint16_t(this->integration_time_ << CONFIG_REG_IT_BIT)); + data |= (uint16_t(this->digital_gain_ << CONFIG_REG_DG_BIT)); + data |= (uint16_t(this->gain_ << CONFIG_REG_G_BIT)); + data |= 0x1; // mandatory 1 here per RM + + ESP_LOGVV(TAG, "Writing 0x%.4x to register 0x%.2x", data, CONFIG_REG); + return this->write_byte_16(CONFIG_REG, data); +} + +float VEML3235Sensor::read_lx_() { + if (!this->power_on_) { // if off, turn on + if (!this->refresh_config_reg(true)) { + ESP_LOGW(TAG, "Turning on failed"); + this->status_set_warning(); + return NAN; + } + delay(4); // from RM: a wait time of 4 ms should be observed before the first measurement is picked up, to allow + // for a correct start of the signal processor and oscillator + } + + uint8_t als_regs[] = {0, 0}; + if ((this->write(&ALS_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(als_regs, 2)) { + this->status_set_warning(); + return NAN; + } + + this->status_clear_warning(); + + float als_raw_value_multiplier = LUX_MULTIPLIER_BASE; + uint16_t als_raw_value = encode_uint16(als_regs[1], als_regs[0]); + // determine multiplier value based on gains and integration time + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_1X) { + als_raw_value_multiplier *= 2; + } + switch (this->gain_) { + case VEML3235_GAIN_1X: + als_raw_value_multiplier *= 4; + break; + case VEML3235_GAIN_2X: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + als_raw_value_multiplier *= 16; + break; + case VEML3235_INTEGRATION_TIME_100MS: + als_raw_value_multiplier *= 8; + break; + case VEML3235_INTEGRATION_TIME_200MS: + als_raw_value_multiplier *= 4; + break; + case VEML3235_INTEGRATION_TIME_400MS: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + // finally, determine and return the actual lux value + float lx = float(als_raw_value) * als_raw_value_multiplier; + ESP_LOGVV(TAG, "'%s': ALS raw = %u, multiplier = %.5f", this->get_name().c_str(), als_raw_value, + als_raw_value_multiplier); + ESP_LOGD(TAG, "'%s': Illuminance = %.4flx", this->get_name().c_str(), lx); + + if (!this->power_on_) { // turn off if required + if (!this->refresh_config_reg()) { + ESP_LOGW(TAG, "Turning off failed"); + this->status_set_warning(); + } + } + + if (this->auto_gain_) { + this->adjust_gain_(als_raw_value); + } + + return lx; +} + +void VEML3235Sensor::adjust_gain_(const uint16_t als_raw_value) { + if ((als_raw_value > UINT16_MAX * this->auto_gain_threshold_low_) && + (als_raw_value < UINT16_MAX * this->auto_gain_threshold_high_)) { + return; + } + + if (als_raw_value >= UINT16_MAX * 0.9) { // over-saturated, reset all gains and start over + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->integration_time_ = VEML3235_INTEGRATION_TIME_50MS; + this->refresh_config_reg(); + return; + } + + if (this->gain_ != VEML3235_GAIN_4X) { // increase gain if possible + switch (this->gain_) { + case VEML3235_GAIN_1X: + this->gain_ = VEML3235_GAIN_2X; + break; + case VEML3235_GAIN_2X: + this->gain_ = VEML3235_GAIN_4X; + break; + default: + break; + } + this->refresh_config_reg(); + return; + } + // gain is maxed out; reset it and try to increase digital gain + if (this->digital_gain_ != VEML3235_DIGITAL_GAIN_2X) { // increase digital gain if possible + this->digital_gain_ = VEML3235_DIGITAL_GAIN_2X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } + // digital gain is maxed out; reset it and try to increase integration time + if (this->integration_time_ != VEML3235_INTEGRATION_TIME_800MS) { // increase integration time if possible + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_100MS; + break; + case VEML3235_INTEGRATION_TIME_100MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_200MS; + break; + case VEML3235_INTEGRATION_TIME_200MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_400MS; + break; + case VEML3235_INTEGRATION_TIME_400MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_800MS; + break; + default: + break; + } + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } +} + +void VEML3235Sensor::dump_config() { + uint8_t digital_gain = 1; + uint8_t gain = 1; + uint16_t integration_time = 0; + + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X) { + digital_gain = 2; + } + switch (this->gain_) { + case VEML3235_GAIN_2X: + gain = 2; + break; + case VEML3235_GAIN_4X: + gain = 4; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + integration_time = 50; + break; + case VEML3235_INTEGRATION_TIME_100MS: + integration_time = 100; + break; + case VEML3235_INTEGRATION_TIME_200MS: + integration_time = 200; + break; + case VEML3235_INTEGRATION_TIME_400MS: + integration_time = 400; + break; + case VEML3235_INTEGRATION_TIME_800MS: + integration_time = 800; + break; + default: + break; + } + + LOG_SENSOR("", "VEML3235", this); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication failed"); + } + LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_)); + if (this->auto_gain_) { + ESP_LOGCONFIG(TAG, " Auto-gain upper threshold: %f%%", this->auto_gain_threshold_high_ * 100.0); + ESP_LOGCONFIG(TAG, " Auto-gain lower threshold: %f%%", this->auto_gain_threshold_low_ * 100.0); + ESP_LOGCONFIG(TAG, " Values below will be used as initial values only"); + } + ESP_LOGCONFIG(TAG, " Digital gain: %uX", digital_gain); + ESP_LOGCONFIG(TAG, " Gain: %uX", gain); + ESP_LOGCONFIG(TAG, " Integration time: %ums", integration_time); +} + +} // namespace veml3235 +} // namespace esphome diff --git a/esphome/components/veml3235/veml3235.h b/esphome/components/veml3235/veml3235.h new file mode 100644 index 000000000000..2b0d6b23ea9c --- /dev/null +++ b/esphome/components/veml3235/veml3235.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace veml3235 { + +// Register IDs/locations +// +static const uint8_t CONFIG_REG = 0x00; +static const uint8_t W_REG = 0x04; +static const uint8_t ALS_REG = 0x05; +static const uint8_t ID_REG = 0x09; + +// Bit offsets within CONFIG_REG +// +static const uint8_t CONFIG_REG_IT_BIT = 12; +static const uint8_t CONFIG_REG_DG_BIT = 5; +static const uint8_t CONFIG_REG_G_BIT = 3; + +// Other important constants +// +static const uint8_t DEVICE_ID = 0x35; +static const uint16_t SHUTDOWN_BITS = 0x0018; + +// Base multiplier value for lux computation +// +static const float LUX_MULTIPLIER_BASE = 0.00213; + +// Enum for conversion/integration time settings for the VEML3235. +// +// Specific values of the enum constants are register values taken from the VEML3235 datasheet. +// Longer times mean more accurate results, but will take more energy/more time. +// +enum VEML3235ComponentIntegrationTime { + VEML3235_INTEGRATION_TIME_50MS = 0b000, + VEML3235_INTEGRATION_TIME_100MS = 0b001, + VEML3235_INTEGRATION_TIME_200MS = 0b010, + VEML3235_INTEGRATION_TIME_400MS = 0b011, + VEML3235_INTEGRATION_TIME_800MS = 0b100, +}; + +// Enum for digital gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentDigitalGain { + VEML3235_DIGITAL_GAIN_1X = 0b0, + VEML3235_DIGITAL_GAIN_2X = 0b1, +}; + +// Enum for gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentGain { + VEML3235_GAIN_1X = 0b00, + VEML3235_GAIN_2X = 0b01, + VEML3235_GAIN_4X = 0b11, +}; + +class VEML3235Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override { this->publish_state(this->read_lx_()); } + float get_setup_priority() const override { return setup_priority::DATA; } + + // Used by ESPHome framework. Does NOT actually set the value on the device. + void set_auto_gain(bool auto_gain) { this->auto_gain_ = auto_gain; } + void set_auto_gain_threshold_high(float auto_gain_threshold_high) { + this->auto_gain_threshold_high_ = auto_gain_threshold_high; + } + void set_auto_gain_threshold_low(float auto_gain_threshold_low) { + this->auto_gain_threshold_low_ = auto_gain_threshold_low; + } + void set_power_on(bool power_on) { this->power_on_ = power_on; } + void set_digital_gain(VEML3235ComponentDigitalGain digital_gain) { this->digital_gain_ = digital_gain; } + void set_gain(VEML3235ComponentGain gain) { this->gain_ = gain; } + void set_integration_time(VEML3235ComponentIntegrationTime integration_time) { + this->integration_time_ = integration_time; + } + + bool auto_gain() { return this->auto_gain_; } + float auto_gain_threshold_high() { return this->auto_gain_threshold_high_; } + float auto_gain_threshold_low() { return this->auto_gain_threshold_low_; } + VEML3235ComponentDigitalGain digital_gain() { return this->digital_gain_; } + VEML3235ComponentGain gain() { return this->gain_; } + VEML3235ComponentIntegrationTime integration_time() { return this->integration_time_; } + + // Updates the configuration register on the device + bool refresh_config_reg(bool force_on = false); + + protected: + float read_lx_(); + void adjust_gain_(uint16_t als_raw_value); + + bool auto_gain_{true}; + bool power_on_{true}; + float auto_gain_threshold_high_{0.9}; + float auto_gain_threshold_low_{0.2}; + VEML3235ComponentDigitalGain digital_gain_{VEML3235_DIGITAL_GAIN_1X}; + VEML3235ComponentGain gain_{VEML3235_GAIN_1X}; + VEML3235ComponentIntegrationTime integration_time_{VEML3235_INTEGRATION_TIME_50MS}; +}; + +} // namespace veml3235 +} // namespace esphome diff --git a/esphome/components/veml7700/__init__.py b/esphome/components/veml7700/__init__.py new file mode 100644 index 000000000000..dd06cfffea04 --- /dev/null +++ b/esphome/components/veml7700/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/veml7700/sensor.py b/esphome/components/veml7700/sensor.py new file mode 100644 index 000000000000..7ce05b47e492 --- /dev/null +++ b/esphome/components/veml7700/sensor.py @@ -0,0 +1,190 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ACTUAL_GAIN, + CONF_AUTO_MODE, + CONF_FULL_SPECTRUM, + CONF_GAIN, + CONF_GLASS_ATTENUATION_FACTOR, + CONF_ID, + CONF_INFRARED, + CONF_INTEGRATION_TIME, + CONF_NAME, + UNIT_LUX, + UNIT_MILLISECOND, + ICON_BRIGHTNESS_5, + ICON_BRIGHTNESS_6, + ICON_TIMER, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@latonita"] +DEPENDENCIES = ["i2c"] + +UNIT_COUNTS = "#" +ICON_MULTIPLICATION = "mdi:multiplication" +ICON_BRIGHTNESS_7 = "mdi:brightness-7" + +CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" +CONF_AMBIENT_LIGHT = "ambient_light" +CONF_AMBIENT_LIGHT_COUNTS = "ambient_light_counts" +CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" +CONF_LUX_COMPENSATION = "lux_compensation" + +veml7700_ns = cg.esphome_ns.namespace("veml7700") + +VEML7700Component = veml7700_ns.class_( + "VEML7700Component", cg.PollingComponent, i2c.I2CDevice +) + +Gain = veml7700_ns.enum("Gain") +GAINS = { + "1/8X": Gain.X_1_8, + "1/4X": Gain.X_1_4, + "1X": Gain.X_1, + "2X": Gain.X_2, +} + +IntegrationTime = veml7700_ns.enum("IntegrationTime") +INTEGRATION_TIMES = { + 25: IntegrationTime.INTEGRATION_TIME_25MS, + 50: IntegrationTime.INTEGRATION_TIME_50MS, + 100: IntegrationTime.INTEGRATION_TIME_100MS, + 200: IntegrationTime.INTEGRATION_TIME_200MS, + 400: IntegrationTime.INTEGRATION_TIME_400MS, + 800: IntegrationTime.INTEGRATION_TIME_800MS, +} + + +def validate_integration_time(value): + value = cv.positive_time_period_milliseconds(value).total_milliseconds + return cv.enum(INTEGRATION_TIMES, int=True)(value) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(VEML7700Component), + cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, + cv.Optional(CONF_GAIN, default="1/8X"): cv.enum(GAINS, upper=True), + cv.Optional( + CONF_INTEGRATION_TIME, default="100ms" + ): validate_integration_time, + cv.Optional(CONF_LUX_COMPENSATION, default=True): cv.boolean, + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), + cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_AMBIENT_LIGHT_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FULL_SPECTRUM): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_7, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FULL_SPECTRUM_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_7, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_INFRARED): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_GAIN): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_MULTIPLICATION, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x10)), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if als_config := config.get(CONF_AMBIENT_LIGHT): + sens = await sensor.new_sensor(als_config) + cg.add(var.set_ambient_light_sensor(sens)) + + if als_cnt_config := config.get(CONF_AMBIENT_LIGHT_COUNTS): + sens = await sensor.new_sensor(als_cnt_config) + cg.add(var.set_ambient_light_counts_sensor(sens)) + + if full_spect_config := config.get(CONF_FULL_SPECTRUM): + sens = await sensor.new_sensor(full_spect_config) + cg.add(var.set_white_sensor(sens)) + + if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS): + sens = await sensor.new_sensor(full_spect_cnt_config) + cg.add(var.set_white_counts_sensor(sens)) + + if infrared_config := config.get(CONF_INFRARED): + sens = await sensor.new_sensor(infrared_config) + cg.add(var.set_infrared_sensor(sens)) + + if act_gain_config := config.get(CONF_ACTUAL_GAIN): + sens = await sensor.new_sensor(act_gain_config) + cg.add(var.set_actual_gain_sensor(sens)) + + if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME): + sens = await sensor.new_sensor(act_itime_config) + cg.add(var.set_actual_integration_time_sensor(sens)) + + cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) + cg.add(var.set_enable_lux_compensation(config[CONF_LUX_COMPENSATION])) + cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) diff --git a/esphome/components/veml7700/veml7700.cpp b/esphome/components/veml7700/veml7700.cpp new file mode 100644 index 000000000000..68550811a1c2 --- /dev/null +++ b/esphome/components/veml7700/veml7700.cpp @@ -0,0 +1,437 @@ +#include "veml7700.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace veml7700 { + +static const char *const TAG = "veml7700"; +static const size_t VEML_REG_SIZE = 2; + +static float reduce_to_zero(float a, float b) { return (a > b) ? (a - b) : 0; } + +template T get_next(const T (&array)[size], const T val) { + size_t i = 0; + size_t idx = -1; + while (idx == -1 && i < size) { + if (array[i] == val) { + idx = i; + break; + } + i++; + } + if (idx == -1 || i + 1 >= size) + return val; + return array[i + 1]; +} + +template T get_prev(const T (&array)[size], const T val) { + size_t i = size - 1; + size_t idx = -1; + while (idx == -1 && i > 0) { + if (array[i] == val) { + idx = i; + break; + } + i--; + } + if (idx == -1 || i == 0) + return val; + return array[i - 1]; +} + +static uint16_t get_itime_ms(IntegrationTime time) { + uint16_t ms = 0; + switch (time) { + case INTEGRATION_TIME_100MS: + ms = 100; + break; + case INTEGRATION_TIME_200MS: + ms = 200; + break; + case INTEGRATION_TIME_400MS: + ms = 400; + break; + case INTEGRATION_TIME_800MS: + ms = 800; + break; + case INTEGRATION_TIME_50MS: + ms = 50; + break; + case INTEGRATION_TIME_25MS: + ms = 25; + break; + default: + ms = 100; + } + return ms; +} + +static float get_gain_coeff(Gain gain) { + static const float GAIN_FLOAT[GAINS_COUNT] = {1.0f, 2.0f, 0.125f, 0.25f}; + return GAIN_FLOAT[gain & 0b11]; +} + +static const char *get_gain_str(Gain gain) { + static const char *gain_str[GAINS_COUNT] = {"1x", "2x", "1/8x", "1/4x"}; + return gain_str[gain & 0b11]; +} + +void VEML7700Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up VEML7700/6030..."); + + auto err = this->configure_(); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Sensor configuration failed"); + this->mark_failed(); + } else { + this->state_ = State::INITIAL_SETUP_COMPLETED; + } +} + +void VEML7700Component::dump_config() { + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Automatic gain/time: %s", YESNO(this->automatic_mode_enabled_)); + if (!this->automatic_mode_enabled_) { + ESP_LOGCONFIG(TAG, " Gain: %s", get_gain_str(this->gain_)); + ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_)); + } + ESP_LOGCONFIG(TAG, " Lux compensation: %s", YESNO(this->lux_compensation_enabled_)); + ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_); + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "ALS channel lux", this->ambient_light_sensor_); + LOG_SENSOR(" ", "ALS channel counts", this->ambient_light_counts_sensor_); + LOG_SENSOR(" ", "WHITE channel lux", this->white_sensor_); + LOG_SENSOR(" ", "WHITE channel counts", this->white_counts_sensor_); + LOG_SENSOR(" ", "FAKE_IR channel lux", this->fake_infrared_sensor_); + LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_); + LOG_SENSOR(" ", "Actual integration time", this->actual_integration_time_sensor_); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with I2C VEML7700/6030 failed!"); + } +} + +void VEML7700Component::update() { + if (this->is_ready() && this->state_ == State::IDLE) { + ESP_LOGV(TAG, "Update: Initiating new data collection"); + + this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::COLLECTING_DATA; + + this->readings_.als_counts = 0; + this->readings_.white_counts = 0; + this->readings_.actual_time = this->integration_time_; + this->readings_.actual_gain = this->gain_; + this->readings_.als_lux = 0; + this->readings_.white_lux = 0; + this->readings_.fake_infrared_lux = 0; + } else { + ESP_LOGV(TAG, "Update: Component not ready yet"); + } +} + +void VEML7700Component::loop() { + ErrorCode err = i2c::ERROR_OK; + + if (this->state_ == State::INITIAL_SETUP_COMPLETED) { + // Datasheet: 2.5 ms before the first measurement is needed, allowing for the correct start of the signal processor + // and oscillator. + // Reality: wait for couple integration times to have first samples captured + this->set_timeout(2 * this->integration_time_, [this]() { this->state_ = State::IDLE; }); + } + + if (this->is_ready()) { + switch (this->state_) { + case State::IDLE: + // doing nothing, having best time + break; + + case State::COLLECTING_DATA: + err = this->read_sensor_output_(this->readings_); + this->state_ = (err == i2c::ERROR_OK) ? State::DATA_COLLECTED : State::IDLE; + break; + + case State::COLLECTING_DATA_AUTO: // Automatic mode - we start here to reconfigure device first + case State::DATA_COLLECTED: + if (!this->are_adjustments_required_(this->readings_)) { + this->state_ = State::READY_TO_PUBLISH_PART_1; + } else { + // if sensitivity adjustment needed - + // shutdown device to change config and wait one integration time period + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, true); + if (err == i2c::ERROR_OK) { + this->set_timeout(1 * get_itime_ms(this->readings_.actual_time), + [this]() { this->state_ = State::READY_TO_APPLY_ADJUSTMENTS; }); + } else { + this->state_ = State::IDLE; + } + } + break; + + case State::ADJUSTMENT_IN_PROGRESS: + // nothing to be done, just waiting for the timeout + break; + + case State::READY_TO_APPLY_ADJUSTMENTS: + // second stage of sensitivity adjustment - turn device back on + // and wait 2-3 integration time periods to get good data samples + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, false); + if (err == i2c::ERROR_OK) { + this->set_timeout(3 * get_itime_ms(this->readings_.actual_time), + [this]() { this->state_ = State::COLLECTING_DATA; }); + } else { + this->state_ = State::IDLE; + } + break; + + case State::READY_TO_PUBLISH_PART_1: + this->status_clear_warning(); + + this->apply_lux_calculation_(this->readings_); + this->apply_lux_compensation_(this->readings_); + this->apply_glass_attenuation_(this->readings_); + + this->publish_data_part_1_(this->readings_); + this->state_ = State::READY_TO_PUBLISH_PART_2; + break; + + case State::READY_TO_PUBLISH_PART_2: + this->publish_data_part_2_(this->readings_); + this->state_ = State::READY_TO_PUBLISH_PART_3; + break; + + case State::READY_TO_PUBLISH_PART_3: + this->publish_data_part_3_(this->readings_); + this->state_ = State::IDLE; + break; + + default: + break; + } + if (err != i2c::ERROR_OK) + this->status_set_warning(); + } +} + +ErrorCode VEML7700Component::configure_() { + ESP_LOGV(TAG, "Configure"); + + ConfigurationRegister als_conf{0}; + als_conf.ALS_INT_EN = false; + als_conf.ALS_PERS = Persistence::PERSISTENCE_1; + als_conf.ALS_IT = this->integration_time_; + als_conf.ALS_GAIN = this->gain_; + + als_conf.ALS_SD = true; + ESP_LOGV(TAG, "Shutdown before config. ALS_CONF_0 to 0x%04X", als_conf.raw); + auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to shutdown, I2C error %d", err); + return err; + } + delay(3); + + als_conf.ALS_SD = false; + ESP_LOGV(TAG, "Turning on. Setting ALS_CONF_0 to 0x%04X", als_conf.raw); + err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to turn on, I2C error %d", err); + return err; + } + + PSMRegister psm{0}; + psm.PSM = PSM::PSM_MODE_1; + psm.PSM_EN = false; + ESP_LOGV(TAG, "Setting PSM to 0x%04X", psm.raw); + err = this->write_register((uint8_t) CommandRegisters::PWR_SAVING, psm.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Failed to set PSM, I2C error %d", err); + return err; + } + + return err; +} + +ErrorCode VEML7700Component::reconfigure_time_and_gain_(IntegrationTime time, Gain gain, bool shutdown) { + ESP_LOGV(TAG, "Reconfigure time and gain (%d ms, %s) %s", get_itime_ms(time), get_gain_str(gain), + shutdown ? "Shutting down" : "Turning back on"); + + ConfigurationRegister als_conf{0}; + als_conf.raw = 0; + + // We have to before changing parameters + als_conf.ALS_SD = shutdown; + als_conf.ALS_INT_EN = false; + als_conf.ALS_PERS = Persistence::PERSISTENCE_1; + als_conf.ALS_IT = time; + als_conf.ALS_GAIN = gain; + auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "%s failed", shutdown ? "Shutdown" : "Turn on"); + } + + return err; +} + +ErrorCode VEML7700Component::read_sensor_output_(Readings &data) { + auto als_err = + this->read_register((uint8_t) CommandRegisters::ALS, (uint8_t *) &data.als_counts, VEML_REG_SIZE, false); + if (als_err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading ALS register, err = %d", als_err); + } + auto white_err = + this->read_register((uint8_t) CommandRegisters::WHITE, (uint8_t *) &data.white_counts, VEML_REG_SIZE, false); + if (white_err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading WHITE register, err = %d", white_err); + } + + ConfigurationRegister conf{0}; + auto err = + this->read_register((uint8_t) CommandRegisters::ALS_CONF_0, (uint8_t *) conf.raw_bytes, VEML_REG_SIZE, false); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Error reading ALS_CONF_0 register, err = %d", white_err); + } + data.actual_time = conf.ALS_IT; + data.actual_gain = conf.ALS_GAIN; + + ESP_LOGV(TAG, "Data from sensors: ALS = %d, WHITE = %d, Gain = %s, Time = %d ms", data.als_counts, data.white_counts, + get_gain_str(data.actual_gain), get_itime_ms(data.actual_time)); + return std::max(als_err, white_err); +} + +bool VEML7700Component::are_adjustments_required_(Readings &data) { + // skip first sample in auto mode - + // we need to reconfigure device after last measurement + if (this->state_ == State::COLLECTING_DATA_AUTO) + return true; + + if (!this->automatic_mode_enabled_) + return false; + + // Recommended thresholds as per datasheet + static constexpr uint16_t LOW_INTENSITY_THRESHOLD = 100; + static constexpr uint16_t HIGH_INTENSITY_THRESHOLD = 10000; + + static const IntegrationTime TIMES[INTEGRATION_TIMES_COUNT] = {INTEGRATION_TIME_25MS, INTEGRATION_TIME_50MS, + INTEGRATION_TIME_100MS, INTEGRATION_TIME_200MS, + INTEGRATION_TIME_400MS, INTEGRATION_TIME_800MS}; + static const Gain GAINS[GAINS_COUNT] = {X_1_8, X_1_4, X_1, X_2}; + + if (data.als_counts <= LOW_INTENSITY_THRESHOLD) { + Gain next_gain = get_next(GAINS, data.actual_gain); + if (next_gain != data.actual_gain) { + data.actual_gain = next_gain; + return true; + } + IntegrationTime next_time = get_next(TIMES, data.actual_time); + if (next_time != data.actual_time) { + data.actual_time = next_time; + return true; + } + } else if (data.als_counts >= HIGH_INTENSITY_THRESHOLD) { + Gain prev_gain = get_prev(GAINS, data.actual_gain); + if (prev_gain != data.actual_gain) { + data.actual_gain = prev_gain; + return true; + } + IntegrationTime prev_time = get_prev(TIMES, data.actual_time); + if (prev_time != data.actual_time) { + data.actual_time = prev_time; + return true; + } + } + + // Counts are either good (between thresholds) + // or there is no room to change sensitivity anymore + return false; +} + +void VEML7700Component::apply_lux_calculation_(Readings &data) { + static const float MAX_GAIN = 2.0f; + static const float MAX_ITIME_MS = 800.0f; + static const float MAX_LX_RESOLUTION = 0.0036f; + float lux_resolution = (MAX_ITIME_MS / (float) get_itime_ms(data.actual_time)) * + (MAX_GAIN / get_gain_coeff(data.actual_gain)) * MAX_LX_RESOLUTION; + ESP_LOGV(TAG, "Lux resolution for (%d, %s) = %.4f ", get_itime_ms(data.actual_time), get_gain_str(data.actual_gain), + lux_resolution); + + data.als_lux = lux_resolution * (float) data.als_counts; + data.white_lux = lux_resolution * (float) data.white_counts; + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + + ESP_LOGV(TAG, "%s mode - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", + this->automatic_mode_enabled_ ? "Automatic" : "Manual", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::apply_lux_compensation_(Readings &data) { + if (!this->lux_compensation_enabled_) + return; + auto &local_data = data; + // Always apply correction for G1/4 and G1/8 + // Other Gains G1 and G2 are not supposed to be used for lux > 1000, + // corrections may help, but not a lot. + // + // "Illumination values higher than 1000 lx show non-linearity. + // This non-linearity is the same for all sensors, so a compensation formula can be applied + // if this light level is exceeded" + auto compensate = [&local_data](float &lux) { + auto calculate_high_lux_compensation = [](float lux_veml) -> float { + return (((6.0135e-13 * lux_veml - 9.3924e-9) * lux_veml + 8.1488e-5) * lux_veml + 1.0023) * lux_veml; + }; + + if (lux > 1000.0f || local_data.actual_gain == Gain::X_1_8 || local_data.actual_gain == Gain::X_1_4) { + lux = calculate_high_lux_compensation(lux); + } + }; + + compensate(data.als_lux); + compensate(data.white_lux); + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + + ESP_LOGV(TAG, "Lux compensation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::apply_glass_attenuation_(Readings &data) { + data.als_lux *= this->glass_attenuation_factor_; + data.white_lux *= this->glass_attenuation_factor_; + data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux); + ESP_LOGV(TAG, "Glass attenuation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux, + data.fake_infrared_lux); +} + +void VEML7700Component::publish_data_part_1_(Readings &data) { + if (this->ambient_light_sensor_ != nullptr) { + this->ambient_light_sensor_->publish_state(data.als_lux); + } + if (this->white_sensor_ != nullptr) { + this->white_sensor_->publish_state(data.white_lux); + } +} + +void VEML7700Component::publish_data_part_2_(Readings &data) { + if (this->fake_infrared_sensor_ != nullptr) { + this->fake_infrared_sensor_->publish_state(data.fake_infrared_lux); + } + if (this->ambient_light_counts_sensor_ != nullptr) { + this->ambient_light_counts_sensor_->publish_state(data.als_counts); + } + if (this->white_counts_sensor_ != nullptr) { + this->white_counts_sensor_->publish_state(data.white_counts); + } +} + +void VEML7700Component::publish_data_part_3_(Readings &data) { + if (this->actual_gain_sensor_ != nullptr) { + this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); + } + if (this->actual_integration_time_sensor_ != nullptr) { + this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.actual_time)); + } +} +} // namespace veml7700 +} // namespace esphome diff --git a/esphome/components/veml7700/veml7700.h b/esphome/components/veml7700/veml7700.h new file mode 100644 index 000000000000..fe5e1158e39d --- /dev/null +++ b/esphome/components/veml7700/veml7700.h @@ -0,0 +1,202 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/optional.h" + +namespace esphome { +namespace veml7700 { + +using esphome::i2c::ErrorCode; + +// +// Datasheet: https://www.vishay.com/docs/84286/veml7700.pdf +// + +enum class CommandRegisters : uint8_t { + ALS_CONF_0 = 0x00, // W: ALS gain, integration time, interrupt, and shutdown + ALS_WH = 0x01, // W: ALS high threshold window setting + ALS_WL = 0x02, // W: ALS low threshold window setting + PWR_SAVING = 0x03, // W: Set (15 : 3) 0000 0000 0000 0b + ALS = 0x04, // R: MSB, LSB data of whole ALS 16 bits + WHITE = 0x05, // R: MSB, LSB data of whole WHITE 16 bits + ALS_INT = 0x06 // R: ALS INT trigger event +}; + +enum Gain : uint8_t { + X_1 = 0, + X_2 = 1, + X_1_8 = 2, + X_1_4 = 3, +}; +const uint8_t GAINS_COUNT = 4; + +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_25MS = 0b1100, + INTEGRATION_TIME_50MS = 0b1000, + INTEGRATION_TIME_100MS = 0b0000, + INTEGRATION_TIME_200MS = 0b0001, + INTEGRATION_TIME_400MS = 0b0010, + INTEGRATION_TIME_800MS = 0b0011, +}; +const uint8_t INTEGRATION_TIMES_COUNT = 6; + +enum Persistence : uint8_t { + PERSISTENCE_1 = 0, + PERSISTENCE_2 = 1, + PERSISTENCE_4 = 2, + PERSISTENCE_8 = 3, +}; + +enum PSM : uint8_t { + PSM_MODE_1 = 0, + PSM_MODE_2 = 1, + PSM_MODE_3 = 2, + PSM_MODE_4 = 3, +}; + +// The following section with bit-fields brings GCC compilation 'notes' about padding bytes due to bug in older GCC back +// in 2009 "Packed bit-fields of type char were not properly bit-packed on many targets prior to GCC 4.4" Even more to +// this - this message can't be disabled with "#pragma GCC diagnostic ignored" due to another bug which was only fixed +// in GCC 13 in 2022 :) No actions required, it is just a note. The code is correct. + +// +// VEML7700_CR_ALS_CONF_0 Register (0x00) +// +union ConfigurationRegister { + uint16_t raw; + uint8_t raw_bytes[2]; + struct { + bool ALS_SD : 1; // ALS shut down setting: 0 = ALS power on, 1 = ALS shut + // down + bool ALS_INT_EN : 1; // ALS interrupt enable setting: 0 = ALS INT disable, 1 + // = ALS INT enable + bool reserved_2 : 1; // 0 + bool reserved_3 : 1; // 0 + Persistence ALS_PERS : 2; // 00 - 1, 01- 2, 10 - 4, 11 - 8 + IntegrationTime ALS_IT : 4; // ALS integration time setting + bool reserved_10 : 1; // 0 + Gain ALS_GAIN : 2; // Gain selection + bool reserved_13 : 1; // 0 + bool reserved_14 : 1; // 0 + bool reserved_15 : 1; // 0 + } __attribute__((packed)); +}; + +// +// Power Saving Mode: PSM Register (0x03) +// +union PSMRegister { + uint16_t raw; + uint8_t raw_bytes[2]; + struct { + bool PSM_EN : 1; + uint8_t PSM : 2; + uint16_t reserved : 13; + } __attribute__((packed)); +}; + +class VEML7700Component : public PollingComponent, public i2c::I2CDevice { + public: + // + // EspHome framework functions + // + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + // + // Configuration setters + // + void set_gain(Gain gain) { this->gain_ = gain; } + void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } + void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } + void set_enable_lux_compensation(bool enable) { this->lux_compensation_enabled_ = enable; } + void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } + + void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; } + void set_ambient_light_counts_sensor(sensor::Sensor *sensor) { this->ambient_light_counts_sensor_ = sensor; } + void set_white_sensor(sensor::Sensor *sensor) { this->white_sensor_ = sensor; } + void set_white_counts_sensor(sensor::Sensor *sensor) { this->white_counts_sensor_ = sensor; } + void set_infrared_sensor(sensor::Sensor *sensor) { this->fake_infrared_sensor_ = sensor; } + void set_actual_gain_sensor(sensor::Sensor *sensor) { this->actual_gain_sensor_ = sensor; } + void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } + + protected: + // + // Internal state machine, used to split all the actions into + // small steps in loop() to make sure we are not blocking execution + // + enum class State : uint8_t { + NOT_INITIALIZED, + INITIAL_SETUP_COMPLETED, + IDLE, + COLLECTING_DATA, + COLLECTING_DATA_AUTO, + DATA_COLLECTED, + ADJUSTMENT_NEEDED, + ADJUSTMENT_IN_PROGRESS, + READY_TO_APPLY_ADJUSTMENTS, + READY_TO_PUBLISH_PART_1, + READY_TO_PUBLISH_PART_2, + READY_TO_PUBLISH_PART_3 + } state_{State::NOT_INITIALIZED}; + + // + // Current measurements data + // + struct Readings { + uint16_t als_counts{0}; + uint16_t white_counts{0}; + IntegrationTime actual_time{INTEGRATION_TIME_100MS}; + Gain actual_gain{X_1_8}; + float als_lux{0}; + float white_lux{0}; + float fake_infrared_lux{0}; + ErrorCode err{i2c::ERROR_OK}; + } readings_; + + // + // Device interaction + // + ErrorCode configure_(); + ErrorCode reconfigure_time_and_gain_(IntegrationTime time, Gain gain, bool shutdown); + ErrorCode read_sensor_output_(Readings &data); + + // + // Working with the data + // + bool are_adjustments_required_(Readings &data); + void apply_lux_calculation_(Readings &data); + void apply_lux_compensation_(Readings &data); + void apply_glass_attenuation_(Readings &data); + void publish_data_part_1_(Readings &data); + void publish_data_part_2_(Readings &data); + void publish_data_part_3_(Readings &data); + + // + // Component configuration + // + bool automatic_mode_enabled_{true}; + bool lux_compensation_enabled_{true}; + float glass_attenuation_factor_{1.0}; + IntegrationTime integration_time_{INTEGRATION_TIME_100MS}; + Gain gain_{X_1}; + + // + // Sensors for publishing data + // + sensor::Sensor *ambient_light_sensor_{nullptr}; // Human eye range 500-600 nm, lx + sensor::Sensor *ambient_light_counts_sensor_{nullptr}; // Raw counts + sensor::Sensor *white_sensor_{nullptr}; // Wide range 450-950 nm, lx + sensor::Sensor *white_counts_sensor_{nullptr}; // Raw counts + sensor::Sensor *fake_infrared_sensor_{nullptr}; // Artificial. = WHITE lx - ALS lx. + sensor::Sensor *actual_gain_sensor_{nullptr}; // Actual gain multiplier for the measurement + sensor::Sensor *actual_integration_time_sensor_{nullptr}; // Actual integration time for the measurement +}; + +} // namespace veml7700 +} // namespace esphome diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 55d995be88f1..3ba0c58ce4b1 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -6,6 +6,9 @@ CONF_MICROPHONE, CONF_SPEAKER, CONF_MEDIA_PLAYER, + CONF_ON_CLIENT_CONNECTED, + CONF_ON_CLIENT_DISCONNECTED, + CONF_ON_IDLE, ) from esphome import automation from esphome.automation import register_action, register_condition @@ -16,14 +19,30 @@ CODEOWNERS = ["@jesserockz"] -CONF_SILENCE_DETECTION = "silence_detection" +CONF_ON_END = "on_end" +CONF_ON_ERROR = "on_error" +CONF_ON_INTENT_END = "on_intent_end" +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_TTS_START = "on_tts_start" +CONF_ON_STT_VAD_END = "on_stt_vad_end" +CONF_ON_STT_VAD_START = "on_stt_vad_start" CONF_ON_TTS_END = "on_tts_end" -CONF_ON_END = "on_end" -CONF_ON_ERROR = "on_error" +CONF_ON_TTS_START = "on_tts_start" +CONF_ON_TTS_STREAM_START = "on_tts_stream_start" +CONF_ON_TTS_STREAM_END = "on_tts_stream_end" +CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" + +CONF_SILENCE_DETECTION = "silence_detection" +CONF_USE_WAKE_WORD = "use_wake_word" +CONF_VAD_THRESHOLD = "vad_threshold" + +CONF_AUTO_GAIN = "auto_gain" +CONF_NOISE_SUPPRESSION_LEVEL = "noise_suppression_level" +CONF_VOLUME_MULTIPLIER = "volume_multiplier" + +CONF_WAKE_WORD = "wake_word" voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant") @@ -41,24 +60,81 @@ IsRunningCondition = voice_assistant_ns.class_( "IsRunningCondition", automation.Condition, cg.Parented.template(VoiceAssistant) ) +ConnectedCondition = voice_assistant_ns.class_( + "ConnectedCondition", automation.Condition, cg.Parented.template(VoiceAssistant) +) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(VoiceAssistant), - cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), - cv.Exclusive(CONF_SPEAKER, "output"): cv.use_id(speaker.Speaker), - cv.Exclusive(CONF_MEDIA_PLAYER, "output"): cv.use_id(media_player.MediaPlayer), - cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean, - cv.Optional(CONF_ON_LISTENING): automation.validate_automation(single=True), - cv.Optional(CONF_ON_START): automation.validate_automation(single=True), - cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True), - cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True), - cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True), - cv.Optional(CONF_ON_END): automation.validate_automation(single=True), - cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), - } -).extend(cv.COMPONENT_SCHEMA) +def tts_stream_validate(config): + if CONF_SPEAKER not in config and ( + CONF_ON_TTS_STREAM_START in config or CONF_ON_TTS_STREAM_END in config + ): + raise cv.Invalid( + f"{CONF_SPEAKER} is required when using {CONF_ON_TTS_STREAM_START} and/or {CONF_ON_TTS_STREAM_END}" + ) + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(VoiceAssistant), + cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), + cv.Exclusive(CONF_SPEAKER, "output"): cv.use_id(speaker.Speaker), + cv.Exclusive(CONF_MEDIA_PLAYER, "output"): cv.use_id( + media_player.MediaPlayer + ), + cv.Optional(CONF_USE_WAKE_WORD, default=False): cv.boolean, + cv.Optional(CONF_VAD_THRESHOLD): cv.All( + cv.requires_component("esp_adf"), cv.only_with_esp_idf, cv.uint8_t + ), + cv.Optional(CONF_NOISE_SUPPRESSION_LEVEL, default=0): cv.int_range(0, 4), + cv.Optional(CONF_AUTO_GAIN, default="0dBFS"): cv.All( + cv.float_with_unit("decibel full scale", "(dBFS|dbfs|DBFS)"), + cv.int_range(0, 31), + ), + cv.Optional(CONF_VOLUME_MULTIPLIER, default=1.0): cv.float_range( + min=0.0, min_included=False + ), + cv.Optional(CONF_ON_LISTENING): automation.validate_automation(single=True), + cv.Optional(CONF_ON_START): automation.validate_automation(single=True), + cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True), + cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), + cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_INTENT_START): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_INTENT_END): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_STT_VAD_START): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_STT_VAD_END): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TTS_STREAM_START): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True), + } + ).extend(cv.COMPONENT_SCHEMA), + tts_stream_validate, +) async def to_code(config): @@ -76,7 +152,14 @@ async def to_code(config): mp = await cg.get_variable(config[CONF_MEDIA_PLAYER]) cg.add(var.set_media_player(mp)) - cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION])) + cg.add(var.set_use_wake_word(config[CONF_USE_WAKE_WORD])) + + if (vad_threshold := config.get(CONF_VAD_THRESHOLD)) is not None: + cg.add(var.set_vad_threshold(vad_threshold)) + + cg.add(var.set_noise_suppression_level(config[CONF_NOISE_SUPPRESSION_LEVEL])) + cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN])) + cg.add(var.set_volume_multiplier(config[CONF_VOLUME_MULTIPLIER])) if CONF_ON_LISTENING in config: await automation.build_automation( @@ -88,6 +171,13 @@ async def to_code(config): var.get_start_trigger(), [], config[CONF_ON_START] ) + if CONF_ON_WAKE_WORD_DETECTED in config: + await automation.build_automation( + var.get_wake_word_detected_trigger(), + [], + config[CONF_ON_WAKE_WORD_DETECTED], + ) + if CONF_ON_STT_END in config: await automation.build_automation( var.get_stt_end_trigger(), [(cg.std_string, "x")], config[CONF_ON_STT_END] @@ -117,6 +207,69 @@ async def to_code(config): config[CONF_ON_ERROR], ) + if CONF_ON_CLIENT_CONNECTED in config: + await automation.build_automation( + var.get_client_connected_trigger(), + [], + config[CONF_ON_CLIENT_CONNECTED], + ) + + if CONF_ON_CLIENT_DISCONNECTED in config: + await automation.build_automation( + var.get_client_disconnected_trigger(), + [], + config[CONF_ON_CLIENT_DISCONNECTED], + ) + + if CONF_ON_INTENT_START in config: + await automation.build_automation( + var.get_intent_start_trigger(), + [], + config[CONF_ON_INTENT_START], + ) + + if CONF_ON_INTENT_END in config: + await automation.build_automation( + var.get_intent_end_trigger(), + [], + config[CONF_ON_INTENT_END], + ) + + if CONF_ON_STT_VAD_START in config: + await automation.build_automation( + var.get_stt_vad_start_trigger(), + [], + config[CONF_ON_STT_VAD_START], + ) + + if CONF_ON_STT_VAD_END in config: + await automation.build_automation( + var.get_stt_vad_end_trigger(), + [], + config[CONF_ON_STT_VAD_END], + ) + + if CONF_ON_TTS_STREAM_START in config: + await automation.build_automation( + var.get_tts_stream_start_trigger(), + [], + config[CONF_ON_TTS_STREAM_START], + ) + + if CONF_ON_TTS_STREAM_END in config: + await automation.build_automation( + var.get_tts_stream_end_trigger(), + [], + config[CONF_ON_TTS_STREAM_END], + ) + + if CONF_ON_IDLE in config: + await automation.build_automation( + var.get_idle_trigger(), + [], + config[CONF_ON_IDLE], + ) + cg.add_define("USE_VOICE_ASSISTANT") @@ -128,10 +281,24 @@ async def to_code(config): StartContinuousAction, VOICE_ASSISTANT_ACTION_SCHEMA, ) -@register_action("voice_assistant.start", StartAction, VOICE_ASSISTANT_ACTION_SCHEMA) +@register_action( + "voice_assistant.start", + StartAction, + VOICE_ASSISTANT_ACTION_SCHEMA.extend( + { + cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean, + cv.Optional(CONF_WAKE_WORD): cv.templatable(cv.string), + } + ), +) async def voice_assistant_listen_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) + if CONF_SILENCE_DETECTION in config: + cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION])) + if wake_word := config.get(CONF_WAKE_WORD): + templ = await cg.templatable(wake_word, args, cg.std_string) + cg.add(var.set_wake_word(templ)) return var @@ -149,3 +316,12 @@ async def voice_assistant_is_running_to_code(config, condition_id, template_arg, var = cg.new_Pvariable(condition_id, template_arg) await cg.register_parented(var, config[CONF_ID]) return var + + +@register_condition( + "voice_assistant.connected", ConnectedCondition, VOICE_ASSISTANT_ACTION_SCHEMA +) +async def voice_assistant_connected_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 217ddb6354bb..109e52f8eb06 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -11,30 +11,37 @@ namespace voice_assistant { static const char *const TAG = "voice_assistant"; -float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } +#ifdef SAMPLE_RATE_HZ +#undef SAMPLE_RATE_HZ +#endif -void VoiceAssistant::setup() { - ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); +static const size_t SAMPLE_RATE_HZ = 16000; +static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms +static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000; +static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); +static const size_t RECEIVE_SIZE = 1024; +static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; - global_voice_assistant = this; +float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } +bool VoiceAssistant::start_udp_socket_() { this->socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (socket_ == nullptr) { - ESP_LOGW(TAG, "Could not create socket."); + if (this->socket_ == nullptr) { + ESP_LOGE(TAG, "Could not create socket"); this->mark_failed(); - return; + return false; } int enable = 1; - int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); if (err != 0) { ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); // we can still continue } - err = socket_->setblocking(false); + err = this->socket_->setblocking(false); if (err != 0) { - ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + ESP_LOGE(TAG, "Socket unable to set nonblocking mode: errno %d", err); this->mark_failed(); - return; + return false; } #ifdef USE_SPEAKER @@ -43,72 +50,431 @@ void VoiceAssistant::setup() { socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055); if (sl == 0) { - ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + ESP_LOGE(TAG, "Socket unable to set sockaddr: errno %d", errno); this->mark_failed(); - return; + return false; } - server.ss_family = AF_INET; - err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + err = this->socket_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { - ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); - return; + return false; } } #endif + this->udp_socket_running_ = true; + return true; +} - this->mic_->add_data_callback([this](const std::vector &data) { - if (!this->running_) { +void VoiceAssistant::setup() { + ESP_LOGCONFIG(TAG, "Setting up Voice Assistant..."); + + global_voice_assistant = this; + +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + ExternalRAMAllocator speaker_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE); + if (this->speaker_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate speaker buffer"); + this->mark_failed(); return; } - this->socket_->sendto(data.data(), data.size() * sizeof(int16_t), 0, (struct sockaddr *) &this->dest_addr_, - sizeof(this->dest_addr_)); - }); + } +#endif + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE); + if (this->input_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate input buffer"); + this->mark_failed(); + return; + } + +#ifdef USE_ESP_ADF + this->vad_instance_ = vad_create(VAD_MODE_4); +#endif + + this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); + if (this->ring_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate ring buffer"); + this->mark_failed(); + return; + } + + ExternalRAMAllocator send_allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE); + if (send_buffer_ == nullptr) { + ESP_LOGW(TAG, "Could not allocate send buffer"); + this->mark_failed(); + return; + } +} + +int VoiceAssistant::read_microphone_() { + size_t bytes_read = 0; + if (this->mic_->is_running()) { // Read audio into input buffer + bytes_read = this->mic_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); + if (bytes_read == 0) { + memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t)); + return 0; + } + // Write audio into ring buffer + this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); + } else { + ESP_LOGD(TAG, "microphone not running"); + } + return bytes_read; } void VoiceAssistant::loop() { -#ifdef USE_SPEAKER - if (this->speaker_ != nullptr) { - uint8_t buf[1024]; - auto len = this->socket_->read(buf, sizeof(buf)); - if (len == -1) { - return; + if (this->api_client_ == nullptr && this->state_ != State::IDLE && this->state_ != State::STOP_MICROPHONE && + this->state_ != State::STOPPING_MICROPHONE) { + if (this->mic_->is_running() || this->state_ == State::STARTING_MICROPHONE) { + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + } else { + this->set_state_(State::IDLE, State::IDLE); } - this->speaker_->play(buf, len); - this->set_timeout("data-incoming", 200, [this]() { - if (this->continuous_) { - this->request_start(true); - } - }); + this->continuous_ = false; + this->signal_stop_(); return; } + switch (this->state_) { + case State::IDLE: { + if (this->continuous_ && this->desired_state_ == State::IDLE) { + this->idle_trigger_->trigger(); + + this->ring_buffer_->reset(); +#ifdef USE_ESP_ADF + if (this->use_wake_word_) { + this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); + } else +#endif + { + this->set_state_(State::START_MICROPHONE, State::START_PIPELINE); + } + } else { + this->high_freq_.stop(); + } + break; + } + case State::START_MICROPHONE: { + ESP_LOGD(TAG, "Starting Microphone"); + memset(this->send_buffer_, 0, SEND_BUFFER_SIZE); + memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t)); + this->mic_->start(); + this->high_freq_.start(); + this->set_state_(State::STARTING_MICROPHONE); + break; + } + case State::STARTING_MICROPHONE: { + if (this->mic_->is_running()) { + this->set_state_(this->desired_state_); + } + break; + } +#ifdef USE_ESP_ADF + case State::WAIT_FOR_VAD: { + this->read_microphone_(); + ESP_LOGD(TAG, "Waiting for speech..."); + this->set_state_(State::WAITING_FOR_VAD); + break; + } + case State::WAITING_FOR_VAD: { + size_t bytes_read = this->read_microphone_(); + if (bytes_read > 0) { + vad_state_t vad_state = + vad_process(this->vad_instance_, this->input_buffer_, SAMPLE_RATE_HZ, VAD_FRAME_LENGTH_MS); + if (vad_state == VAD_SPEECH) { + if (this->vad_counter_ < this->vad_threshold_) { + this->vad_counter_++; + } else { + ESP_LOGD(TAG, "VAD detected speech"); + this->set_state_(State::START_PIPELINE, State::STREAMING_MICROPHONE); + + // Reset for next time + this->vad_counter_ = 0; + } + } else { + if (this->vad_counter_ > 0) { + this->vad_counter_--; + } + } + } + break; + } +#endif + case State::START_PIPELINE: { + this->read_microphone_(); + ESP_LOGD(TAG, "Requesting start..."); + uint32_t flags = 0; + if (this->use_wake_word_) + flags |= api::enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD; + if (this->silence_detection_) + flags |= api::enums::VOICE_ASSISTANT_REQUEST_USE_VAD; + api::VoiceAssistantAudioSettings audio_settings; + audio_settings.noise_suppression_level = this->noise_suppression_level_; + audio_settings.auto_gain = this->auto_gain_; + audio_settings.volume_multiplier = this->volume_multiplier_; + + api::VoiceAssistantRequest msg; + msg.start = true; + msg.conversation_id = this->conversation_id_; + msg.flags = flags; + msg.audio_settings = audio_settings; + msg.wake_word_phrase = this->wake_word_; + this->wake_word_ = ""; + + if (this->api_client_ == nullptr || !this->api_client_->send_voice_assistant_request(msg)) { + ESP_LOGW(TAG, "Could not request start"); + this->error_trigger_->trigger("not-connected", "Could not request start"); + this->continuous_ = false; + this->set_state_(State::IDLE, State::IDLE); + break; + } + this->set_state_(State::STARTING_PIPELINE); + this->set_timeout("reset-conversation_id", 5 * 60 * 1000, [this]() { this->conversation_id_ = ""; }); + break; + } + case State::STARTING_PIPELINE: { + this->read_microphone_(); + break; // State changed when udp server port received + } + case State::STREAMING_MICROPHONE: { + this->read_microphone_(); + size_t available = this->ring_buffer_->available(); + while (available >= SEND_BUFFER_SIZE) { + 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.data.assign((char *) this->send_buffer_, read_bytes); + this->api_client_->send_voice_assistant_audio(msg); + } else { + if (!this->udp_socket_running_) { + if (!this->start_udp_socket_()) { + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + break; + } + } + this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_, + sizeof(this->dest_addr_)); + } + available = this->ring_buffer_->available(); + } + + break; + } + case State::STOP_MICROPHONE: { + if (this->mic_->is_running()) { + this->mic_->stop(); + this->set_state_(State::STOPPING_MICROPHONE); + } else { + this->set_state_(this->desired_state_); + } + break; + } + case State::STOPPING_MICROPHONE: { + if (this->mic_->is_stopped()) { + this->set_state_(this->desired_state_); + } + break; + } + case State::AWAITING_RESPONSE: { + break; // State changed by events + } + case State::STREAMING_RESPONSE: { + bool playing = false; +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + ssize_t received_len = 0; + if (this->audio_mode_ == AUDIO_MODE_UDP) { + if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) { + received_len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE); + if (received_len > 0) { + this->speaker_buffer_index_ += received_len; + this->speaker_buffer_size_ += received_len; + this->speaker_bytes_received_ += received_len; + } + } else { + ESP_LOGD(TAG, "Receive buffer full"); + } + } + // Build a small buffer of audio before sending to the speaker + bool end_of_stream = this->stream_ended_ && (this->audio_mode_ == AUDIO_MODE_API || received_len < 0); + if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4 || end_of_stream) + this->write_speaker_(); + if (this->wait_for_stream_end_) { + this->cancel_timeout("playing"); + if (end_of_stream) { + ESP_LOGD(TAG, "End of audio stream received"); + this->cancel_timeout("speaker-timeout"); + this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); + } + break; // We dont want to timeout here as the STREAM_END event will take care of that. + } + playing = this->speaker_->is_running(); + } #endif #ifdef USE_MEDIA_PLAYER - if (this->media_player_ != nullptr) { - if (!this->playing_tts_ || - this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING) { - return; + if (this->media_player_ != nullptr) { + playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING); + } +#endif + if (playing) { + this->set_timeout("playing", 2000, [this]() { + this->cancel_timeout("speaker-timeout"); + this->set_state_(State::IDLE, State::IDLE); + }); + } + break; } - this->set_timeout("playing-media", 1000, [this]() { - this->playing_tts_ = false; - if (this->continuous_) { - this->request_start(true); + case State::RESPONSE_FINISHED: { +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + if (this->speaker_buffer_size_ > 0) { + this->write_speaker_(); + break; + } + if (this->speaker_->has_buffered_data() || this->speaker_->is_running()) { + break; + } + ESP_LOGD(TAG, "Speaker has finished outputting all audio"); + this->speaker_->stop(); + this->cancel_timeout("speaker-timeout"); + this->cancel_timeout("playing"); + this->speaker_buffer_size_ = 0; + this->speaker_buffer_index_ = 0; + this->speaker_bytes_received_ = 0; + memset(this->speaker_buffer_, 0, SPEAKER_BUFFER_SIZE); + this->wait_for_stream_end_ = false; + this->stream_ended_ = false; + + this->tts_stream_end_trigger_->trigger(); } - }); - return; +#endif + this->set_state_(State::IDLE, State::IDLE); + break; + } + default: + break; } +} + +#ifdef USE_SPEAKER +void VoiceAssistant::write_speaker_() { + if (this->speaker_buffer_size_ > 0) { + size_t written = this->speaker_->play(this->speaker_buffer_, this->speaker_buffer_size_); + if (written > 0) { + memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); + this->speaker_buffer_size_ -= written; + this->speaker_buffer_index_ -= written; + this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); + } else { + ESP_LOGD(TAG, "Speaker buffer full, trying again next loop"); + } + } +} #endif - // Set a 1 second timeout to start the voice assistant again. - this->set_timeout("continuous-no-sound", 1000, [this]() { - if (this->continuous_) { - this->request_start(true); + +void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscribe) { + if (!subscribe) { + if (this->api_client_ == nullptr || client != this->api_client_) { + ESP_LOGE(TAG, "Client attempting to unsubscribe that is not the current API Client"); + return; } - }); + this->api_client_ = nullptr; + this->client_disconnected_trigger_->trigger(); + return; + } + + if (this->api_client_ != nullptr) { + ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant"); + ESP_LOGE(TAG, "Current client: %s", this->api_client_->get_client_combined_info().c_str()); + ESP_LOGE(TAG, "New client: %s", client->get_client_combined_info().c_str()); + return; + } + + this->api_client_ = client; + this->client_connected_trigger_->trigger(); } -void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { - ESP_LOGD(TAG, "Starting..."); +static const LogString *voice_assistant_state_to_string(State state) { + switch (state) { + case State::IDLE: + return LOG_STR("IDLE"); + case State::START_MICROPHONE: + return LOG_STR("START_MICROPHONE"); + case State::STARTING_MICROPHONE: + return LOG_STR("STARTING_MICROPHONE"); + case State::WAIT_FOR_VAD: + return LOG_STR("WAIT_FOR_VAD"); + case State::WAITING_FOR_VAD: + return LOG_STR("WAITING_FOR_VAD"); + case State::START_PIPELINE: + return LOG_STR("START_PIPELINE"); + case State::STARTING_PIPELINE: + return LOG_STR("STARTING_PIPELINE"); + case State::STREAMING_MICROPHONE: + return LOG_STR("STREAMING_MICROPHONE"); + case State::STOP_MICROPHONE: + return LOG_STR("STOP_MICROPHONE"); + case State::STOPPING_MICROPHONE: + return LOG_STR("STOPPING_MICROPHONE"); + case State::AWAITING_RESPONSE: + return LOG_STR("AWAITING_RESPONSE"); + case State::STREAMING_RESPONSE: + return LOG_STR("STREAMING_RESPONSE"); + case State::RESPONSE_FINISHED: + return LOG_STR("RESPONSE_FINISHED"); + default: + return LOG_STR("UNKNOWN"); + } +}; + +void VoiceAssistant::set_state_(State state) { + State old_state = this->state_; + this->state_ = state; + ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(voice_assistant_state_to_string(old_state)), + LOG_STR_ARG(voice_assistant_state_to_string(state))); +} + +void VoiceAssistant::set_state_(State state, State desired_state) { + this->set_state_(state); + this->desired_state_ = desired_state; + ESP_LOGD(TAG, "Desired state set to %s", LOG_STR_ARG(voice_assistant_state_to_string(desired_state))); +} + +void VoiceAssistant::failed_to_start() { + ESP_LOGE(TAG, "Failed to start server. See Home Assistant logs for more details."); + this->error_trigger_->trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details."); + this->set_state_(State::STOP_MICROPHONE, State::IDLE); +} + +void VoiceAssistant::start_streaming() { + if (this->state_ != State::STARTING_PIPELINE) { + this->signal_stop_(); + return; + } + + ESP_LOGD(TAG, "Client started, streaming microphone"); + this->audio_mode_ = AUDIO_MODE_API; + + if (this->mic_->is_running()) { + this->set_state_(State::STREAMING_MICROPHONE, State::STREAMING_MICROPHONE); + } else { + this->set_state_(State::START_MICROPHONE, State::STREAMING_MICROPHONE); + } +} + +void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t port) { + if (this->state_ != State::STARTING_PIPELINE) { + this->signal_stop_(); + return; + } + + ESP_LOGD(TAG, "Client started, streaming microphone"); + this->audio_mode_ = AUDIO_MODE_UDP; memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_)); if (this->dest_addr_.ss_family == AF_INET) { @@ -123,36 +489,93 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { ESP_LOGW(TAG, "Unknown address family: %d", this->dest_addr_.ss_family); return; } - this->running_ = true; - this->mic_->start(); - this->listening_trigger_->trigger(); + + if (this->mic_->is_running()) { + this->set_state_(State::STREAMING_MICROPHONE, State::STREAMING_MICROPHONE); + } else { + this->set_state_(State::START_MICROPHONE, State::STREAMING_MICROPHONE); + } } -void VoiceAssistant::request_start(bool continuous) { - ESP_LOGD(TAG, "Requesting start..."); - if (!api::global_api_server->start_voice_assistant(this->conversation_id_, this->silence_detection_)) { - ESP_LOGW(TAG, "Could not request start."); - this->error_trigger_->trigger("not-connected", "Could not request start."); +void VoiceAssistant::request_start(bool continuous, bool silence_detection) { + if (this->api_client_ == nullptr) { + ESP_LOGE(TAG, "No API client connected"); + this->set_state_(State::IDLE, State::IDLE); this->continuous_ = false; return; } - this->continuous_ = continuous; - this->set_timeout("reset-conversation_id", 5 * 60 * 1000, [this]() { this->conversation_id_ = ""; }); + if (this->state_ == State::IDLE) { + this->continuous_ = continuous; + this->silence_detection_ = silence_detection; + this->ring_buffer_->reset(); +#ifdef USE_ESP_ADF + if (this->use_wake_word_) { + this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); + } else +#endif + { + this->set_state_(State::START_MICROPHONE, State::START_PIPELINE); + } + } } -void VoiceAssistant::signal_stop() { - ESP_LOGD(TAG, "Signaling stop..."); - this->mic_->stop(); - this->running_ = false; - api::global_api_server->stop_voice_assistant(); +void VoiceAssistant::request_stop() { + this->continuous_ = false; + + switch (this->state_) { + case State::IDLE: + break; + case State::START_MICROPHONE: + case State::STARTING_MICROPHONE: + case State::WAIT_FOR_VAD: + case State::WAITING_FOR_VAD: + case State::START_PIPELINE: + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + break; + case State::STARTING_PIPELINE: + case State::STREAMING_MICROPHONE: + this->signal_stop_(); + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + break; + case State::STOP_MICROPHONE: + case State::STOPPING_MICROPHONE: + this->desired_state_ = State::IDLE; + break; + case State::AWAITING_RESPONSE: + case State::STREAMING_RESPONSE: + case State::RESPONSE_FINISHED: + break; // Let the incoming audio stream finish then it will go to idle. + } +} + +void VoiceAssistant::signal_stop_() { memset(&this->dest_addr_, 0, sizeof(this->dest_addr_)); + if (this->api_client_ == nullptr) { + return; + } + ESP_LOGD(TAG, "Signaling stop..."); + api::VoiceAssistantRequest msg; + msg.start = false; + this->api_client_->send_voice_assistant_request(msg); } void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { + ESP_LOGD(TAG, "Event Type: %d", msg.event_type); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_RUN_START: ESP_LOGD(TAG, "Assist Pipeline running"); - this->start_trigger_->trigger(); + this->defer([this]() { this->start_trigger_->trigger(); }); + break; + case api::enums::VOICE_ASSISTANT_WAKE_WORD_START: + break; + case api::enums::VOICE_ASSISTANT_WAKE_WORD_END: { + ESP_LOGD(TAG, "Wake word detected"); + this->defer([this]() { this->wake_word_detected_trigger_->trigger(); }); + break; + } + case api::enums::VOICE_ASSISTANT_STT_START: + ESP_LOGD(TAG, "STT started"); + this->defer([this]() { this->listening_trigger_->trigger(); }); break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; @@ -162,20 +585,24 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } if (text.empty()) { - ESP_LOGW(TAG, "No text in STT_END event."); + ESP_LOGW(TAG, "No text in STT_END event"); return; } ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); - this->signal_stop(); - this->stt_end_trigger_->trigger(text); + this->defer([this, text]() { this->stt_end_trigger_->trigger(text); }); break; } + case api::enums::VOICE_ASSISTANT_INTENT_START: + ESP_LOGD(TAG, "Intent started"); + this->defer([this]() { this->intent_start_trigger_->trigger(); }); + break; case api::enums::VOICE_ASSISTANT_INTENT_END: { for (auto arg : msg.data) { if (arg.name == "conversation_id") { this->conversation_id_ = std::move(arg.value); } } + this->defer([this]() { this->intent_end_trigger_->trigger(); }); break; } case api::enums::VOICE_ASSISTANT_TTS_START: { @@ -186,11 +613,16 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } if (text.empty()) { - ESP_LOGW(TAG, "No text in TTS_START event."); + ESP_LOGW(TAG, "No text in TTS_START event"); return; } ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); - this->tts_start_trigger_->trigger(text); + this->defer([this, text]() { + this->tts_start_trigger_->trigger(text); +#ifdef USE_SPEAKER + this->speaker_->start(); +#endif + }); break; } case api::enums::VOICE_ASSISTANT_TTS_END: { @@ -201,23 +633,42 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } if (url.empty()) { - ESP_LOGW(TAG, "No url in TTS_END event."); + ESP_LOGW(TAG, "No url in TTS_END event"); return; } ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str()); + this->defer([this, url]() { #ifdef USE_MEDIA_PLAYER - if (this->media_player_ != nullptr) { - this->playing_tts_ = true; - this->media_player_->make_call().set_media_url(url).perform(); - } + if (this->media_player_ != nullptr) { + this->media_player_->make_call().set_media_url(url).set_announcement(true).perform(); + } #endif - this->tts_end_trigger_->trigger(url); + this->tts_end_trigger_->trigger(url); + }); + State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE; + this->set_state_(new_state, new_state); break; } - case api::enums::VOICE_ASSISTANT_RUN_END: + case api::enums::VOICE_ASSISTANT_RUN_END: { ESP_LOGD(TAG, "Assist Pipeline ended"); - this->end_trigger_->trigger(); + if (this->state_ == State::STREAMING_MICROPHONE) { + this->ring_buffer_->reset(); +#ifdef USE_ESP_ADF + if (this->use_wake_word_) { + // No need to stop the microphone since we didn't use the speaker + this->set_state_(State::WAIT_FOR_VAD, State::WAITING_FOR_VAD); + } else +#endif + { + this->set_state_(State::IDLE, State::IDLE); + } + } else if (this->state_ == State::AWAITING_RESPONSE) { + // No TTS start event ("nevermind") + this->set_state_(State::IDLE, State::IDLE); + } + this->defer([this]() { this->end_trigger_->trigger(); }); break; + } case api::enums::VOICE_ASSISTANT_ERROR: { std::string code = ""; std::string message = ""; @@ -228,16 +679,68 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { message = std::move(arg.value); } } + if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") { + // Don't change state here since either the "tts-end" or "run-end" events will do it. + return; + } else if (code == "wake-provider-missing" || code == "wake-engine-missing") { + // Wake word is not set up or not ready on Home Assistant so stop and do not retry until user starts again. + this->defer([this, code, message]() { + this->request_stop(); + this->error_trigger_->trigger(code, message); + }); + return; + } ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str()); - this->continuous_ = false; - this->signal_stop(); - this->error_trigger_->trigger(code, message); + if (this->state_ != State::IDLE) { + this->signal_stop_(); + this->set_state_(State::STOP_MICROPHONE, State::IDLE); + } + this->defer([this, code, message]() { this->error_trigger_->trigger(code, message); }); + break; } + case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: { +#ifdef USE_SPEAKER + this->wait_for_stream_end_ = true; + ESP_LOGD(TAG, "TTS stream start"); + this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); +#endif + break; + } + case api::enums::VOICE_ASSISTANT_TTS_STREAM_END: { +#ifdef USE_SPEAKER + this->stream_ended_ = true; + ESP_LOGD(TAG, "TTS stream end"); +#endif + break; + } + case api::enums::VOICE_ASSISTANT_STT_VAD_START: + ESP_LOGD(TAG, "Starting STT by VAD"); + this->defer([this]() { this->stt_vad_start_trigger_->trigger(); }); + break; + case api::enums::VOICE_ASSISTANT_STT_VAD_END: + ESP_LOGD(TAG, "STT by VAD end"); + this->set_state_(State::STOP_MICROPHONE, State::AWAITING_RESPONSE); + this->defer([this]() { this->stt_vad_end_trigger_->trigger(); }); + break; default: + ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type); break; } } +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_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(); + } else { + ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); + } +#endif +} + VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace voice_assistant diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 75c17965bc5e..1c0ea12f4fe0 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -7,9 +7,10 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/ring_buffer.h" +#include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" -#include "esphome/components/api/api_server.h" #include "esphome/components/microphone/microphone.h" #ifdef USE_SPEAKER #include "esphome/components/speaker/speaker.h" @@ -19,102 +20,240 @@ #endif #include "esphome/components/socket/socket.h" +#ifdef USE_ESP_ADF +#include +#endif + namespace esphome { namespace voice_assistant { // Version 1: Initial version // Version 2: Adds raw speaker support -// Version 3: Unused/skip -static const uint32_t INITIAL_VERSION = 1; -static const uint32_t SPEAKER_SUPPORT = 2; +static const uint32_t LEGACY_INITIAL_VERSION = 1; +static const uint32_t LEGACY_SPEAKER_SUPPORT = 2; + +enum VoiceAssistantFeature : uint32_t { + FEATURE_VOICE_ASSISTANT = 1 << 0, + FEATURE_SPEAKER = 1 << 1, + FEATURE_API_AUDIO = 1 << 2, +}; + +enum class State { + IDLE, + START_MICROPHONE, + STARTING_MICROPHONE, + WAIT_FOR_VAD, + WAITING_FOR_VAD, + START_PIPELINE, + STARTING_PIPELINE, + STREAMING_MICROPHONE, + STOP_MICROPHONE, + STOPPING_MICROPHONE, + AWAITING_RESPONSE, + STREAMING_RESPONSE, + RESPONSE_FINISHED, +}; + +enum AudioMode : uint8_t { + AUDIO_MODE_UDP, + AUDIO_MODE_API, +}; class VoiceAssistant : public Component { public: void setup() override; void loop() override; float get_setup_priority() const override; - void start(struct sockaddr_storage *addr, uint16_t port); + void start_streaming(); + void start_streaming(struct sockaddr_storage *addr, uint16_t port); + void failed_to_start(); void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; } #ifdef USE_SPEAKER - void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } + void set_speaker(speaker::Speaker *speaker) { + this->speaker_ = speaker; + this->local_output_ = true; + } #endif #ifdef USE_MEDIA_PLAYER - void set_media_player(media_player::MediaPlayer *media_player) { this->media_player_ = media_player; } + void set_media_player(media_player::MediaPlayer *media_player) { + this->media_player_ = media_player; + this->local_output_ = true; + } +#endif + + uint32_t get_legacy_version() const { +#ifdef USE_SPEAKER + if (this->speaker_ != nullptr) { + return LEGACY_SPEAKER_SUPPORT; + } #endif + return LEGACY_INITIAL_VERSION; + } - uint32_t get_version() const { + uint32_t get_feature_flags() const { + uint32_t flags = 0; + flags |= VoiceAssistantFeature::FEATURE_VOICE_ASSISTANT; + flags |= VoiceAssistantFeature::FEATURE_API_AUDIO; #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { - return SPEAKER_SUPPORT; + flags |= VoiceAssistantFeature::FEATURE_SPEAKER; } #endif - return INITIAL_VERSION; + return flags; } - void request_start(bool continuous = false); - void signal_stop(); + void request_start(bool continuous, bool silence_detection); + void request_stop(); void on_event(const api::VoiceAssistantEventResponse &msg); + void on_audio(const api::VoiceAssistantAudio &msg); - bool is_running() const { return this->running_; } + bool is_running() const { return this->state_ != State::IDLE; } void set_continuous(bool continuous) { this->continuous_ = continuous; } bool is_continuous() const { return this->continuous_; } - void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; } + void set_use_wake_word(bool use_wake_word) { this->use_wake_word_ = use_wake_word; } +#ifdef USE_ESP_ADF + void set_vad_threshold(uint8_t vad_threshold) { this->vad_threshold_ = vad_threshold; } +#endif + + void set_noise_suppression_level(uint8_t noise_suppression_level) { + this->noise_suppression_level_ = noise_suppression_level; + } + void set_auto_gain(uint8_t auto_gain) { this->auto_gain_ = auto_gain; } + void set_volume_multiplier(float volume_multiplier) { this->volume_multiplier_ = volume_multiplier; } + Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; } + Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; } Trigger<> *get_listening_trigger() const { return this->listening_trigger_; } + Trigger<> *get_end_trigger() const { return this->end_trigger_; } Trigger<> *get_start_trigger() const { return this->start_trigger_; } + Trigger<> *get_stt_vad_end_trigger() const { return this->stt_vad_end_trigger_; } + Trigger<> *get_stt_vad_start_trigger() const { return this->stt_vad_start_trigger_; } +#ifdef USE_SPEAKER + Trigger<> *get_tts_stream_start_trigger() const { return this->tts_stream_start_trigger_; } + Trigger<> *get_tts_stream_end_trigger() const { return this->tts_stream_end_trigger_; } +#endif + Trigger<> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } Trigger *get_stt_end_trigger() const { return this->stt_end_trigger_; } - Trigger *get_tts_start_trigger() const { return this->tts_start_trigger_; } Trigger *get_tts_end_trigger() const { return this->tts_end_trigger_; } - Trigger<> *get_end_trigger() const { return this->end_trigger_; } + Trigger *get_tts_start_trigger() const { return this->tts_start_trigger_; } Trigger *get_error_trigger() const { return this->error_trigger_; } + Trigger<> *get_idle_trigger() const { return this->idle_trigger_; } + + Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; } + Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; } + + void client_subscription(api::APIConnection *client, bool subscribe); + api::APIConnection *get_api_connection() const { return this->api_client_; } + + void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } protected: + int read_microphone_(); + void set_state_(State state); + void set_state_(State state, State desired_state); + void signal_stop_(); + std::unique_ptr socket_ = nullptr; struct sockaddr_storage dest_addr_; + Trigger<> *intent_end_trigger_ = new Trigger<>(); + Trigger<> *intent_start_trigger_ = new Trigger<>(); Trigger<> *listening_trigger_ = new Trigger<>(); + Trigger<> *end_trigger_ = new Trigger<>(); Trigger<> *start_trigger_ = new Trigger<>(); + Trigger<> *stt_vad_start_trigger_ = new Trigger<>(); + Trigger<> *stt_vad_end_trigger_ = new Trigger<>(); +#ifdef USE_SPEAKER + Trigger<> *tts_stream_start_trigger_ = new Trigger<>(); + Trigger<> *tts_stream_end_trigger_ = new Trigger<>(); +#endif + Trigger<> *wake_word_detected_trigger_ = new Trigger<>(); Trigger *stt_end_trigger_ = new Trigger(); - Trigger *tts_start_trigger_ = new Trigger(); Trigger *tts_end_trigger_ = new Trigger(); - Trigger<> *end_trigger_ = new Trigger<>(); + Trigger *tts_start_trigger_ = new Trigger(); Trigger *error_trigger_ = new Trigger(); + Trigger<> *idle_trigger_ = new Trigger<>(); + + Trigger<> *client_connected_trigger_ = new Trigger<>(); + Trigger<> *client_disconnected_trigger_ = new Trigger<>(); + + api::APIConnection *api_client_{nullptr}; microphone::Microphone *mic_{nullptr}; #ifdef USE_SPEAKER + void write_speaker_(); speaker::Speaker *speaker_{nullptr}; + uint8_t *speaker_buffer_; + size_t speaker_buffer_index_{0}; + size_t speaker_buffer_size_{0}; + size_t speaker_bytes_received_{0}; + bool wait_for_stream_end_{false}; + bool stream_ended_{false}; #endif #ifdef USE_MEDIA_PLAYER media_player::MediaPlayer *media_player_{nullptr}; - bool playing_tts_{false}; #endif + bool local_output_{false}; + std::string conversation_id_{""}; - bool running_{false}; + std::string wake_word_{""}; + + HighFrequencyLoopRequester high_freq_; + +#ifdef USE_ESP_ADF + vad_handle_t vad_instance_; + uint8_t vad_threshold_{5}; + uint8_t vad_counter_{0}; +#endif + std::unique_ptr ring_buffer_; + + bool use_wake_word_; + uint8_t noise_suppression_level_; + uint8_t auto_gain_; + float volume_multiplier_; + + uint8_t *send_buffer_; + int16_t *input_buffer_; + bool continuous_{false}; bool silence_detection_; + + State state_{State::IDLE}; + State desired_state_{State::IDLE}; + + AudioMode audio_mode_{AUDIO_MODE_UDP}; + bool udp_socket_running_{false}; + bool start_udp_socket_(); }; template class StartAction : public Action, public Parented { + TEMPLATABLE_VALUE(std::string, wake_word); + public: - void play(Ts... x) override { this->parent_->request_start(); } + void play(Ts... x) override { + this->parent_->set_wake_word(this->wake_word_.value(x...)); + this->parent_->request_start(false, this->silence_detection_); + } + + void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; } + + protected: + bool silence_detection_; }; template class StartContinuousAction : public Action, public Parented { public: - void play(Ts... x) override { this->parent_->request_start(true); } + void play(Ts... x) override { this->parent_->request_start(true, true); } }; template class StopAction : public Action, public Parented { public: - void play(Ts... x) override { - this->parent_->set_continuous(false); - this->parent_->signal_stop(); - } + void play(Ts... x) override { this->parent_->request_stop(); } }; template class IsRunningCondition : public Condition, public Parented { @@ -122,6 +261,11 @@ template class IsRunningCondition : public Condition, pub bool check(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; } +}; + extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace voice_assistant diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index 893aa758951b..f414bf6c7158 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -30,11 +30,14 @@ void WakeOnLanButton::press_action() { ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); bool begin_status = false; bool end_status = false; - uint32_t interface = esphome::network::get_ip_address(); - IPAddress interface_ip = IPAddress(interface); IPAddress broadcast = IPAddress(255, 255, 255, 255); #ifdef USE_ESP8266 - begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, interface_ip, 128); + for (auto ip : esphome::network::get_ip_addresses()) { + if (ip.is_ip4()) { + begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128); + break; + } + } #endif #ifdef USE_ESP32 begin_status = this->udp_client_.beginPacket(broadcast, 9); diff --git a/esphome/components/waveshare_epaper/__init__.py b/esphome/components/waveshare_epaper/__init__.py index e69de29bb2d1..c58ce8a01e84 100644 --- a/esphome/components/waveshare_epaper/__init__.py +++ b/esphome/components/waveshare_epaper/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index eb0faadc02cc..bba60efc0aa6 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -17,8 +17,12 @@ DEPENDENCIES = ["spi"] waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper") -WaveshareEPaper = waveshare_epaper_ns.class_( - "WaveshareEPaper", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +WaveshareEPaperBase = waveshare_epaper_ns.class_( + "WaveshareEPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperBase) +WaveshareEPaperBWR = waveshare_epaper_ns.class_( + "WaveshareEPaperBWR", WaveshareEPaperBase ) WaveshareEPaperTypeA = waveshare_epaper_ns.class_( "WaveshareEPaperTypeA", WaveshareEPaper @@ -26,10 +30,28 @@ WaveshareEPaper2P7In = waveshare_epaper_ns.class_( "WaveshareEPaper2P7In", WaveshareEPaper ) +WaveshareEPaper2P7InB = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InB", WaveshareEPaperBWR +) +WaveshareEPaper2P7InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InBV2", WaveshareEPaperBWR +) +WaveshareEPaper2P7InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InV2", WaveshareEPaper +) WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( "WaveshareEPaper2P9InB", WaveshareEPaper ) +WaveshareEPaper2P9InBV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InBV3", WaveshareEPaper +) +WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InV2R2", WaveshareEPaper +) GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper) +WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InDKE", WaveshareEPaper +) WaveshareEPaper4P2In = waveshare_epaper_ns.class_( "WaveshareEPaper4P2In", WaveshareEPaper ) @@ -66,6 +88,12 @@ WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P13InDKE", WaveshareEPaper ) +WaveshareEPaper2P13InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV2", WaveshareEPaper +) +WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV3", WaveshareEPaper +) GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") @@ -75,6 +103,7 @@ "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), + "2.13inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN_V2), "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), "2.13in-ttgo-b73": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), @@ -83,7 +112,13 @@ "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), "gdey029t94": ("c", GDEY029T94), "2.70in": ("b", WaveshareEPaper2P7In), + "2.70in-b": ("b", WaveshareEPaper2P7InB), + "2.70in-bv2": ("b", WaveshareEPaper2P7InBV2), + "2.70inv2": ("b", WaveshareEPaper2P7InV2), "2.90in-b": ("b", WaveshareEPaper2P9InB), + "2.90in-bv3": ("b", WaveshareEPaper2P9InBV3), + "2.90inv2-r2": ("c", WaveshareEPaper2P9InV2R2), + "2.90in-dke": ("c", WaveshareEPaper2P9InDKE), "4.20in": ("b", WaveshareEPaper4P2In), "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), @@ -96,9 +131,12 @@ "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), + "2.13inv3": ("c", WaveshareEPaper2P13InV3), "1.54in-m5coreink-m09": ("c", GDEW0154M09), } +RESET_PIN_REQUIRED_MODELS = ("2.13inv2", "2.13in-ttgo-b74") + def validate_full_update_every_only_types_ac(value): if CONF_FULL_UPDATE_EVERY not in value: @@ -115,15 +153,23 @@ def validate_full_update_every_only_types_ac(value): return value +def validate_reset_pin_required(config): + if config[CONF_MODEL] in RESET_PIN_REQUIRED_MODELS and CONF_RESET_PIN not in config: + raise cv.Invalid( + f"'{CONF_RESET_PIN}' is required for model {config[CONF_MODEL]}" + ) + return config + + CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(WaveshareEPaper), + cv.GenerateID(): cv.declare_id(WaveshareEPaperBase), cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, + cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295), cv.Optional(CONF_RESET_DURATION): cv.All( cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=500)), @@ -133,6 +179,7 @@ def validate_full_update_every_only_types_ac(value): .extend(cv.polling_component_schema("1s")) .extend(spi.spi_device_schema()), validate_full_update_every_only_types_ac, + validate_reset_pin_required, cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) @@ -148,7 +195,6 @@ async def to_code(config): else: raise NotImplementedError() - await cg.register_component(var, config) await display.register_display(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp new file mode 100644 index 000000000000..196aeed3f792 --- /dev/null +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -0,0 +1,186 @@ +#include "waveshare_epaper.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace waveshare_epaper { + +static const char *const TAG = "waveshare_2.13v3"; + +static const uint8_t PARTIAL_LUT[] = { + 0x32, // cmd + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t FULL_LUT[] = { + 0x32, // CMD + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t SW_RESET = 0x12; +static const uint8_t ACTIVATE = 0x20; +static const uint8_t WRITE_BUFFER = 0x24; +static const uint8_t WRITE_BASE = 0x26; + +static const uint8_t DRV_OUT_CTL[] = {0x01, 0x27, 0x01, 0x00}; // driver output control +static const uint8_t GATEV[] = {0x03, 0x17}; +static const uint8_t SRCV[] = {0x04, 0x41, 0x0C, 0x32}; +static const uint8_t SLEEP[] = {0x10, 0x01}; +static const uint8_t DATA_ENTRY[] = {0x11, 0x03}; // data entry mode +static const uint8_t TEMP_SENS[] = {0x18, 0x80}; // Temp sensor +static const uint8_t DISPLAY_UPDATE[] = {0x21, 0x00, 0x80}; // Display update control +static const uint8_t UPSEQ[] = {0x22, 0xC0}; +static const uint8_t ON_FULL[] = {0x22, 0xC7}; +static const uint8_t ON_PARTIAL[] = {0x22, 0x0F}; +static const uint8_t VCOM[] = {0x2C, 0x36}; +static const uint8_t CMD5[] = {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t BORDER_PART[] = {0x3C, 0x80}; // border waveform +static const uint8_t BORDER_FULL[] = {0x3C, 0x05}; // border waveform +static const uint8_t CMD1[] = {0x3F, 0x22}; +static const uint8_t RAM_X_START[] = {0x44, 0x00, 121 / 8}; // set ram_x_address_start_end +static const uint8_t RAM_Y_START[] = {0x45, 0x00, 0x00, 250 - 1, 0}; // set ram_y_address_start_end +static const uint8_t RAM_X_POS[] = {0x4E, 0x00}; // set ram_x_address_counter +// static const uint8_t RAM_Y_POS[] = {0x4F, 0x00, 0x00}; // set ram_y_address_counter +#define SEND(x) this->cmd_data(x, sizeof(x)) + +void WaveshareEPaper2P13InV3::write_lut_(const uint8_t *lut) { + this->wait_until_idle_(); + this->cmd_data(lut, sizeof(PARTIAL_LUT)); + SEND(CMD1); + SEND(GATEV); + SEND(SRCV); + SEND(VCOM); +} + +// write the buffer starting on line top, up to line bottom. +void WaveshareEPaper2P13InV3::write_buffer_(uint8_t cmd, int top, int bottom) { + this->wait_until_idle_(); + this->set_window_(top, bottom); + this->command(cmd); + this->start_data_(); + auto width_bytes = this->get_width_internal() / 8; + this->write_array(this->buffer_ + top * width_bytes, (bottom - top) * width_bytes); + this->end_data_(); +} + +void WaveshareEPaper2P13InV3::send_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + } +} + +void WaveshareEPaper2P13InV3::setup() { + setup_pins_(); + delay(20); + this->send_reset_(); + // as a one-off delay this is not worth working around. + delay(100); // NOLINT + this->wait_until_idle_(); + this->command(SW_RESET); + this->wait_until_idle_(); + + SEND(DRV_OUT_CTL); + SEND(DATA_ENTRY); + SEND(CMD5); + this->set_window_(0, this->get_height_internal()); + SEND(BORDER_FULL); + SEND(DISPLAY_UPDATE); + SEND(TEMP_SENS); + this->wait_until_idle_(); + this->write_lut_(FULL_LUT); +} + +// t and b are y positions, i.e. line numbers. +void WaveshareEPaper2P13InV3::set_window_(int t, int b) { + uint8_t buffer[3]; + + SEND(RAM_X_START); + SEND(RAM_Y_START); + SEND(RAM_X_POS); + buffer[0] = 0x4F; + buffer[1] = (uint8_t) t; + buffer[2] = (uint8_t) (t >> 8); + SEND(buffer); +} + +// must implement, but we override setup to have more control +void WaveshareEPaper2P13InV3::initialize() {} + +void WaveshareEPaper2P13InV3::partial_update_() { + this->send_reset_(); + this->set_timeout(100, [this] { + this->write_lut_(PARTIAL_LUT); + SEND(BORDER_PART); + SEND(UPSEQ); + this->command(ACTIVATE); + this->set_timeout(100, [this] { + this->wait_until_idle_(); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + SEND(ON_PARTIAL); + this->command(ACTIVATE); // Activate Display Update Sequence + this->is_busy_ = false; + }); + }); +} + +void WaveshareEPaper2P13InV3::full_update_() { + ESP_LOGI(TAG, "Performing full e-paper update."); + this->write_lut_(FULL_LUT); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + this->write_buffer_(WRITE_BASE, 0, this->get_height_internal()); + SEND(ON_FULL); + this->command(ACTIVATE); // don't wait here + this->is_busy_ = false; +} + +void WaveshareEPaper2P13InV3::display() { + if (this->is_busy_ || (this->busy_pin_ != nullptr && this->busy_pin_->digital_read())) + return; + this->is_busy_ = true; + const bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + if (partial) { + this->partial_update_(); + } else { + this->full_update_(); + } +} + +int WaveshareEPaper2P13InV3::get_width_internal() { return 128; } + +int WaveshareEPaper2P13InV3::get_height_internal() { return 250; } + +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_UPDATE_INTERVAL(this) +} + +void WaveshareEPaper2P13InV3::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +} // namespace waveshare_epaper +} // namespace esphome diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 73c2680add88..7224aa44ed89 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -83,7 +83,34 @@ static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -void WaveshareEPaper::setup_pins_() { +// clang-format off +// Disable formatting to preserve the same look as in Waveshare examples +static const uint8_t PARTIAL_UPD_2IN9_LUT_SIZE = 159; +static const uint8_t PARTIAL_UPD_2IN9_LUT[PARTIAL_UPD_2IN9_LUT_SIZE] = +{ + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, + 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36, +}; +// clang-format on + +void WaveshareEPaperBase::setup_pins_() { this->init_internal_(this->get_buffer_length_()); this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); @@ -98,19 +125,31 @@ void WaveshareEPaper::setup_pins_() { this->reset_(); } -float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; } -void WaveshareEPaper::command(uint8_t value) { +float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } +void WaveshareEPaperBase::command(uint8_t value) { this->start_command_(); this->write_byte(value); this->end_command_(); } -void WaveshareEPaper::data(uint8_t value) { +void WaveshareEPaperBase::data(uint8_t value) { this->start_data_(); this->write_byte(value); this->end_data_(); } -bool WaveshareEPaper::wait_until_idle_() { - if (this->busy_pin_ == nullptr) { + +// write a command followed by one or more bytes of data. +// The command is the first byte, length is the total including cmd. +void WaveshareEPaperBase::cmd_data(const uint8_t *c_data, size_t length) { + this->dc_pin_->digital_write(false); + this->enable(); + this->write_byte(c_data[0]); + this->dc_pin_->digital_write(true); + this->write_array(c_data + 1, length - 1); + this->disable(); +} + +bool WaveshareEPaperBase::wait_until_idle_() { + if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) { return true; } @@ -120,11 +159,11 @@ bool WaveshareEPaper::wait_until_idle_() { ESP_LOGE(TAG, "Timeout while displaying image!"); return false; } - delay(10); + delay(1); } return true; } -void WaveshareEPaper::update() { +void WaveshareEPaperBase::update() { this->do_update_(); this->display(); } @@ -147,32 +186,84 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color this->buffer_[pos] &= ~(0x80 >> subpos); } } + uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_controller() * this->get_height_internal() / 8u; +} // just a black buffer +uint32_t WaveshareEPaperBWR::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 4u; +} // black and red buffer + +void WaveshareEPaperBWR::fill(Color color) { + this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); +} +void HOT WaveshareEPaperBWR::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) + return; + + const uint32_t buf_half_len = this->get_buffer_length_() / 2u; + + const uint32_t pos = (x + y * this->get_width_internal()) / 8u; + const uint8_t subpos = x & 0x07; + // flip logic + if (color.is_on()) { + this->buffer_[pos] |= 0x80 >> subpos; + } else { + this->buffer_[pos] &= ~(0x80 >> subpos); + } + + // draw red pixels only, if the color contains red only + if (((color.red > 0) && (color.green == 0) && (color.blue == 0))) { + this->buffer_[pos + buf_half_len] |= 0x80 >> subpos; + } else { + this->buffer_[pos + buf_half_len] &= ~(0x80 >> subpos); + } } -void WaveshareEPaper::start_command_() { + +void WaveshareEPaperBase::start_command_() { this->dc_pin_->digital_write(false); this->enable(); } -void WaveshareEPaper::end_command_() { this->disable(); } -void WaveshareEPaper::start_data_() { +void WaveshareEPaperBase::end_command_() { this->disable(); } +void WaveshareEPaperBase::start_data_() { this->dc_pin_->digital_write(true); this->enable(); } -void WaveshareEPaper::end_data_() { this->disable(); } -void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); } +void WaveshareEPaperBase::end_data_() { this->disable(); } +void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); } // ======================================================== // Type A // ======================================================== void WaveshareEPaperTypeA::initialize() { - if (this->model_ == TTGO_EPAPER_2_13_IN_B74) { - this->reset_pin_->digital_write(false); - delay(10); - this->reset_pin_->digital_write(true); - delay(10); - this->wait_until_idle_(); + // Achieve display intialization + this->init_display_(); + // If a reset pin is configured, eligible displays can be set to deep sleep + // between updates, as recommended by the hardware provider + if (this->reset_pin_ != nullptr) { + switch (this->model_) { + // More models can be added here to enable deep sleep if eligible + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + this->deep_sleep_between_updates_ = true; + ESP_LOGI(TAG, "Set the display to deep sleep"); + this->deep_sleep(); + break; + default: + break; + } + } +} +void WaveshareEPaperTypeA::init_display_() { + if (this->model_ == TTGO_EPAPER_2_13_IN_B74 || this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + this->wait_until_idle_(); + } this->command(0x12); // SWRESET this->wait_until_idle_(); @@ -232,6 +323,9 @@ void WaveshareEPaperTypeA::dump_config() { case WAVESHARE_EPAPER_2_13_IN: ESP_LOGCONFIG(TAG, " Model: 2.13in"); break; + case WAVESHARE_EPAPER_2_13_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 2.13inV2"); + break; case TTGO_EPAPER_2_13_IN: ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO)"); break; @@ -261,6 +355,13 @@ void HOT WaveshareEPaperTypeA::display() { bool full_update = this->at_update_ == 0; bool prev_full_update = this->at_update_ == 1; + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Wake up the display"); + this->reset_(); + this->wait_until_idle_(); + this->init_display_(); + } + if (!this->wait_until_idle_()) { this->status_set_warning(); return; @@ -270,6 +371,8 @@ void HOT WaveshareEPaperTypeA::display() { if (full_update != prev_full_update) { switch (this->model_) { case TTGO_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + // Waveshare 2.13" V2 uses the same LUTs as TTGO this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO : PARTIAL_UPDATE_LUT_TTGO, LUT_SIZE_TTGO); break; case TTGO_EPAPER_2_13_IN_B73: @@ -288,6 +391,41 @@ void HOT WaveshareEPaperTypeA::display() { this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; } + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + // Set VCOM for full or partial update + this->command(0x2C); + this->data(full_update ? 0x55 : 0x26); + + if (!full_update) { + // Enable "ping-pong" + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->command(0x22); + this->data(0xc0); + this->command(0x20); + } + } + + // Border waveform + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B74: + this->command(0x3C); + this->data(full_update ? 0x05 : 0x80); + break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->command(0x3C); + this->data(full_update ? 0x03 : 0x01); + break; + default: + break; + } + // Set x & y regions we want to write to (full) switch (this->model_) { case TTGO_EPAPER_2_13_IN_B1: @@ -311,12 +449,6 @@ void HOT WaveshareEPaperTypeA::display() { this->data((this->get_height_internal() - 1) >> 8); break; - case TTGO_EPAPER_2_13_IN_B74: - // BorderWaveform - this->command(0x3C); - this->data(full_update ? 0x05 : 0x80); - - // fall through default: // COMMAND SET RAM X ADDRESS START END POSITION this->command(0x44); @@ -362,6 +494,14 @@ void HOT WaveshareEPaperTypeA::display() { } this->end_data_(); + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2 && full_update) { + // Write base image again on full refresh + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + } + // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); switch (this->model_) { @@ -373,6 +513,9 @@ void HOT WaveshareEPaperTypeA::display() { case TTGO_EPAPER_2_13_IN_B73: this->data(0xC7); break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->data(full_update ? 0xC7 : 0x0C); + break; default: this->data(0xC4); break; @@ -384,6 +527,11 @@ void HOT WaveshareEPaperTypeA::display() { this->command(0xFF); this->status_clear_warning(); + + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Set the display back to deep sleep"); + this->deep_sleep(); + } } int WaveshareEPaperTypeA::get_width_internal() { switch (this->model_) { @@ -391,6 +539,7 @@ int WaveshareEPaperTypeA::get_width_internal() { case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -406,6 +555,7 @@ int WaveshareEPaperTypeA::get_width_internal() { int WaveshareEPaperTypeA::get_width_controller() { switch (this->model_) { case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -421,6 +571,7 @@ int WaveshareEPaperTypeA::get_height_internal() { case WAVESHARE_EPAPER_1_54_IN_V2: return 200; case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN: case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: @@ -445,10 +596,13 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { uint32_t WaveshareEPaperTypeA::idle_timeout_() { switch (this->model_) { + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: case TTGO_EPAPER_2_13_IN_B1: return 2500; default: - return WaveshareEPaper::idle_timeout_(); + return WaveshareEPaperBase::idle_timeout_(); } } @@ -590,65 +744,551 @@ void HOT WaveshareEPaper2P7In::display() { // COMMAND DISPLAY REFRESH this->command(0x12); } -int WaveshareEPaper2P7In::get_width_internal() { return 176; } -int WaveshareEPaper2P7In::get_height_internal() { return 264; } -void WaveshareEPaper2P7In::dump_config() { +int WaveshareEPaper2P7In::get_width_internal() { return 176; } +int WaveshareEPaper2P7In::get_height_internal() { return 264; } +void WaveshareEPaper2P7In::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in"); + 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); +} + +void WaveshareEPaper2P7InV2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + // SET WINDOWS + // XRAM_START_AND_END_POSITION + this->command(0x44); + this->data(0x00); + this->data(((get_width_internal() - 1) >> 3) & 0xFF); + // YRAM_START_AND_END_POSITION + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((get_height_internal() - 1) & 0xFF); + this->data(((get_height_internal() - 1) >> 8) & 0xFF); + + // SET CURSOR + // XRAM_ADDRESS + this->command(0x4E); + this->data(0x00); + // YRAM_ADDRESS + this->command(0x4F); + this->data(0x00); + this->data(0x00); + + this->command(0x11); // data entry mode + this->data(0x03); +} +void HOT WaveshareEPaper2P7InV2::display() { + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x22); + this->data(0xF7); + this->command(0x20); +} +int WaveshareEPaper2P7InV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in V2"); + 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); +} + +// ======================================================== +// 2.7inch_e-paper_b +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b.c + +static const uint8_t LUT_VCOM_DC_2_7B[44] = {0x00, 0x00, 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, + 0x00, 0x00, 0x08, 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x00, 0x0A, + 0x0A, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, + 0x03, 0x0E, 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_WHITE_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_WHITE_2_7B[42] = {0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x90, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, 0xB0, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_BLACK_2_7B[] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x20, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x10, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_BLACK_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +void WaveshareEPaper2P7InB::initialize() { + this->reset_(); + + // command power on + this->command(0x04); + this->wait_until_idle_(); + delay(10); + + // Command panel setting + this->command(0x00); + this->data(0xAF); // KW-BF KWR-AF BWROTP 0f + // command pll control + this->command(0x30); + this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + // command power setting + this->command(0x01); + this->data(0x03); // VDS_EN, VDG_EN + this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] + this->data(0x2B); // VDH + this->data(0x2B); // VDL + this->data(0x09); // VDHR + + // command booster soft start + this->command(0x06); + this->data(0x07); + this->data(0x07); + this->data(0x17); + + // Power optimization - ??? + this->command(0xF8); + this->data(0x60); + this->data(0xA5); + this->command(0xF8); + this->data(0x89); + this->data(0xA5); + this->command(0xF8); + this->data(0x90); + this->data(0x00); + this->command(0xF8); + this->data(0x93); + this->data(0x2A); + this->command(0xF8); + this->data(0x73); + this->data(0x41); + + // COMMAND VCM DC SETTING + this->command(0x82); + this->data(0x12); + + // VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + this->data(0x87); // define by OTP + + delay(2); + // COMMAND LUT FOR VCOM + this->command(0x20); + for (uint8_t i : LUT_VCOM_DC_2_7B) + this->data(i); + // COMMAND LUT WHITE TO WHITE + this->command(0x21); + for (uint8_t i : LUT_WHITE_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); + for (uint8_t i : LUT_BLACK_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); + for (uint8_t i : LUT_WHITE_TO_BLACK_2_7B) { + this->data(i); + } + // COMMAND LUT BLACK TO BLACK + this->command(0x24); + + for (uint8_t i : LUT_BLACK_TO_BLACK_2_7B) { + this->data(i); + } + + delay(2); +} + +void HOT WaveshareEPaper2P7InB::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // TCON_RESOLUTION + this->command(0x61); + this->data(this->get_width_internal() >> 8); + this->data(this->get_width_internal() & 0xff); // 176 + this->data(this->get_height_internal() >> 8); + this->data(this->get_height_internal() & 0xff); // 264 + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x10); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x13); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper2P7InB::get_width_internal() { return 176; } +int WaveshareEPaper2P7InB::get_height_internal() { return 264; } +void WaveshareEPaper2P7InB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B"); + 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); +} + +// ======================================================== +// 2.7inch_e-paper_b_v2 +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/7/7b/2.7inch-e-paper-b-v2-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b_V2.c + +void WaveshareEPaper2P7InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x00); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // self.SetWindows(0, 0, self.width-1, self.height-1) + // SetWindows(self, Xstart, Ystart, Xend, Yend): + + uint32_t xend = this->get_width_internal() - 1; + uint32_t yend = this->get_height_internal() - 1; + this->command(0x44); + this->data(0x00); + this->data((xend >> 3) & 0xff); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(yend & 0xff); + this->data((yend >> 8) & 0xff); + + // SetCursor(self, Xstart, Ystart): + this->command(0x4E); + this->data(0x00); + this->command(0x4F); + this->data(0x00); + this->data(0x00); +} + +void HOT WaveshareEPaper2P7InBV2::display() { + uint32_t buf_len = this->get_buffer_length_(); + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + delay(2); + + this->command(0x20); + + this->wait_until_idle_(); +} +int WaveshareEPaper2P7InBV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InBV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B V2"); + 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); +} + +// ======================================================== +// 2.90in Type B (LUT from OTP) +// Datasheet: +// - https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf +// - https://github.com/soonuse/epd-library-arduino/blob/master/2.9inch_e-paper_b/epd2in9b/epd2in9b.cpp +// ======================================================== + +void WaveshareEPaper2P9InB::initialize() { + // from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37 + // EPD hardware init start + this->reset_(); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); + this->data(0x17); + this->data(0x17); + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + // 128x296 resolution: 10 + // LUT from OTP: 0 + // B/W mode (doesn't work): 1 + // scan-up: 1 + // shift-right: 1 + // booster ON: 1 + // no soft reset: 1 + this->data(0x9F); + + // COMMAND RESOLUTION SETTING + // set to 128x296 by COMMAND PANEL SETTING + + // COMMAND VCOM AND DATA INTERVAL SETTING + // use defaults for white border and ESPHome image polarity + + // EPD hardware init end +} +void HOT WaveshareEPaper2P9InB::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(2); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper2P9InB::get_width_internal() { return 128; } +int WaveshareEPaper2P9InB::get_height_internal() { return 296; } +void WaveshareEPaper2P9InB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in (B)"); + 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); +} + +// DKE 2.9 +// https://www.badge.team/docs/badges/sha2017/hardware/#e-ink-display-the-dke-group-depg0290b1 +// https://www.badge.team/docs/badges/sha2017/hardware/DEPG0290B01V3.0.pdf +static const uint8_t LUT_SIZE_DKE = 70; +static const uint8_t UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0xA0, 0x90, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0xF, + 0xF, 0x0, 0x0, 0x0, 0xF, 0xF, 0x0, 0x0, 0x02, 0xF, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +}; +static const uint8_t PART_UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t FULL_UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0x90, 0x50, 0xa0, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x90, 0x50, 0xa0, 0x50, + 0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, + 0x04, 0x00, 0x00, 0x00, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x06, 0x05, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00, + 0x00, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +void WaveshareEPaper2P9InDKE::initialize() { + // Hardware reset + delay(10); + this->reset_pin_->digital_write(false); + delayMicroseconds(200); + this->reset_pin_->digital_write(true); + delayMicroseconds(200); + // Wait for busy low + this->wait_until_idle_(); + // Software reset + this->command(0x12); + // Wait for busy low + this->wait_until_idle_(); + // Set Analog Block Control + this->command(0x74); + this->data(0x54); + // Set Digital Block Control + this->command(0x7E); + this->data(0x3B); + // Set display size and driver output control + this->command(0x01); + // this->data(0x27); + // this->data(0x01); + // this->data(0x00); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + this->data(0x00); // ? GD = 0, SM = 0, TB = 0 + // Ram data entry mode + this->command(0x11); + this->data(0x03); + // Set Ram X address + this->command(0x44); + this->data(0x00); + this->data(0x0F); + // Set Ram Y address + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(0x27); + this->data(0x01); + // Set border + this->command(0x3C); + // this->data(0x80); + this->data(0x01); + // Set VCOM value + this->command(0x2C); + this->data(0x26); + // Gate voltage setting + this->command(0x03); + this->data(0x17); + // Source voltage setting + this->command(0x04); + this->data(0x41); + this->data(0x00); + this->data(0x32); + // Frame setting 50hz + this->command(0x3A); + this->data(0x30); + this->command(0x3B); + this->data(0x0A); + // Load LUT + this->command(0x32); + for (uint8_t v : FULL_UPDATE_LUT_DKE) + this->data(v); +} + +void HOT WaveshareEPaper2P9InDKE::display() { + ESP_LOGI(TAG, "Performing e-paper update."); + // Set Ram X address counter + this->command(0x4e); + this->data(0); + // Set Ram Y address counter + this->command(0x4f); + this->data(0); + this->data(0); + // Load image (128/8*296) + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + // Image update + this->command(0x22); + this->data(0xC7); + this->command(0x20); + // Wait for busy low + this->wait_until_idle_(); + // Enter deep sleep mode + this->command(0x10); + this->data(0x01); +} +int WaveshareEPaper2P9InDKE::get_width_internal() { return 128; } +int WaveshareEPaper2P9InDKE::get_height_internal() { return 296; } +void WaveshareEPaper2P9InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 2.7in"); + ESP_LOGCONFIG(TAG, " Model: 2.9in DKE"); 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); } +void WaveshareEPaper2P9InDKE::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} // ======================================================== // 2.90in Type B (LUT from OTP) // Datasheet: -// - https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf -// - https://github.com/soonuse/epd-library-arduino/blob/master/2.9inch_e-paper_b/epd2in9b/epd2in9b.cpp +// - https://files.waveshare.com/upload/a/af/2.9inch-e-paper-b-v3-specification.pdf // ======================================================== -void WaveshareEPaper2P9InB::initialize() { - // from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37 - // EPD hardware init start +void WaveshareEPaper2P9InBV3::initialize() { + // from https://github.com/waveshareteam/e-Paper/blob/master/Arduino/epd2in9b_V3/epd2in9b_V3.cpp this->reset_(); - // COMMAND BOOSTER SOFT START - this->command(0x06); - this->data(0x17); - this->data(0x17); - this->data(0x17); - // COMMAND POWER ON this->command(0x04); this->wait_until_idle_(); // COMMAND PANEL SETTING this->command(0x00); - // 128x296 resolution: 10 - // LUT from OTP: 0 - // B/W mode (doesn't work): 1 - // scan-up: 1 - // shift-right: 1 - // booster ON: 1 - // no soft reset: 1 - this->data(0x9F); + this->data(0x0F); + this->data(0x89); // COMMAND RESOLUTION SETTING - // set to 128x296 by COMMAND PANEL SETTING + this->command(0x61); + this->data(0x80); + this->data(0x01); + this->data(0x28); // COMMAND VCOM AND DATA INTERVAL SETTING - // use defaults for white border and ESPHome image polarity - - // EPD hardware init end + this->command(0x50); + this->data(0x77); } -void HOT WaveshareEPaper2P9InB::display() { +void HOT WaveshareEPaper2P9InBV3::display() { // COMMAND DATA START TRANSMISSION 1 (B/W data) this->command(0x10); delay(2); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); + this->command(0x92); delay(2); // COMMAND DATA START TRANSMISSION 2 (RED data) @@ -656,8 +1296,9 @@ void HOT WaveshareEPaper2P9InB::display() { delay(2); this->start_data_(); for (size_t i = 0; i < this->get_buffer_length_(); i++) - this->write_byte(0x00); + this->write_byte(0xFF); this->end_data_(); + this->command(0x92); delay(2); // COMMAND DISPLAY REFRESH @@ -669,17 +1310,203 @@ void HOT WaveshareEPaper2P9InB::display() { // NOTE: power off < deep sleep this->command(0x02); } -int WaveshareEPaper2P9InB::get_width_internal() { return 128; } -int WaveshareEPaper2P9InB::get_height_internal() { return 296; } -void WaveshareEPaper2P9InB::dump_config() { +int WaveshareEPaper2P9InBV3::get_width_internal() { return 128; } +int WaveshareEPaper2P9InBV3::get_height_internal() { return 296; } +void WaveshareEPaper2P9InBV3::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 2.9in (B)"); + ESP_LOGCONFIG(TAG, " Model: 2.9in (B) V3"); + 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); +} + +// ======================================================== +// 2.90in v2 rev2 +// based on SDK and examples in ZIP file from: +// https://www.waveshare.com/pico-epaper-2.9.htm +// ======================================================== + +void WaveshareEPaper2P9InV2R2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x01); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + this->command(0x21); + this->data(0x00); + this->data(0x80); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); +} + +WaveshareEPaper2P9InV2R2::WaveshareEPaper2P9InV2R2() { this->reset_duration_ = 10; } + +void WaveshareEPaper2P9InV2R2::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(reset_duration_); // NOLINT + } +} + +void WaveshareEPaper2P9InV2R2::display() { + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "fail idle 1"); + return; + } + + if (this->full_update_every_ == 1) { + // do single full update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + return; + } + + // if (this->full_update_every_ == 1 || + if (this->at_update_ == 0) { + // do base update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + } else { + // do partial update + this->reset_(); + + this->write_lut_(PARTIAL_UPD_2IN9_LUT, PARTIAL_UPD_2IN9_LUT_SIZE); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + + this->command(0x22); + this->data(0xC0); + this->command(0x20); + + if (!this->wait_until_idle_()) { + ESP_LOGE(TAG, "fail idle 2"); + } + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + // write b/w + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplayPartial + this->command(0x22); + this->data(0x0F); + this->command(0x20); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; +} + +void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size) { + // COMMAND WRITE LUT REGISTER + this->command(0x32); + for (uint8_t i = 0; i < size; i++) + this->data(lut[i]); +} + +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_); 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); } +void WaveshareEPaper2P9InV2R2::deep_sleep() { + this->command(0x10); + this->data(0x01); +} + +int WaveshareEPaper2P9InV2R2::get_width_internal() { return 128; } +int WaveshareEPaper2P9InV2R2::get_height_internal() { return 296; } +int WaveshareEPaper2P9InV2R2::get_width_controller() { return this->get_width_internal(); } +void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + // ======================================================== // Good Display 2.9in black/white/grey // Datasheet: @@ -1288,6 +2115,12 @@ void WaveshareEPaper7P5InBV2::initialize() { // COMMAND TCON SETTING this->command(0x60); this->data(0x22); + + this->command(0x82); + this->data(0x08); + this->command(0x30); + this->data(0x06); + // COMMAND RESOLUTION SETTING this->command(0x65); this->data(0x00); @@ -1317,6 +2150,7 @@ void HOT WaveshareEPaper7P5InBV2::display() { this->command(0x12); delay(100); // NOLINT this->wait_until_idle_(); + this->deep_sleep(); } int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; } int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; } @@ -1329,6 +2163,7 @@ void WaveshareEPaper7P5InBV2::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InBV3::initialize() { this->init_display_(); } bool WaveshareEPaper7P5InBV3::wait_until_idle_() { if (this->busy_pin_ == nullptr) { return true; @@ -1341,12 +2176,13 @@ bool WaveshareEPaper7P5InBV3::wait_until_idle_() { ESP_LOGI(TAG, "Timeout while displaying image!"); return false; } + App.feed_wdt(); delay(10); } delay(200); // NOLINT return true; }; -void WaveshareEPaper7P5InBV3::initialize() { +void WaveshareEPaper7P5InBV3::init_display_() { this->reset_(); // COMMAND POWER SETTING @@ -1402,8 +2238,6 @@ void WaveshareEPaper7P5InBV3::initialize() { this->data(0x00); this->data(0x00); - this->wait_until_idle_(); - uint8_t lut_vcom_7_i_n5_v2[] = { 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, @@ -1449,24 +2283,26 @@ void WaveshareEPaper7P5InBV3::initialize() { this->command(0x24); // LUTBB for (count = 0; count < 42; count++) this->data(lut_bb_7_i_n5_v2[count]); +}; +void HOT WaveshareEPaper7P5InBV3::display() { + this->init_display_(); + uint32_t buf_len = this->get_buffer_length_(); this->command(0x10); - for (uint32_t i = 0; i < 800 * 480 / 8; i++) { + for (uint32_t i = 0; i < buf_len; i++) { this->data(0xFF); } -}; -void HOT WaveshareEPaper7P5InBV3::display() { - uint32_t buf_len = this->get_buffer_length_(); this->command(0x13); // Start Transmission delay(2); for (uint32_t i = 0; i < buf_len; i++) { - this->data(~(this->buffer_[i])); + this->data(~this->buffer_[i]); } this->command(0x12); // Display Refresh delay(100); // NOLINT this->wait_until_idle_(); + this->deep_sleep(); } int WaveshareEPaper7P5InBV3::get_width_internal() { return 800; } int WaveshareEPaper7P5InBV3::get_height_internal() { return 480; } @@ -1561,6 +2397,23 @@ void WaveshareEPaper7P5In::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } +bool WaveshareEPaper7P5InV2::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + return true; +} void WaveshareEPaper7P5InV2::initialize() { // COMMAND POWER SETTING this->command(0x01); @@ -1568,10 +2421,21 @@ void WaveshareEPaper7P5InV2::initialize() { this->data(0x07); this->data(0x3f); this->data(0x3f); - this->command(0x04); + + // We don't want the display to be powered at this point delay(100); // NOLINT this->wait_until_idle_(); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x07); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // COMMAND PANEL SETTING this->command(0x00); this->data(0x1F); @@ -1582,19 +2446,30 @@ void WaveshareEPaper7P5InV2::initialize() { this->data(0x20); this->data(0x01); this->data(0xE0); - // COMMAND ...? + + // COMMAND DUAL SPI MM_EN, DUSPI_EN this->command(0x15); this->data(0x00); - // COMMAND VCOM AND DATA INTERVAL SETTING - this->command(0x50); - this->data(0x10); - this->data(0x07); - // COMMAND TCON SETTING - this->command(0x60); - this->data(0x22); + + // COMMAND POWER DRIVER HAT DOWN + // This command will turn off booster, controller, source driver, gate driver, VCOM, and + // temperature sensor, but register data will be kept until VDD turned OFF or Deep Sleep Mode. + // Source/Gate/Border/VCOM will be released to floating. + this->command(0x02); } void HOT WaveshareEPaper7P5InV2::display() { uint32_t buf_len = this->get_buffer_length_(); + + // COMMAND POWER ON + ESP_LOGI(TAG, "Power on the display and hat"); + + // This command will turn on booster, controller, regulators, and temperature sensor will be + // activated for one-time sensing before enabling booster. When all voltages are ready, the + // BUSY_N signal will return to high. + this->command(0x04); + delay(200); // NOLINT + this->wait_until_idle_(); + // COMMAND DATA START TRANSMISSION NEW DATA this->command(0x13); delay(2); @@ -1602,14 +2477,23 @@ void HOT WaveshareEPaper7P5InV2::display() { this->data(~(this->buffer_[i])); } + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND DISPLAY REFRESH this->command(0x12); delay(100); // NOLINT this->wait_until_idle_(); + + ESP_LOGV(TAG, "Before command(0x02) (>> power off)"); + this->command(0x02); + this->wait_until_idle_(); + ESP_LOGV(TAG, "After command(0x02) (>> power off)"); } int WaveshareEPaper7P5InV2::get_width_internal() { return 800; } int WaveshareEPaper7P5InV2::get_height_internal() { return 480; } +uint32_t WaveshareEPaper7P5InV2::idle_timeout_() { return 10000; } void WaveshareEPaper7P5InV2::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 7.5inV2rev2"); @@ -2006,8 +2890,9 @@ void HOT WaveshareEPaper2P13InDKE::display() { } else { // set up partial update this->command(0x32); - for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE) - this->data(v); + this->start_data_(); + this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE)); + this->end_data_(); this->command(0x3F); this->data(0x22); @@ -2052,12 +2937,10 @@ void HOT WaveshareEPaper2P13InDKE::display() { this->wait_until_idle_(); // data must be sent again on partial update - delay(300); // NOLINT this->command(0x24); this->start_data_(); this->write_array(this->buffer_, this->get_buffer_length_()); this->end_data_(); - delay(300); // NOLINT } ESP_LOGI(TAG, "Completed e-paper update."); @@ -2069,6 +2952,7 @@ uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InDKE::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); + 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_); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 315af9ea82f2..3c4470c30cf2 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -7,10 +7,9 @@ namespace esphome { namespace waveshare_epaper { -class WaveshareEPaper : public PollingComponent, - public display::DisplayBuffer, - public spi::SPIDevice { +class WaveshareEPaperBase : public display::DisplayBuffer, + public spi::SPIDevice { public: void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; @@ -20,6 +19,7 @@ class WaveshareEPaper : public PollingComponent, void command(uint8_t value); void data(uint8_t value); + void cmd_data(const uint8_t *data, size_t length); virtual void display() = 0; virtual void initialize() = 0; @@ -27,8 +27,6 @@ class WaveshareEPaper : public PollingComponent, void update() override; - void fill(Color color) override; - void setup() override { this->setup_pins_(); this->initialize(); @@ -36,11 +34,7 @@ class WaveshareEPaper : public PollingComponent, void on_safe_shutdown() override; - display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } - protected: - void draw_absolute_pixel_internal(int x, int y, Color color) override; - bool wait_until_idle_(); void setup_pins_(); @@ -50,13 +44,13 @@ class WaveshareEPaper : public PollingComponent, this->reset_pin_->digital_write(false); delay(reset_duration_); // NOLINT this->reset_pin_->digital_write(true); - delay(200); // NOLINT + delay(20); } } virtual int get_width_controller() { return this->get_width_internal(); }; - uint32_t get_buffer_length_(); + virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming) uint32_t reset_duration_{200}; void start_command_(); @@ -70,10 +64,33 @@ class WaveshareEPaper : public PollingComponent, virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; +class WaveshareEPaper : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + +class WaveshareEPaperBWR : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + enum WaveshareEPaperTypeAModel { WAVESHARE_EPAPER_1_54_IN = 0, WAVESHARE_EPAPER_1_54_IN_V2, WAVESHARE_EPAPER_2_13_IN, + WAVESHARE_EPAPER_2_13_IN_V2, WAVESHARE_EPAPER_2_9_IN, WAVESHARE_EPAPER_2_9_IN_V2, TTGO_EPAPER_2_13_IN, @@ -93,15 +110,27 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { void display() override; void deep_sleep() override { - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { - // COMMAND DEEP SLEEP MODE - this->command(0x10); - this->data(0x01); - } else { - // COMMAND DEEP SLEEP MODE - this->command(0x10); + switch (this->model_) { + // Models with specific deep sleep command and data + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: + // COMMAND DEEP SLEEP MODE + this->command(0x10); + this->data(0x01); + break; + // Other models default to simple deep sleep command + default: + // COMMAND DEEP SLEEP + this->command(0x10); + break; + } + if (this->model_ != WAVESHARE_EPAPER_2_13_IN_V2) { + // From panel specification: + // "After this command initiated, the chip will enter Deep Sleep Mode, BUSY pad will keep output high." + this->wait_until_idle_(); } - this->wait_until_idle_(); } void set_full_update_every(uint32_t full_update_every); @@ -109,6 +138,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { protected: void write_lut_(const uint8_t *lut, uint8_t size); + void init_display_(); + int get_width_internal() override; int get_height_internal() override; @@ -119,10 +150,14 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; uint32_t idle_timeout_() override; + + bool deep_sleep_between_updates_{false}; }; enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_2_7_IN = 0, + WAVESHARE_EPAPER_2_7_IN_B, + WAVESHARE_EPAPER_2_7_IN_B_V2, WAVESHARE_EPAPER_4_2_IN, WAVESHARE_EPAPER_4_2_IN_B_V2, WAVESHARE_EPAPER_7_5_IN, @@ -146,7 +181,49 @@ class WaveshareEPaper2P7In : public WaveshareEPaper { protected: int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InB : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; int get_height_internal() override; }; @@ -170,6 +247,22 @@ class GDEY029T94 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P7InV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { ; } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class GDEW0154M09 : public WaveshareEPaper { public: void initialize() override; @@ -230,6 +323,80 @@ class WaveshareEPaper2P9InB : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P9InBV3 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P9InV2R2 : public WaveshareEPaper { + public: + WaveshareEPaper2P9InV2R2(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override; + + void set_full_update_every(uint32_t full_update_every); + + protected: + void write_lut_(const uint8_t *lut, uint8_t size); + + int get_width_internal() override; + + int get_height_internal() override; + + int get_width_controller() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + + private: + void reset_(); +}; + +class WaveshareEPaper2P9InDKE : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper4P2In : public WaveshareEPaper { public: void initialize() override; @@ -430,6 +597,8 @@ class WaveshareEPaper7P5InBV3 : public WaveshareEPaper { this->data(0xA5); } + void clear_screen(); + protected: int get_width_internal() override; @@ -445,6 +614,8 @@ class WaveshareEPaper7P5InBV3 : public WaveshareEPaper { delay(200); // NOLINT } }; + + void init_display_(); }; class WaveshareEPaper7P5InBC : public WaveshareEPaper { @@ -472,6 +643,8 @@ class WaveshareEPaper7P5InBC : public WaveshareEPaper { class WaveshareEPaper7P5InV2 : public WaveshareEPaper { public: + bool wait_until_idle_(); + void initialize() override; void display() override; @@ -491,6 +664,8 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_width_internal() override; int get_height_internal() override; + + uint32_t idle_timeout_() override; }; class WaveshareEPaper7P5InV2alt : public WaveshareEPaper7P5InV2 { @@ -560,5 +735,39 @@ class WaveshareEPaper2P13InDKE : public WaveshareEPaper { uint32_t at_update_{0}; }; +class WaveshareEPaper2P13InV3 : public WaveshareEPaper { + public: + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + void setup() override; + void initialize() override; + + protected: + int get_width_internal() override; + int get_height_internal() override; + uint32_t idle_timeout_() override; + + void write_buffer_(uint8_t cmd, int top, int bottom); + void set_window_(int t, int b); + void send_reset_(); + void partial_update_(); + void full_update_(); + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + bool is_busy_{false}; + void write_lut_(const uint8_t *lut); +}; } // namespace waveshare_epaper } // namespace esphome diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index b3e911035379..fa614fb5a601 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,3 +1,4 @@ +import gzip import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import web_server_base @@ -8,6 +9,7 @@ CONF_ID, CONF_JS_INCLUDE, CONF_JS_URL, + CONF_ENABLE_PRIVATE_NETWORK_ACCESS, CONF_PORT, CONF_AUTH, CONF_USERNAME, @@ -17,6 +19,10 @@ CONF_LOG, CONF_VERSION, CONF_LOCAL, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, ) from esphome.core import CORE, coroutine_with_priority @@ -29,15 +35,20 @@ def default_url(config): config = config.copy() if config[CONF_VERSION] == 1: - if not (CONF_CSS_URL in config): + if CONF_CSS_URL not in config: config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css" - if not (CONF_JS_URL in config): + if CONF_JS_URL not in config: config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js" if config[CONF_VERSION] == 2: - if not (CONF_CSS_URL in config): + if CONF_CSS_URL not in config: config[CONF_CSS_URL] = "" - if not (CONF_JS_URL in config): + if CONF_JS_URL not in config: config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js" + if config[CONF_VERSION] == 3: + if CONF_CSS_URL not in config: + config[CONF_CSS_URL] = "" + if CONF_JS_URL not in config: + config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js" return config @@ -58,11 +69,12 @@ def validate_ota(config): { cv.GenerateID(): cv.declare_id(WebServer), cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2), + cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, 3, int=True), cv.Optional(CONF_CSS_URL): cv.string, cv.Optional(CONF_CSS_INCLUDE): cv.file_, cv.Optional(CONF_JS_URL): cv.string, cv.Optional(CONF_JS_INCLUDE): cv.file_, + cv.Optional(CONF_ENABLE_PRIVATE_NETWORK_ACCESS, default=True): cv.boolean, cv.Optional(CONF_AUTH): cv.Schema( { cv.Required(CONF_USERNAME): cv.All( @@ -89,7 +101,7 @@ def validate_ota(config): cv.Optional(CONF_LOCAL): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), default_url, validate_local, validate_ota, @@ -114,9 +126,13 @@ def build_index_html(config) -> str: return html -def add_resource_as_progmem(resource_name: str, content: str) -> None: +def add_resource_as_progmem( + resource_name: str, content: str, compress: bool = True +) -> None: """Add a resource to progmem.""" content_encoded = content.encode("utf-8") + if compress: + content_encoded = gzip.compress(content_encoded) content_encoded_size = len(content_encoded) bytes_as_int = ", ".join(str(x) for x in content_encoded) uint8_t = f"const uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" @@ -141,13 +157,16 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) cg.add_define("USE_WEBSERVER_VERSION", version) - if version == 2: - add_resource_as_progmem("INDEX_HTML", build_index_html(config)) + if version >= 2: + # Don't compress the index HTML as the data sizes are almost the same. + add_resource_as_progmem("INDEX_HTML", build_index_html(config), compress=False) else: cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) cg.add(var.set_expose_log(config[CONF_LOG])) + if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: + cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 016dd37dd918..42af72e8722a 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -12,6 +12,8 @@ ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_( #ifdef USE_BINARY_SENSOR bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); return true; @@ -19,30 +21,40 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ #endif #ifdef USE_COVER bool ListEntitiesIterator::on_cover(cover::Cover *cover) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_FAN bool ListEntitiesIterator::on_fan(fan::Fan *fan) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_LIGHT bool ListEntitiesIterator::on_light(light::LightState *light) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_SENSOR bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), "state"); return true; @@ -50,12 +62,16 @@ bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { #endif #ifdef USE_BUTTON bool ListEntitiesIterator::on_button(button::Button *button) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); return true; } #endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); return true; @@ -63,13 +79,26 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) #endif #ifdef USE_LOCK bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); return true; } #endif +#ifdef USE_VALVE +bool ListEntitiesIterator::on_valve(valve::Valve *valve) { + if (this->web_server_->events_.count() == 0) + return true; + this->web_server_->events_.send(this->web_server_->valve_json(valve, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + #ifdef USE_CLIMATE bool ListEntitiesIterator::on_climate(climate::Climate *climate) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); return true; } @@ -77,13 +106,51 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { #ifdef USE_NUMBER bool ListEntitiesIterator::on_number(number::Number *number) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); return true; } #endif +#ifdef USE_DATETIME_DATE +bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { + if (this->web_server_->events_.count() == 0) + return true; + this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_DATETIME_TIME +bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { + this->web_server_->events_.send(this->web_server_->time_json(time, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_DATETIME_DATETIME +bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { + if (this->web_server_->events_.count() == 0) + return true; + this->web_server_->events_.send(this->web_server_->datetime_json(datetime, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_TEXT +bool ListEntitiesIterator::on_text(text::Text *text) { + if (this->web_server_->events_.count() == 0) + return true; + this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + #ifdef USE_SELECT bool ListEntitiesIterator::on_select(select::Select *select) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); return true; } @@ -91,6 +158,8 @@ bool ListEntitiesIterator::on_select(select::Select *select) { #ifdef USE_ALARM_CONTROL_PANEL bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { + if (this->web_server_->events_.count() == 0) + return true; this->web_server_->events_.send( this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL) .c_str(), @@ -99,5 +168,14 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont } #endif +#ifdef USE_EVENT +bool ListEntitiesIterator::on_event(event::Event *event) { + // Null event type, since we are just iterating over entities + const std::string null_event_type = ""; + this->web_server_->events_.send(this->web_server_->event_json(event, null_event_type, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + } // namespace web_server } // namespace esphome diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 1569c8ac5718..47d427d9b58a 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -41,15 +41,33 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_NUMBER bool on_number(number::Number *number) override; #endif +#ifdef USE_DATETIME_DATE + bool on_date(datetime::DateEntity *date) override; +#endif +#ifdef USE_DATETIME_TIME + bool on_time(datetime::TimeEntity *time) override; +#endif +#ifdef USE_DATETIME_DATETIME + bool on_datetime(datetime::DateTimeEntity *datetime) override; +#endif +#ifdef USE_TEXT + bool on_text(text::Text *text) override; +#endif #ifdef USE_SELECT bool on_select(select::Select *select) override; #endif #ifdef USE_LOCK bool on_lock(lock::Lock *a_lock) override; #endif +#ifdef USE_VALVE + bool on_valve(valve::Valve *valve) override; +#endif #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; #endif +#ifdef USE_EVENT + bool on_event(event::Event *event) override; +#endif protected: WebServer *web_server_; diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h deleted file mode 100644 index 180dffab6720..000000000000 --- a/esphome/components/web_server/server_index.h +++ /dev/null @@ -1,605 +0,0 @@ -#pragma once -// Generated from https://github.com/esphome/esphome-webserver -#include "esphome/core/hal.h" -namespace esphome { - -namespace web_server { - -const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc5, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, - 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0x4a, 0xad, 0x02, 0xae, 0x40, 0x88, 0xa4, 0x6a, 0x33, 0x28, 0x90, 0x57, 0xb5, - 0xd8, 0x55, 0x76, 0x6d, 0x2e, 0xa9, 0xec, 0x6b, 0xcb, 0xb4, 0x04, 0x91, 0x49, 0x11, 0x2e, 0x10, 0xa0, 0x81, 0xa4, - 0x16, 0x53, 0xe8, 0x33, 0x4f, 0xf3, 0xd4, 0xe7, 0xcc, 0xd6, 0x0f, 0xfd, 0x30, 0x7d, 0xba, 0x1f, 0xe6, 0x23, 0xe6, - 0xb9, 0x3f, 0xe5, 0xfe, 0xc0, 0xf4, 0x27, 0x4c, 0x44, 0xe4, 0x82, 0x04, 0x17, 0x49, 0x5e, 0xba, 0xe7, 0xd8, 0x2a, - 0x12, 0xb9, 0x46, 0x44, 0x46, 0xc6, 0x96, 0x91, 0xe0, 0xde, 0xc6, 0x30, 0x1b, 0xf0, 0xab, 0x29, 0xb3, 0xc6, 0x7c, - 0x92, 0x74, 0xf7, 0xe4, 0xbf, 0x2c, 0x1a, 0x76, 0xf7, 0x92, 0x38, 0xfd, 0x64, 0xe5, 0x2c, 0x09, 0xe3, 0x41, 0x96, - 0x5a, 0xe3, 0x9c, 0x8d, 0xc2, 0x61, 0xc4, 0xa3, 0x20, 0x9e, 0x44, 0x67, 0xcc, 0xda, 0xe9, 0xee, 0x4d, 0x18, 0x8f, - 0xac, 0xc1, 0x38, 0xca, 0x0b, 0xc6, 0xc3, 0x8f, 0x87, 0x9f, 0x37, 0x9e, 0x74, 0xf7, 0x8a, 0x41, 0x1e, 0x4f, 0xb9, - 0x85, 0x43, 0x86, 0x93, 0x6c, 0x38, 0x4b, 0x58, 0xf7, 0x3c, 0xca, 0xad, 0x17, 0x3c, 0x7c, 0x77, 0xfa, 0x13, 0x1b, - 0x70, 0x7f, 0xc8, 0x46, 0x71, 0xca, 0xde, 0xe7, 0xd9, 0x94, 0xe5, 0xfc, 0xca, 0x3b, 0x58, 0x5d, 0x11, 0xb3, 0xc2, - 0x7b, 0xa6, 0xab, 0xce, 0x18, 0x7f, 0x77, 0x91, 0xaa, 0x3e, 0xcf, 0x99, 0x98, 0x24, 0xcb, 0x0b, 0xaf, 0x58, 0xd3, - 0xe6, 0xe0, 0x6a, 0x72, 0x9a, 0x25, 0x85, 0xf7, 0x49, 0xd7, 0x4f, 0xf3, 0x8c, 0x67, 0x08, 0x96, 0x3f, 0x8e, 0x0a, - 0xa3, 0xa5, 0xf7, 0x6e, 0x45, 0x93, 0xa9, 0xac, 0x7c, 0x55, 0xbc, 0x48, 0x67, 0x13, 0x96, 0x47, 0xa7, 0x09, 0xf3, - 0x72, 0x1e, 0x3a, 0xdc, 0x63, 0x5e, 0xec, 0x86, 0x5d, 0x66, 0xc5, 0xa9, 0xc5, 0x7b, 0x2f, 0x38, 0x95, 0xcc, 0x99, - 0x6e, 0x15, 0x6c, 0x34, 0x3d, 0x20, 0xd7, 0x28, 0x3e, 0x9b, 0xe9, 0xe7, 0x8b, 0x3c, 0xe6, 0xea, 0xfb, 0x79, 0x94, - 0xcc, 0x58, 0x10, 0x97, 0x6e, 0xc0, 0x8f, 0x58, 0x3f, 0x8c, 0xbd, 0x4f, 0x34, 0x28, 0x0c, 0x39, 0x1f, 0x65, 0xb9, - 0x83, 0xb4, 0x8a, 0x71, 0x6c, 0x76, 0x7d, 0xed, 0xb0, 0x70, 0x5e, 0xba, 0xee, 0x27, 0xee, 0x0f, 0xa2, 0x24, 0x71, - 0x70, 0xe2, 0xad, 0xad, 0x1c, 0x67, 0x8c, 0x3d, 0x76, 0x14, 0xf7, 0xdd, 0x4e, 0x3c, 0x72, 0x0a, 0xee, 0x56, 0xfd, - 0xb2, 0x91, 0x55, 0x70, 0x87, 0xb9, 0xee, 0xbb, 0xf5, 0x7d, 0x72, 0xc6, 0x67, 0x39, 0xc0, 0x5e, 0x7a, 0xef, 0xd4, - 0xcc, 0x07, 0x58, 0xff, 0x8c, 0x3a, 0x76, 0x00, 0xf6, 0x82, 0x5b, 0x9f, 0x87, 0x17, 0x71, 0x3a, 0xcc, 0x2e, 0xfc, - 0x83, 0x71, 0x04, 0x1f, 0x1f, 0xb2, 0x8c, 0x6f, 0x6d, 0x39, 0xe7, 0x59, 0x3c, 0xb4, 0x9a, 0x61, 0x68, 0x56, 0x5e, - 0x3d, 0x3b, 0x38, 0xb8, 0xbe, 0x5e, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0xe7, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x9c, - 0x72, 0x36, 0x3c, 0xe0, 0x57, 0x09, 0x94, 0x32, 0xc6, 0x0b, 0x1b, 0x70, 0x7c, 0x9e, 0x0d, 0x80, 0x6c, 0xa9, 0x41, - 0x78, 0x68, 0x9a, 0xb3, 0x69, 0x12, 0x0d, 0x18, 0xd6, 0xc3, 0x48, 0x55, 0x8f, 0xaa, 0x91, 0xf7, 0x6d, 0x28, 0x96, - 0xd7, 0x71, 0xbd, 0x94, 0x87, 0x29, 0xbb, 0xb0, 0xde, 0x44, 0xd3, 0xce, 0x20, 0x89, 0x8a, 0xc2, 0xca, 0xf8, 0x9c, - 0x50, 0xc8, 0x67, 0x03, 0x60, 0x10, 0x42, 0x70, 0x0e, 0x64, 0xe2, 0xe3, 0xb8, 0xf0, 0x8f, 0x37, 0x07, 0x45, 0xf1, - 0x81, 0x15, 0xb3, 0x84, 0x6f, 0x86, 0xb0, 0x16, 0x6c, 0x23, 0x0c, 0xbf, 0x75, 0xf9, 0x38, 0xcf, 0x2e, 0xac, 0x17, - 0x79, 0x0e, 0xcd, 0x6d, 0x98, 0x52, 0x34, 0xb0, 0xe2, 0xc2, 0x4a, 0x33, 0x6e, 0xe9, 0xc1, 0x70, 0x01, 0x7d, 0xeb, - 0x63, 0xc1, 0xac, 0x93, 0x59, 0x5a, 0x44, 0x23, 0x06, 0x4d, 0x4f, 0xac, 0x2c, 0xb7, 0x4e, 0x60, 0xd0, 0x13, 0x58, - 0xb2, 0x82, 0xc3, 0xae, 0xf1, 0x6d, 0xb7, 0x43, 0x73, 0x41, 0xe1, 0x21, 0xbb, 0xe4, 0x21, 0x2f, 0x81, 0x31, 0x61, - 0x55, 0x14, 0x1a, 0x8e, 0x3b, 0x4f, 0xa0, 0x00, 0xc0, 0x26, 0x96, 0x75, 0xcc, 0xc6, 0x7a, 0x71, 0x3e, 0xdf, 0xda, - 0xd2, 0xb4, 0x46, 0xc2, 0x43, 0xdb, 0x62, 0xa1, 0xad, 0x27, 0x10, 0xaf, 0x91, 0xc8, 0xf5, 0xb8, 0x2f, 0xc9, 0x77, - 0x70, 0x95, 0x0e, 0xea, 0x63, 0x43, 0x65, 0xc9, 0xb3, 0x03, 0x9e, 0xc7, 0xe9, 0x19, 0x00, 0xa1, 0xd8, 0xc0, 0x68, - 0x52, 0x96, 0x62, 0xf1, 0xdf, 0x03, 0xd4, 0x61, 0x17, 0x47, 0xcf, 0xb8, 0x63, 0x17, 0xd4, 0xc3, 0x06, 0x40, 0x80, - 0xf4, 0xc0, 0x60, 0xbc, 0xc7, 0x03, 0xbe, 0x6d, 0xdb, 0xde, 0xb7, 0xae, 0x77, 0x81, 0x1c, 0xe4, 0xfb, 0x3e, 0xb1, - 0xaf, 0xe8, 0x1c, 0x87, 0x2d, 0x04, 0xda, 0x4f, 0x58, 0x7a, 0xc6, 0xc7, 0x3d, 0x7e, 0xd4, 0xec, 0x07, 0x0c, 0xa0, - 0x1a, 0xce, 0x06, 0xcc, 0x41, 0x7e, 0xf4, 0x0a, 0xdc, 0x3e, 0xdb, 0x0e, 0x4c, 0x81, 0x0b, 0xb3, 0x41, 0x38, 0xd6, - 0x96, 0xc6, 0x55, 0xb0, 0x29, 0xc0, 0x90, 0xcf, 0x6d, 0xd8, 0x61, 0xa7, 0x2c, 0x37, 0xe0, 0xd0, 0xcd, 0x3a, 0xb5, - 0x15, 0x9c, 0xc1, 0x0a, 0x41, 0x3f, 0x6b, 0x34, 0x4b, 0x07, 0x3c, 0x06, 0xc1, 0x65, 0x6f, 0x03, 0xb8, 0x62, 0xe5, - 0xf4, 0xc2, 0xd9, 0x6e, 0xe9, 0x3a, 0xb1, 0xbb, 0xcd, 0x8f, 0x8a, 0xed, 0x56, 0xdf, 0x43, 0x28, 0x35, 0xf1, 0x25, - 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0xb9, 0xde, 0x9e, 0x9f, 0xf7, 0xb8, 0xbf, 0xcc, 0xc7, 0x21, 0xf3, 0x27, 0xd1, - 0x14, 0xb1, 0xe1, 0xc4, 0x03, 0x51, 0x3a, 0x40, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, 0x2b, 0x16, 0x70, 0x81, 0x20, - 0xb0, 0x67, 0x5f, 0x44, 0x83, 0x31, 0x6c, 0xf1, 0x8a, 0x70, 0x43, 0xb5, 0x1d, 0x06, 0x39, 0x8b, 0x38, 0x7b, 0x91, - 0x30, 0x7c, 0xc2, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0x15, 0x6a, 0xdf, 0x25, 0x31, 0x7f, 0x9b, 0xc1, 0x3c, 0x1d, 0xc1, - 0x24, 0xc0, 0xc5, 0xc5, 0xd6, 0x56, 0x8c, 0x2c, 0xb2, 0xcf, 0x61, 0xb5, 0x4e, 0x67, 0x9c, 0x01, 0xbd, 0xb0, 0x85, - 0x0d, 0xd4, 0xf6, 0x62, 0x9f, 0x03, 0x11, 0x9f, 0x65, 0x29, 0x87, 0xe1, 0x00, 0x5e, 0xcd, 0x41, 0x7e, 0x34, 0x9d, - 0xb2, 0x74, 0xf8, 0x6c, 0x1c, 0x27, 0x43, 0xa0, 0x46, 0x09, 0xf8, 0x26, 0x3c, 0x04, 0x3c, 0x01, 0x99, 0xe0, 0x66, - 0x8c, 0x68, 0xf9, 0x90, 0x91, 0x59, 0x68, 0xdb, 0x1d, 0x94, 0x40, 0x12, 0x0b, 0x94, 0x41, 0xb4, 0x70, 0x1f, 0x40, - 0xf4, 0x17, 0x2e, 0xdb, 0x0e, 0x63, 0xbd, 0x8c, 0x92, 0xc0, 0xef, 0x51, 0xd2, 0x00, 0xfd, 0x81, 0x10, 0xbc, 0x83, - 0x82, 0xeb, 0x4b, 0x29, 0x75, 0x22, 0xae, 0x30, 0x04, 0x02, 0x0c, 0x50, 0x82, 0x48, 0x1a, 0xbc, 0xcf, 0x92, 0xab, - 0x51, 0x9c, 0x24, 0x07, 0xb3, 0xe9, 0x34, 0xcb, 0xb9, 0xf7, 0x55, 0x38, 0xe7, 0x59, 0x85, 0x2b, 0x6d, 0xf2, 0xe2, - 0x22, 0xe6, 0x48, 0x50, 0x77, 0x3e, 0x88, 0x60, 0xa9, 0x9f, 0x66, 0x59, 0xc2, 0xa2, 0x14, 0xd0, 0xe0, 0x3d, 0xdb, - 0x0e, 0xd2, 0x59, 0x92, 0x74, 0x4e, 0x61, 0xd8, 0x4f, 0x1d, 0xaa, 0x16, 0x12, 0x3f, 0xa0, 0xef, 0xfb, 0x79, 0x1e, - 0x5d, 0x41, 0x43, 0x6c, 0x03, 0xec, 0x05, 0xab, 0xf5, 0xe5, 0xc1, 0xbb, 0xb7, 0xbe, 0x60, 0xfc, 0x78, 0x74, 0x05, - 0x80, 0x96, 0x95, 0xd4, 0x1c, 0xe5, 0xd9, 0x64, 0x61, 0x6a, 0xa4, 0x43, 0x1c, 0xf2, 0xce, 0x1a, 0x10, 0x62, 0x1a, - 0x19, 0x56, 0x89, 0x9b, 0x10, 0xbc, 0x25, 0x7e, 0x96, 0x95, 0xb8, 0x07, 0x7a, 0xf8, 0x25, 0x10, 0xc5, 0x30, 0xe5, - 0x2d, 0xd0, 0xe6, 0x57, 0xf3, 0x38, 0x24, 0x38, 0xa7, 0xa8, 0x7f, 0x11, 0xc6, 0x41, 0x04, 0xb3, 0xcf, 0xc5, 0x80, - 0xa5, 0x82, 0x38, 0x2e, 0x4b, 0x6f, 0xac, 0x99, 0x18, 0x25, 0x1e, 0x0a, 0x14, 0x16, 0x86, 0xa0, 0x60, 0x38, 0x3c, - 0xb8, 0xde, 0xd7, 0xe1, 0x3c, 0x52, 0xf8, 0xa0, 0x86, 0xc2, 0xfd, 0x15, 0x08, 0x39, 0x81, 0x9a, 0xec, 0x1c, 0xf4, - 0x20, 0xc0, 0xf9, 0x95, 0x07, 0xfa, 0x3f, 0x41, 0x28, 0x36, 0x5a, 0x1e, 0x68, 0xd0, 0x67, 0xe3, 0x28, 0x3d, 0x63, - 0xc3, 0x60, 0xcc, 0x4b, 0x29, 0x79, 0xf7, 0x2d, 0x58, 0x63, 0x60, 0xa7, 0xc2, 0x7a, 0x79, 0xf8, 0xe6, 0xb5, 0x5c, - 0xb9, 0x9a, 0x30, 0x86, 0x45, 0x9a, 0x81, 0x5a, 0x05, 0xb1, 0x2d, 0xc5, 0xf1, 0x0b, 0x2d, 0xbd, 0x45, 0x49, 0x5c, - 0x7c, 0x9c, 0x82, 0x89, 0xc1, 0xde, 0xc3, 0x30, 0x30, 0x7d, 0x08, 0x53, 0x51, 0x39, 0xcc, 0x27, 0x2a, 0x86, 0xba, - 0x08, 0x3a, 0x0b, 0x4c, 0xc5, 0x63, 0xe6, 0xb8, 0x25, 0xb0, 0x2a, 0x8f, 0x07, 0x56, 0x34, 0x1c, 0xbe, 0x4a, 0x63, - 0x1e, 0x47, 0x49, 0xfc, 0x0b, 0x51, 0x72, 0x8e, 0x3c, 0xc6, 0x3a, 0x72, 0x11, 0x00, 0x77, 0xea, 0x91, 0xb8, 0x4a, - 0xc8, 0x6e, 0x10, 0x31, 0x84, 0xb4, 0x4c, 0xc2, 0xa3, 0xbe, 0x04, 0x2f, 0xf1, 0xa7, 0xb3, 0x62, 0x8c, 0x84, 0x95, - 0x03, 0xa3, 0x20, 0xcf, 0x4e, 0x0b, 0x96, 0x9f, 0xb3, 0xa1, 0xe6, 0x80, 0x02, 0xb0, 0xa2, 0xe6, 0x60, 0xbc, 0xd0, - 0x8c, 0x8e, 0xd2, 0xa1, 0x1c, 0x86, 0xea, 0x98, 0x62, 0x96, 0x49, 0x66, 0xd6, 0x16, 0x8e, 0x96, 0x02, 0x8e, 0x30, - 0x2a, 0xa4, 0x24, 0x28, 0x42, 0x85, 0xe1, 0x18, 0xa4, 0x10, 0x73, 0x6b, 0xdb, 0x5c, 0x69, 0xb2, 0x17, 0x33, 0x52, - 0x09, 0x05, 0x74, 0x84, 0x8d, 0x4c, 0x90, 0x16, 0x2e, 0xec, 0x2a, 0x90, 0xf2, 0x12, 0x5c, 0x21, 0x45, 0x94, 0x99, - 0x83, 0x0c, 0x10, 0x7e, 0x4d, 0xba, 0x90, 0xf9, 0xd8, 0x82, 0x21, 0x1b, 0xf8, 0x7a, 0xe5, 0x81, 0xb0, 0x12, 0xef, - 0x0a, 0x11, 0x6f, 0x0d, 0xd8, 0xa4, 0x8b, 0x00, 0x30, 0x6f, 0x83, 0xf9, 0x69, 0xb6, 0x3f, 0x18, 0xb0, 0xa2, 0xc8, - 0xf2, 0xad, 0xad, 0x0d, 0x6a, 0xbf, 0xce, 0xd0, 0x02, 0x4a, 0xba, 0x5a, 0xd6, 0xd9, 0x05, 0x69, 0x70, 0x53, 0xad, - 0x28, 0x9d, 0x1e, 0xd8, 0xc7, 0xc7, 0x20, 0xb3, 0x3d, 0x49, 0x06, 0xa0, 0xfa, 0xb2, 0xe1, 0x27, 0xec, 0x99, 0x3a, - 0x65, 0x56, 0xda, 0x97, 0x4e, 0x1d, 0x24, 0x0f, 0x86, 0x75, 0x4b, 0x63, 0x41, 0x57, 0x0e, 0x8d, 0xab, 0x21, 0x15, - 0xe4, 0xfc, 0x8c, 0x54, 0xb6, 0xb1, 0x8c, 0x60, 0xb5, 0x95, 0x1e, 0x91, 0x5e, 0x61, 0x93, 0x13, 0xa0, 0x47, 0xbc, - 0xdf, 0x91, 0xf5, 0x61, 0x21, 0x28, 0x97, 0xb3, 0x9f, 0x67, 0xac, 0xe0, 0x82, 0x75, 0x61, 0xdc, 0x1c, 0xc6, 0x2d, - 0x97, 0xac, 0xc3, 0x9a, 0xed, 0xb8, 0x0a, 0xb6, 0x77, 0x53, 0xd4, 0x63, 0x05, 0x72, 0xf2, 0xcd, 0xec, 0x44, 0xf6, - 0x84, 0x7b, 0x7d, 0xfd, 0xb5, 0x1a, 0xa4, 0x5a, 0x4a, 0x6d, 0x03, 0x2d, 0xac, 0x89, 0xad, 0x9a, 0x0c, 0x6d, 0x57, - 0x2a, 0xd4, 0x8d, 0x56, 0xa7, 0xc6, 0x07, 0xb0, 0xe7, 0x9a, 0x9a, 0xa5, 0x2b, 0x63, 0xfb, 0xbd, 0xa2, 0xe9, 0x3b, - 0x31, 0x32, 0x59, 0xa3, 0xfc, 0x76, 0xee, 0x51, 0x3b, 0x1e, 0xda, 0x2e, 0xd5, 0x55, 0x82, 0x61, 0x56, 0x17, 0x0c, - 0x8b, 0x50, 0x4f, 0x75, 0x17, 0x5b, 0x33, 0x15, 0x0f, 0xd5, 0x5a, 0x2b, 0x07, 0x82, 0x85, 0x47, 0x60, 0x9c, 0xac, - 0xf4, 0x0f, 0xde, 0x46, 0x13, 0x86, 0x14, 0xf5, 0xd6, 0x35, 0x90, 0x0e, 0x04, 0x34, 0xe9, 0x2f, 0xaa, 0x37, 0xe6, - 0x0a, 0xab, 0xa9, 0xbe, 0xbf, 0x62, 0xb0, 0x22, 0xc0, 0xbe, 0x2e, 0x57, 0x2c, 0x11, 0xe9, 0x4d, 0xc9, 0xce, 0x8a, - 0x3e, 0xa2, 0x4c, 0xac, 0x09, 0x29, 0x78, 0x40, 0x1e, 0x96, 0x7f, 0x61, 0xe1, 0x54, 0x2b, 0x85, 0x23, 0x43, 0x99, - 0x02, 0x74, 0x26, 0x25, 0x00, 0xe2, 0x92, 0x3e, 0x6b, 0x1b, 0x0b, 0xc9, 0x76, 0x80, 0x7c, 0xe0, 0x8f, 0x92, 0x88, - 0x3b, 0xad, 0x9d, 0xa6, 0x0b, 0x7c, 0x08, 0x42, 0x1c, 0x74, 0x04, 0x98, 0xf7, 0x15, 0x2a, 0x1c, 0x51, 0x89, 0x5d, - 0xe6, 0x83, 0x51, 0x34, 0x8e, 0x47, 0xdc, 0x49, 0x90, 0x79, 0xdc, 0x92, 0x25, 0xa0, 0x64, 0xf4, 0xbe, 0x02, 0x65, - 0xc1, 0x84, 0x74, 0x11, 0xd5, 0x4a, 0xa0, 0x31, 0x05, 0x29, 0x49, 0x29, 0xd2, 0x82, 0x0a, 0x02, 0x43, 0xa8, 0x74, - 0x14, 0x47, 0x81, 0x7e, 0x8b, 0x7b, 0x62, 0xd0, 0x60, 0xc9, 0xa2, 0x8c, 0x7b, 0xf1, 0x72, 0x21, 0xa8, 0x61, 0x9f, - 0x67, 0xaf, 0xb3, 0x0b, 0x96, 0x3f, 0x8b, 0x10, 0xf6, 0x40, 0x74, 0x2f, 0x41, 0xd2, 0x93, 0x40, 0xe7, 0x1d, 0xc5, - 0x2b, 0xe7, 0x84, 0x34, 0x2c, 0xc4, 0x24, 0x46, 0x45, 0x08, 0x76, 0x0b, 0xd1, 0x3e, 0xc5, 0x2d, 0x45, 0x7b, 0x0f, - 0x55, 0x09, 0xd7, 0xbc, 0xb5, 0xff, 0xba, 0xce, 0x5b, 0x30, 0xc2, 0x54, 0x71, 0x6b, 0x7d, 0xc7, 0x82, 0x7b, 0x21, - 0x74, 0xb3, 0x23, 0x79, 0xcb, 0x50, 0x66, 0xa0, 0x3f, 0xae, 0xaf, 0x2b, 0x23, 0x1d, 0x94, 0xa9, 0x96, 0xe6, 0x08, - 0x81, 0xd8, 0x12, 0x6e, 0x09, 0xca, 0x08, 0x0d, 0xaf, 0x3c, 0x4b, 0x12, 0x43, 0x17, 0x79, 0x71, 0xc7, 0x59, 0x50, - 0x47, 0x00, 0xc5, 0xa4, 0xa6, 0x91, 0x7a, 0x2c, 0xd0, 0x15, 0xa8, 0x94, 0x94, 0x36, 0xf2, 0xaa, 0xb5, 0x11, 0x10, - 0xa7, 0x43, 0x96, 0x0b, 0x07, 0x4d, 0xea, 0x50, 0x98, 0x30, 0x05, 0x86, 0x66, 0x43, 0xf4, 0x1c, 0x24, 0x02, 0x60, - 0x9e, 0xf8, 0xe3, 0xac, 0xe0, 0xba, 0xce, 0x84, 0x3e, 0xbe, 0xbe, 0x8e, 0x85, 0xbf, 0x88, 0x0c, 0x90, 0xb3, 0x49, - 0x76, 0xce, 0x56, 0x40, 0xdd, 0x51, 0x83, 0x99, 0x20, 0x1b, 0xc3, 0x80, 0x12, 0x05, 0xd5, 0x32, 0x4d, 0x62, 0xb0, - 0xf4, 0x75, 0x03, 0x1f, 0x0c, 0x3a, 0x76, 0x89, 0x32, 0xc2, 0xed, 0x76, 0xbb, 0x4d, 0xaf, 0xe5, 0x96, 0x82, 0xe0, - 0xf3, 0x25, 0x8a, 0xde, 0xa0, 0x1f, 0xa5, 0x09, 0xbe, 0x4a, 0x16, 0x30, 0xd7, 0x50, 0x8a, 0xc2, 0x4f, 0x62, 0x9e, - 0x14, 0xc4, 0xae, 0x37, 0x84, 0x41, 0x39, 0x53, 0x82, 0x1b, 0x4d, 0x5c, 0xb1, 0x6d, 0x3f, 0x68, 0xb2, 0x69, 0x76, - 0x52, 0x3b, 0x4c, 0x2d, 0x8c, 0x5c, 0xf3, 0x42, 0x7b, 0xc0, 0xe6, 0xf2, 0x90, 0x4d, 0x8f, 0xd5, 0xc0, 0xeb, 0x00, - 0xa1, 0xf0, 0x74, 0x9d, 0x25, 0x94, 0xaa, 0xce, 0x52, 0x88, 0xeb, 0x0d, 0xf4, 0x51, 0x81, 0xb9, 0x8a, 0x04, 0x07, - 0x52, 0x20, 0x30, 0xf4, 0xc8, 0xc4, 0x7a, 0x3d, 0x83, 0xe5, 0x39, 0x8d, 0x06, 0x9f, 0x34, 0xb8, 0x15, 0xef, 0x2d, - 0xb2, 0x81, 0xb3, 0x50, 0x12, 0x1a, 0xe2, 0xca, 0xc4, 0x5b, 0x49, 0xe8, 0xda, 0x46, 0x01, 0x87, 0x6c, 0x89, 0xed, - 0x17, 0x17, 0x7a, 0x91, 0xdb, 0x25, 0x7b, 0x28, 0xff, 0xa9, 0xe2, 0x92, 0xf5, 0x2c, 0xc7, 0x94, 0x34, 0x60, 0x8a, - 0xf1, 0x60, 0x69, 0x16, 0x20, 0x01, 0xbe, 0x2b, 0x87, 0x71, 0xb1, 0x9e, 0x04, 0x7f, 0x28, 0x98, 0xcf, 0x8d, 0x99, - 0x6e, 0x85, 0x54, 0x4b, 0x38, 0x69, 0x06, 0x6b, 0xd0, 0xa4, 0xf1, 0xa0, 0x44, 0xcd, 0x57, 0x68, 0xa8, 0x10, 0xc7, - 0x9f, 0x89, 0x2a, 0x34, 0xc1, 0x10, 0x8c, 0xc2, 0xcb, 0x25, 0xc3, 0xa5, 0xcb, 0xa2, 0x45, 0xca, 0xd4, 0x98, 0x54, - 0xaa, 0x66, 0xb9, 0x14, 0x0c, 0x2c, 0xda, 0xad, 0xbe, 0xb4, 0xc4, 0x95, 0xc8, 0xcd, 0x42, 0x2d, 0x4c, 0x72, 0xe5, - 0x4d, 0x38, 0x05, 0xfa, 0x5d, 0xca, 0x7a, 0x37, 0xf1, 0x29, 0x14, 0x3e, 0x85, 0x6f, 0xf8, 0x50, 0x26, 0x6f, 0xe7, - 0x3d, 0x30, 0xf7, 0x6b, 0x95, 0x68, 0x9f, 0xfa, 0x28, 0x98, 0x5d, 0x2d, 0x74, 0x41, 0xa0, 0x48, 0x36, 0xc9, 0x7a, - 0x92, 0xdf, 0x50, 0x6c, 0x54, 0x9e, 0x51, 0xea, 0x8a, 0x0d, 0x52, 0xf3, 0x4a, 0x53, 0x2f, 0x73, 0x17, 0xec, 0xf7, - 0xb2, 0x94, 0x74, 0x62, 0x82, 0x32, 0xb1, 0x77, 0x13, 0x6d, 0xbc, 0x2c, 0x4c, 0x85, 0xf5, 0x2b, 0x8c, 0x9d, 0x1a, - 0x85, 0x32, 0x29, 0x02, 0x71, 0x6c, 0x7c, 0xac, 0x2c, 0x83, 0xd4, 0x5f, 0x61, 0x4f, 0x01, 0x28, 0x09, 0x2c, 0xbe, - 0xa6, 0x92, 0x17, 0x85, 0x75, 0x3a, 0x6e, 0x10, 0x1d, 0x2b, 0x11, 0x5a, 0x13, 0xf9, 0x5a, 0x9f, 0xc5, 0x7e, 0xcd, - 0x25, 0x34, 0x29, 0x59, 0xf4, 0x8a, 0xc0, 0x56, 0x81, 0x88, 0x4a, 0xb7, 0x25, 0xbd, 0x84, 0x1c, 0xd2, 0x65, 0xa2, - 0xd7, 0x46, 0x32, 0x68, 0x9d, 0x09, 0x89, 0x96, 0xf5, 0xc3, 0x08, 0xc5, 0x86, 0x58, 0x8b, 0x25, 0x42, 0x2e, 0xda, - 0x9b, 0xc4, 0x8a, 0xe8, 0x9c, 0x16, 0x68, 0xc2, 0x99, 0x3a, 0xdd, 0x71, 0x00, 0x1d, 0x10, 0xfb, 0x4b, 0xac, 0xb7, - 0xd2, 0xec, 0x74, 0xfd, 0xca, 0xe1, 0xbb, 0xbe, 0x1e, 0x73, 0xd7, 0x91, 0x06, 0x2f, 0xac, 0x59, 0x4f, 0xc9, 0xde, - 0xfd, 0xd7, 0xd8, 0x8a, 0xec, 0xcf, 0xaa, 0xa4, 0xf2, 0x14, 0x6a, 0x9c, 0x5b, 0x5f, 0xa7, 0x5a, 0x68, 0x51, 0x55, - 0x1c, 0x18, 0x52, 0xfd, 0x40, 0x29, 0xec, 0x0a, 0xe5, 0x03, 0x39, 0x74, 0xec, 0xba, 0x6e, 0x50, 0x90, 0xf3, 0xb2, - 0xb1, 0xca, 0x85, 0xdc, 0xda, 0x32, 0x7d, 0xa6, 0x73, 0x3d, 0xfc, 0x33, 0x07, 0x95, 0x73, 0x71, 0x95, 0x92, 0x05, - 0xf3, 0x4c, 0xa9, 0xa3, 0x25, 0x07, 0xb4, 0xd9, 0x41, 0x4f, 0x3b, 0xba, 0x88, 0x62, 0x6e, 0xe9, 0x51, 0x84, 0xa7, - 0x8d, 0xf2, 0x49, 0x1a, 0x1d, 0x80, 0x17, 0x9a, 0x90, 0xe4, 0x84, 0x9b, 0xb6, 0x68, 0x31, 0x18, 0x33, 0x0c, 0x81, - 0x2b, 0x7b, 0xc2, 0x94, 0x3d, 0x1b, 0x88, 0xb7, 0x1c, 0x78, 0x35, 0xec, 0xe5, 0x62, 0xf7, 0x9a, 0xf9, 0x0f, 0x6b, - 0x04, 0xb2, 0x6d, 0xa2, 0xea, 0xca, 0x85, 0x67, 0x29, 0x22, 0x31, 0xc2, 0xb6, 0x6a, 0x6c, 0x69, 0xeb, 0x77, 0x16, - 0xdc, 0xeb, 0xca, 0x31, 0xaf, 0x29, 0xd5, 0x05, 0x3d, 0xac, 0xdc, 0x1c, 0x6e, 0x3a, 0xf2, 0x62, 0x05, 0xdd, 0x8e, - 0x08, 0x0a, 0x81, 0x13, 0xa1, 0xec, 0x41, 0xcd, 0x0d, 0x44, 0x4a, 0xa6, 0xb4, 0x6a, 0x36, 0x4b, 0x86, 0x12, 0x58, - 0x70, 0x61, 0x99, 0xe4, 0xa3, 0x8b, 0x38, 0x49, 0xaa, 0xd2, 0x3f, 0x54, 0xc0, 0x8b, 0x61, 0x6f, 0x13, 0xed, 0x02, - 0xa3, 0x99, 0x02, 0xc1, 0xd5, 0x46, 0xd8, 0x47, 0xc7, 0xad, 0xd6, 0x5d, 0x44, 0x1c, 0x99, 0x19, 0x8d, 0xf8, 0x88, - 0x36, 0x64, 0xc9, 0x34, 0x6b, 0xef, 0xbf, 0xc0, 0x90, 0x9a, 0x81, 0x0f, 0xaa, 0x33, 0x2a, 0xfe, 0x55, 0xf6, 0xd4, - 0xaf, 0x44, 0xef, 0x56, 0xd5, 0xb5, 0x18, 0x50, 0x51, 0x81, 0x0f, 0x33, 0xc4, 0xd2, 0x54, 0x81, 0x80, 0x5c, 0x0f, - 0xeb, 0x70, 0xb7, 0x46, 0x1a, 0x2c, 0x28, 0x05, 0xd6, 0x5a, 0xd9, 0xbd, 0xbe, 0x2d, 0x98, 0x43, 0xa1, 0x70, 0xd1, - 0xff, 0x59, 0x36, 0x99, 0xa2, 0x65, 0xb6, 0xc0, 0xd4, 0xd0, 0xe0, 0xe3, 0x42, 0x7d, 0xb9, 0xa2, 0xac, 0xd6, 0x87, - 0x76, 0x64, 0x8d, 0x9f, 0xb4, 0xa3, 0x0c, 0x0e, 0xd5, 0x4c, 0x17, 0xd5, 0xed, 0xe6, 0x45, 0x11, 0xb3, 0x8a, 0xc7, - 0x7d, 0xd2, 0xdb, 0xda, 0x9a, 0xf4, 0x34, 0x0d, 0x48, 0x26, 0x49, 0x86, 0x37, 0x19, 0xa0, 0xac, 0x88, 0x33, 0x2f, - 0x17, 0xc8, 0x37, 0x2f, 0x4b, 0x5c, 0xbf, 0xef, 0x3b, 0xfb, 0x35, 0xcf, 0xda, 0xdb, 0x5f, 0xef, 0x22, 0x57, 0x75, - 0xd2, 0x83, 0x3c, 0xea, 0x43, 0xd1, 0x92, 0x4d, 0x19, 0xce, 0x27, 0xd9, 0x90, 0x05, 0x36, 0x74, 0x4f, 0xed, 0x52, - 0x6e, 0x9a, 0x08, 0x36, 0x07, 0xf8, 0x7f, 0xf3, 0x0f, 0xf5, 0x48, 0x6a, 0xb0, 0x0f, 0x2c, 0xa0, 0xcd, 0x85, 0x2f, - 0xc3, 0xb3, 0x24, 0x3b, 0x8d, 0x92, 0x43, 0xa1, 0xc0, 0x6b, 0x2d, 0xbf, 0x01, 0x97, 0x91, 0x2c, 0x56, 0x43, 0x49, - 0x7d, 0xd9, 0xfb, 0x32, 0xb8, 0xbd, 0x47, 0xe5, 0xad, 0xd8, 0x2d, 0xbf, 0xe9, 0xb7, 0x6c, 0x15, 0x11, 0xfb, 0xc9, - 0x9c, 0x0e, 0x34, 0x4e, 0x01, 0x94, 0x39, 0x04, 0x4d, 0x56, 0x78, 0x03, 0x1e, 0xfe, 0xd4, 0xfb, 0x49, 0xb9, 0xd4, - 0x19, 0xb8, 0x10, 0xe0, 0xe4, 0x27, 0x31, 0x6f, 0xe0, 0x79, 0xa4, 0xed, 0xcd, 0x45, 0x05, 0xc6, 0x15, 0x29, 0x2e, - 0x5d, 0x2a, 0x6f, 0xd0, 0x3b, 0x0e, 0x4f, 0xa0, 0xd9, 0xe6, 0xe6, 0xdc, 0x79, 0x13, 0xf1, 0xb1, 0x9f, 0x47, 0xe9, - 0x30, 0x9b, 0x38, 0xee, 0xb6, 0x6d, 0xbb, 0x7e, 0x41, 0x9e, 0xc8, 0x67, 0x6e, 0xb9, 0x79, 0xe2, 0x0d, 0x79, 0x68, - 0xf7, 0xec, 0xed, 0x63, 0xef, 0x90, 0x87, 0x27, 0x7b, 0x9b, 0xf3, 0x21, 0x2f, 0xbb, 0x27, 0xde, 0xa5, 0x8e, 0xb9, - 0x7b, 0xef, 0x51, 0xca, 0x40, 0xaf, 0xb0, 0x7b, 0x29, 0xc1, 0x00, 0x76, 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0x8f, 0x74, - 0x20, 0x22, 0xe3, 0xb4, 0xd7, 0xd7, 0x76, 0x46, 0x11, 0x03, 0x7b, 0x43, 0x3b, 0xab, 0x5b, 0x5b, 0x95, 0x9a, 0xaf, - 0x4a, 0xbd, 0x19, 0x0f, 0x6b, 0x9e, 0xba, 0xf7, 0x92, 0x8e, 0x56, 0xea, 0x1b, 0x79, 0x26, 0x82, 0x36, 0xcb, 0x76, - 0x82, 0x63, 0x6c, 0xf1, 0xd5, 0xdb, 0xfa, 0x48, 0x44, 0x29, 0xfc, 0x18, 0xac, 0x97, 0x08, 0xd4, 0x37, 0x38, 0x38, - 0xde, 0x61, 0xb8, 0xb3, 0xe7, 0xf4, 0x02, 0x67, 0xa3, 0xd1, 0xb8, 0xfe, 0x61, 0xe7, 0xe8, 0xc7, 0xa8, 0xf1, 0xcb, - 0x7e, 0xe3, 0xfb, 0xbe, 0x7b, 0xed, 0xfc, 0xb0, 0xd3, 0x3b, 0x92, 0x4f, 0x47, 0x3f, 0x76, 0x7f, 0x28, 0xfa, 0x7f, - 0x12, 0x85, 0x9b, 0xae, 0xbb, 0x73, 0xe6, 0x4d, 0x79, 0xb8, 0xd3, 0x68, 0x74, 0xe1, 0xdb, 0x19, 0x7c, 0xc3, 0xcf, - 0x53, 0xf8, 0xb8, 0x3e, 0xb2, 0xfe, 0xc3, 0x0f, 0xe9, 0x7f, 0xfc, 0x21, 0xef, 0xe3, 0x98, 0x47, 0x3f, 0xfe, 0x50, - 0xd8, 0xf7, 0xbb, 0xe1, 0x4e, 0x7f, 0xdb, 0x75, 0x74, 0xcd, 0x9f, 0xc2, 0xea, 0x2b, 0xb4, 0x3a, 0xfa, 0x51, 0x3e, - 0xd9, 0xf7, 0x4f, 0xf6, 0xba, 0x61, 0xff, 0xda, 0xb1, 0xaf, 0xef, 0xbb, 0xd7, 0xae, 0x7b, 0xbd, 0x89, 0xf3, 0x9c, - 0xc3, 0xe8, 0xf7, 0xe1, 0x73, 0x04, 0x9f, 0x36, 0x7c, 0x6e, 0xc2, 0xe7, 0x8f, 0xd0, 0x4d, 0xc4, 0xdf, 0xae, 0x29, - 0x16, 0x72, 0x8d, 0x07, 0x16, 0x11, 0xac, 0x82, 0xbb, 0xb9, 0x13, 0x7b, 0x13, 0x22, 0x1a, 0xec, 0x43, 0xdf, 0xf7, - 0x31, 0x4c, 0xea, 0xcc, 0x8f, 0x37, 0x61, 0xd1, 0x91, 0x73, 0x36, 0x03, 0xee, 0x89, 0xc8, 0x41, 0x11, 0x30, 0x71, - 0xb6, 0x5a, 0xe0, 0xe1, 0xaa, 0x37, 0x0c, 0x27, 0xdc, 0x01, 0xa3, 0xe0, 0x03, 0xc7, 0x2f, 0x6d, 0xd7, 0x7b, 0x21, - 0xcf, 0x0c, 0x71, 0x9f, 0x0b, 0xd6, 0x4a, 0x33, 0x61, 0xd2, 0xd8, 0xae, 0x37, 0x5d, 0x51, 0x09, 0xdb, 0x3a, 0x3d, - 0x83, 0xba, 0x63, 0x11, 0xa3, 0xfe, 0x96, 0x45, 0x9f, 0x70, 0x4b, 0xbe, 0x35, 0x0e, 0x81, 0x97, 0x2c, 0xf9, 0x45, - 0xa3, 0xd1, 0xb0, 0x11, 0x85, 0x3b, 0xf6, 0x94, 0xc1, 0x0c, 0x4b, 0x26, 0x22, 0x23, 0xa5, 0x29, 0x2c, 0x5b, 0x98, - 0xfc, 0x7d, 0x94, 0xf3, 0xcd, 0xca, 0xb0, 0x0d, 0xeb, 0x96, 0xec, 0x82, 0xa5, 0x7f, 0x87, 0x29, 0xd0, 0xb4, 0xa4, - 0xf3, 0x0f, 0x73, 0xfc, 0x30, 0x23, 0xb4, 0x3e, 0x38, 0x0c, 0x3c, 0xf4, 0x02, 0xe4, 0x8e, 0xe8, 0xe7, 0xbc, 0x47, - 0x35, 0x06, 0xff, 0xcb, 0x30, 0x83, 0x27, 0xe6, 0xc3, 0x10, 0xcd, 0xbc, 0xd4, 0xc1, 0xad, 0x0c, 0xc5, 0xfd, 0x2b, - 0xdc, 0x19, 0x59, 0xe9, 0x1d, 0x84, 0x6a, 0xc7, 0x1c, 0xe6, 0x8c, 0x7d, 0x1b, 0x25, 0x9f, 0x58, 0xee, 0x5c, 0x7a, - 0xad, 0xf6, 0x67, 0xd4, 0xd9, 0x43, 0xdb, 0xec, 0x4d, 0x75, 0x8c, 0xa6, 0xcd, 0x02, 0x79, 0x44, 0xd8, 0x68, 0x79, - 0x28, 0x31, 0x88, 0x04, 0xb9, 0x97, 0x86, 0x6d, 0xe2, 0x70, 0x7b, 0xaf, 0x38, 0x3f, 0xeb, 0xda, 0x81, 0x6d, 0x83, - 0xc5, 0x7f, 0x48, 0x61, 0x2b, 0x61, 0x58, 0x34, 0x3b, 0x6c, 0x2f, 0xee, 0xb0, 0xed, 0xed, 0x2a, 0xe0, 0x84, 0x07, - 0xe9, 0xd4, 0x3d, 0xf1, 0x22, 0x6f, 0x1c, 0xc2, 0x80, 0x03, 0x68, 0x86, 0x5d, 0x3a, 0x83, 0xbd, 0x58, 0x4e, 0x03, - 0xb2, 0x3e, 0xf3, 0x93, 0xa8, 0xe0, 0xaf, 0x30, 0x1e, 0x11, 0x0e, 0xc0, 0xd8, 0xcf, 0x7c, 0x76, 0xc9, 0x06, 0xca, - 0xce, 0x00, 0x42, 0x45, 0x6e, 0xc7, 0x1d, 0x84, 0x46, 0x33, 0x98, 0x3b, 0x0c, 0x0f, 0x7b, 0x36, 0xec, 0x25, 0xd8, - 0x95, 0x61, 0x74, 0xd4, 0xea, 0xf7, 0xb2, 0x70, 0xca, 0x03, 0x4d, 0x5b, 0x59, 0x74, 0x56, 0x2b, 0x6a, 0xf7, 0x7b, - 0xce, 0x26, 0x18, 0xe9, 0x60, 0x8b, 0x3b, 0xf8, 0x84, 0x11, 0x8a, 0x3c, 0xfc, 0xc0, 0xce, 0x5e, 0x5c, 0x4e, 0x1d, - 0x7b, 0x6f, 0xc7, 0xde, 0xc6, 0x52, 0xcf, 0x06, 0xf6, 0x02, 0x0a, 0x86, 0xa7, 0xae, 0xd9, 0x79, 0xb7, 0x8f, 0xa0, - 0x62, 0x21, 0x4e, 0x7e, 0xda, 0xb3, 0xbb, 0x62, 0xea, 0x26, 0x0c, 0x9a, 0xc9, 0xe5, 0xc7, 0x15, 0x3d, 0x24, 0x54, - 0x55, 0x57, 0x05, 0x1d, 0x94, 0xb5, 0x03, 0x67, 0x6c, 0x22, 0xd1, 0xc0, 0xc9, 0x24, 0x15, 0xc0, 0xe1, 0xc1, 0x66, - 0x30, 0xa9, 0xd1, 0x6d, 0xb7, 0xdf, 0x3b, 0x0d, 0xee, 0xdb, 0xf7, 0xd5, 0xc3, 0x08, 0x90, 0xe1, 0x62, 0xfa, 0x11, - 0x48, 0x3b, 0xfc, 0x3c, 0xe7, 0x80, 0xe4, 0x29, 0x15, 0x4d, 0x65, 0xd1, 0x19, 0x16, 0x1d, 0x06, 0x08, 0xaa, 0x97, - 0x6b, 0xeb, 0x4f, 0xac, 0xc9, 0x30, 0x24, 0xd8, 0xc1, 0x16, 0x3a, 0x62, 0xdb, 0xad, 0x3e, 0x9e, 0x37, 0xe4, 0xbc, - 0xf8, 0x36, 0xe6, 0xa0, 0x12, 0x76, 0xba, 0xb6, 0xdb, 0xb3, 0x2d, 0x5c, 0xda, 0x4e, 0xba, 0x1d, 0x0a, 0x0a, 0xc7, - 0xdb, 0x87, 0x3c, 0x18, 0x77, 0xc3, 0x66, 0xcf, 0x29, 0x64, 0xb8, 0x11, 0xcf, 0x2d, 0x85, 0x04, 0x6f, 0x7a, 0x63, - 0x10, 0xe8, 0xc8, 0xb9, 0x9b, 0xf6, 0xb6, 0x2a, 0x84, 0xa2, 0xe3, 0xed, 0xa1, 0x1b, 0xc4, 0xf0, 0xe1, 0x34, 0x90, - 0x69, 0xc6, 0xba, 0xaf, 0xd2, 0xcc, 0xcc, 0x0d, 0x86, 0xca, 0x22, 0x4f, 0xc2, 0x74, 0xdb, 0xc1, 0x08, 0x2d, 0x48, - 0xda, 0xbd, 0x1e, 0xc0, 0xb0, 0xed, 0x28, 0x4e, 0xdb, 0x51, 0xac, 0xa6, 0xec, 0xf3, 0x23, 0xbd, 0x1c, 0x03, 0xde, - 0x1b, 0xa8, 0xf3, 0x58, 0xd4, 0x3e, 0x00, 0x56, 0x90, 0x78, 0x45, 0x5f, 0x9d, 0x79, 0xbd, 0xac, 0x9d, 0x6f, 0xcd, - 0x95, 0x28, 0xe2, 0x9e, 0x21, 0xa1, 0x58, 0xa9, 0xdd, 0x30, 0x61, 0x6e, 0x4f, 0x91, 0x18, 0x9a, 0xe5, 0x43, 0xd8, - 0x63, 0xa1, 0x0a, 0xb0, 0x67, 0xe6, 0xb6, 0x48, 0xc2, 0xaa, 0xb9, 0x77, 0x04, 0xac, 0xdd, 0x0f, 0xdf, 0x08, 0x77, - 0xaa, 0xa3, 0xa2, 0xf9, 0x2c, 0x09, 0x5f, 0x2e, 0x1c, 0x17, 0x47, 0x78, 0x22, 0x74, 0xe0, 0x0f, 0x66, 0x39, 0xc8, - 0x03, 0xfe, 0x16, 0x2c, 0x83, 0x50, 0x36, 0x45, 0x47, 0x0f, 0x8f, 0x80, 0x3d, 0x42, 0x7c, 0x21, 0x6c, 0x6e, 0x54, - 0xa3, 0x45, 0x49, 0xc6, 0x0b, 0x1d, 0x0c, 0x77, 0x98, 0x74, 0xed, 0x51, 0x30, 0xc8, 0x13, 0x63, 0x07, 0xcf, 0xfc, - 0xfd, 0x01, 0x56, 0xe3, 0x04, 0x85, 0x5b, 0xd2, 0x6e, 0xab, 0xc4, 0xdf, 0x81, 0x9f, 0x82, 0x04, 0xc7, 0x3a, 0xf0, - 0xb3, 0xb6, 0xb6, 0x12, 0x89, 0xd4, 0x5e, 0xd6, 0xa1, 0x93, 0x08, 0x8c, 0x07, 0x17, 0x7e, 0x0a, 0xd5, 0x48, 0x22, - 0x2a, 0x22, 0x0b, 0xd4, 0x3c, 0x55, 0xab, 0xe0, 0x3b, 0x32, 0x23, 0xf0, 0x8c, 0x92, 0x5c, 0xd0, 0x50, 0xd4, 0x8d, - 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, 0xa3, 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, - 0xb1, 0x11, 0x2b, 0x1f, 0x1f, 0xa5, 0xdb, 0xdb, 0x7d, 0x71, 0x6e, 0x41, 0x8c, 0xc3, 0x8c, 0xe8, 0x6a, 0x5c, 0x01, - 0x50, 0x9f, 0xce, 0x89, 0xeb, 0x81, 0x69, 0xc5, 0x9a, 0x2e, 0xc5, 0x3e, 0x39, 0xcc, 0x00, 0x14, 0xdc, 0x71, 0x8e, - 0xfc, 0xde, 0x9f, 0xfb, 0xe0, 0x1e, 0xfb, 0x7f, 0x72, 0x77, 0x94, 0xa0, 0xe9, 0xc8, 0x33, 0xc5, 0x39, 0x9d, 0xb1, - 0xb6, 0x3c, 0x8a, 0x8d, 0x06, 0x20, 0xf5, 0x00, 0x03, 0xd0, 0xe6, 0x20, 0x13, 0x2a, 0x0e, 0x42, 0x8e, 0x0a, 0x6c, - 0x1f, 0x37, 0x3f, 0xc3, 0x9d, 0xfd, 0x9c, 0x07, 0x60, 0xc1, 0xa8, 0xa7, 0xd7, 0xf0, 0xf4, 0x67, 0xfd, 0xf4, 0x13, - 0x0f, 0x7e, 0x29, 0x65, 0xe8, 0xbe, 0x36, 0xc5, 0x23, 0x35, 0x45, 0x29, 0x96, 0xc8, 0xa0, 0x21, 0x77, 0x97, 0x63, - 0x36, 0xcc, 0x2d, 0x81, 0x18, 0x4a, 0x74, 0x81, 0x8d, 0x16, 0x9d, 0x21, 0x71, 0x5d, 0x93, 0x14, 0x46, 0x2e, 0x81, - 0x89, 0x70, 0xc5, 0xb7, 0x48, 0x4f, 0xd6, 0x6d, 0xba, 0xf3, 0x5a, 0x5b, 0xb2, 0xef, 0xd8, 0x64, 0xca, 0xaf, 0x0e, - 0x48, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0x37, 0x3b, 0xf1, 0x1e, 0xeb, 0xc4, 0x20, 0xd5, 0x0b, 0xc5, 0x62, - 0xb8, 0x57, 0xbd, 0xf7, 0x18, 0xa5, 0x34, 0x99, 0xc9, 0xab, 0xa1, 0xd7, 0x96, 0xe8, 0x6d, 0x6f, 0x03, 0x82, 0x1d, - 0xa3, 0x2b, 0x13, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, 0x7a, 0x52, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, 0x83, 0xe2, - 0x21, 0x77, 0x57, 0x8e, 0x8a, 0x63, 0x62, 0x77, 0x99, 0xb0, 0x03, 0x98, 0x11, 0x97, 0x37, 0x5a, 0x22, 0x3a, 0x2c, - 0xfa, 0xeb, 0xf8, 0xf6, 0xb1, 0xc7, 0xb7, 0x5b, 0x2e, 0x68, 0x90, 0xda, 0x58, 0x8f, 0xab, 0xb1, 0xa0, 0x3e, 0x3c, - 0xd6, 0x54, 0x2a, 0xf3, 0xed, 0xed, 0xb2, 0x7e, 0x54, 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0x72, 0x31, 0x1b, 0x84, - 0x03, 0x11, 0x13, 0x28, 0xd0, 0xd2, 0xca, 0x8a, 0x01, 0x86, 0x94, 0xe5, 0x28, 0x9f, 0x42, 0xee, 0xc5, 0x65, 0xa9, - 0x53, 0x5f, 0x9e, 0xc9, 0xa0, 0x23, 0x9e, 0x7a, 0x92, 0xb1, 0x02, 0xac, 0xe6, 0x65, 0x5e, 0x42, 0x4b, 0x04, 0x98, - 0xbf, 0x50, 0x39, 0x34, 0xc2, 0x02, 0x89, 0x42, 0xc3, 0x2c, 0x51, 0xc6, 0x67, 0x1e, 0xc6, 0xa0, 0xed, 0x9f, 0xd5, - 0x62, 0x5f, 0xb9, 0x32, 0x3a, 0xf2, 0xa3, 0xa2, 0x1f, 0x50, 0xfd, 0x4c, 0x4a, 0xb0, 0x71, 0xf8, 0x11, 0xd8, 0xa8, - 0x72, 0x3c, 0x49, 0x10, 0x3e, 0x8f, 0x73, 0x46, 0x9e, 0xc2, 0xa6, 0x84, 0x59, 0x9a, 0xb6, 0x91, 0x6a, 0x17, 0x99, - 0x41, 0x28, 0x17, 0xe6, 0x1f, 0x1b, 0x67, 0x17, 0x69, 0xb8, 0xd4, 0x1a, 0xcc, 0x8f, 0x77, 0x26, 0x40, 0xe9, 0xf5, - 0x75, 0x2a, 0x7c, 0xdc, 0x88, 0xec, 0x0d, 0x5d, 0x31, 0xee, 0x29, 0xa4, 0x02, 0x27, 0x22, 0x8b, 0x87, 0xce, 0x50, - 0x68, 0x84, 0x43, 0x3a, 0x45, 0x2e, 0x5c, 0x63, 0xd3, 0x17, 0x3d, 0xed, 0x1b, 0x65, 0xa1, 0x93, 0x80, 0x10, 0x10, - 0xb8, 0x1b, 0xd6, 0x54, 0xd6, 0xcb, 0x82, 0x84, 0x4a, 0xd1, 0xcf, 0x01, 0xfc, 0xc3, 0x48, 0x52, 0x00, 0xec, 0x87, - 0x6a, 0xa4, 0x88, 0xb2, 0x2c, 0x70, 0x01, 0x68, 0xae, 0x03, 0x5c, 0x09, 0x5f, 0x18, 0xa8, 0x30, 0x3d, 0xcd, 0xca, - 0x4a, 0xa1, 0x44, 0x9e, 0xae, 0x48, 0x59, 0x23, 0x99, 0x7c, 0x8e, 0x0e, 0x9f, 0xf2, 0xae, 0xdf, 0x4a, 0x3c, 0x74, - 0xc1, 0x73, 0x58, 0x56, 0xf5, 0xfd, 0x4d, 0xc8, 0xc8, 0xb9, 0x06, 0x5d, 0x21, 0x85, 0xfe, 0x92, 0x93, 0xbc, 0xff, - 0xc6, 0xaf, 0x6a, 0xa9, 0x31, 0x94, 0x7d, 0x5c, 0xd5, 0x0c, 0xcb, 0xcb, 0x69, 0x15, 0xa6, 0x20, 0xe0, 0xe6, 0x2c, - 0x09, 0xe6, 0x52, 0x43, 0x80, 0x85, 0xed, 0x91, 0x56, 0x0a, 0x8a, 0x52, 0x87, 0x77, 0x9e, 0x83, 0x15, 0x60, 0x1c, - 0x6a, 0xa9, 0x64, 0x1a, 0x49, 0x7c, 0xa9, 0x44, 0x81, 0x29, 0x0f, 0x06, 0xe0, 0xa7, 0x2e, 0x9e, 0x74, 0x5d, 0xba, - 0x7e, 0x3c, 0xc1, 0xd4, 0x1e, 0x02, 0x3d, 0xf6, 0x36, 0xc0, 0x94, 0xa8, 0xeb, 0xb0, 0x9c, 0x38, 0x34, 0xad, 0x69, - 0x16, 0x30, 0x63, 0x9a, 0xa0, 0x25, 0x9b, 0x60, 0xcb, 0x15, 0x60, 0x1f, 0x89, 0xed, 0x59, 0xad, 0x80, 0xd0, 0x35, - 0x68, 0x60, 0xc8, 0x5d, 0x2a, 0xb4, 0x30, 0xeb, 0xb4, 0xa9, 0x08, 0xf7, 0x67, 0x8f, 0x49, 0x2b, 0x38, 0xf5, 0x52, - 0x1a, 0xf8, 0x20, 0x3e, 0x4d, 0x30, 0xf1, 0x05, 0xb1, 0x02, 0x3b, 0x38, 0x68, 0x2d, 0x36, 0x05, 0x4e, 0xc5, 0x45, - 0x4a, 0x61, 0x59, 0x51, 0x6a, 0xc3, 0x87, 0x14, 0xd9, 0xba, 0xcb, 0x23, 0xdd, 0x85, 0x58, 0x00, 0x3b, 0xfd, 0xc2, - 0xa1, 0x83, 0xac, 0x97, 0x01, 0x83, 0x73, 0xad, 0x71, 0x10, 0xf8, 0xed, 0xed, 0xa4, 0x5f, 0x66, 0x48, 0xb9, 0x25, - 0x56, 0x17, 0x90, 0xe3, 0x76, 0x58, 0xc0, 0x1d, 0x84, 0xa5, 0xb2, 0xc7, 0xf3, 0x72, 0x82, 0xcb, 0xa5, 0x2c, 0xe4, - 0xc5, 0x74, 0x2c, 0x9a, 0xcf, 0xad, 0x34, 0x9b, 0x8e, 0xb7, 0xe2, 0x83, 0x82, 0xbf, 0xe7, 0xc4, 0xd2, 0xaa, 0xa7, - 0xd4, 0x0a, 0x8f, 0x32, 0xb7, 0x64, 0x9d, 0x92, 0x5a, 0x6d, 0x37, 0x50, 0x8d, 0xf0, 0x34, 0x0d, 0x1b, 0x81, 0x10, - 0x13, 0x5c, 0xfc, 0x61, 0x91, 0x89, 0x69, 0x6f, 0x09, 0xa9, 0x23, 0xec, 0x1e, 0xca, 0x09, 0x6e, 0x6b, 0x9e, 0x7d, - 0x19, 0x4e, 0xd7, 0x33, 0xf7, 0xbe, 0xc1, 0xdc, 0x4f, 0x43, 0x66, 0x30, 0x7a, 0x2c, 0x13, 0x7e, 0x64, 0xec, 0xa3, - 0x50, 0x55, 0xcf, 0xce, 0xc2, 0x4a, 0x64, 0x89, 0x6f, 0xc6, 0x51, 0x87, 0x71, 0x2a, 0x5a, 0x13, 0x64, 0xd7, 0xd7, - 0xb9, 0xb9, 0x17, 0x28, 0x68, 0xea, 0xb1, 0x7a, 0x9c, 0xb6, 0x62, 0x67, 0x23, 0x12, 0xb9, 0xff, 0xa6, 0x16, 0x89, - 0xac, 0xf8, 0x1c, 0x47, 0x5a, 0x73, 0x90, 0xfb, 0xec, 0x6c, 0x79, 0x93, 0x0a, 0xdd, 0xa2, 0xd1, 0x36, 0xf6, 0xa8, - 0x3e, 0x90, 0xd4, 0x33, 0x2a, 0xb0, 0xaa, 0xb1, 0xb7, 0xb6, 0x5a, 0x22, 0xdd, 0x52, 0x29, 0x36, 0x0c, 0x69, 0x85, - 0xcc, 0x18, 0x05, 0x83, 0x92, 0x22, 0x03, 0x35, 0xca, 0xd7, 0x08, 0x86, 0x7d, 0x6a, 0x00, 0x8a, 0x73, 0x75, 0xf5, - 0xd3, 0x52, 0xb2, 0x85, 0x80, 0x04, 0x64, 0x13, 0x8a, 0x35, 0x62, 0x66, 0xe4, 0x93, 0x8f, 0xc0, 0x79, 0x3d, 0x8e, - 0x8e, 0x01, 0xc8, 0x60, 0xb1, 0xe9, 0xc1, 0xc4, 0xb6, 0x89, 0x28, 0xfa, 0x6c, 0xe0, 0x25, 0x00, 0x3b, 0xad, 0x42, - 0xa3, 0x1f, 0xaa, 0x14, 0x30, 0x64, 0x03, 0x37, 0xe0, 0x55, 0x58, 0x6e, 0xff, 0x25, 0xb4, 0x83, 0xc7, 0x17, 0xb2, - 0xf9, 0x26, 0xe6, 0x09, 0x56, 0xb1, 0x3b, 0xbf, 0xb2, 0xac, 0xc5, 0xb9, 0xd3, 0xe1, 0x42, 0xbd, 0xa2, 0x84, 0xa8, - 0x3d, 0xc0, 0xda, 0x97, 0x9c, 0x60, 0xc4, 0xe7, 0x37, 0x94, 0x75, 0xa8, 0xc6, 0x2d, 0xf7, 0x35, 0x5a, 0x84, 0xe9, - 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, 0xbd, 0x3c, 0x10, 0xb1, 0xe0, 0x0a, 0x47, 0x23, 0x6c, 0xbe, 0x80, - 0x24, 0x7d, 0xdb, 0xa7, 0x03, 0xf6, 0xcd, 0xc5, 0x5e, 0x40, 0x99, 0x8f, 0x15, 0xa9, 0x24, 0xa4, 0x34, 0xbb, 0x21, - 0x92, 0x84, 0xb5, 0x22, 0x4f, 0x9d, 0x0f, 0x1c, 0xed, 0x73, 0x2b, 0x89, 0x60, 0x04, 0x27, 0x71, 0xba, 0xf2, 0x70, - 0x51, 0x80, 0xab, 0xe8, 0x88, 0xe9, 0x9b, 0xa0, 0xfc, 0x06, 0xb9, 0xbd, 0x94, 0x5c, 0x5b, 0x68, 0x18, 0x9e, 0x21, - 0xc1, 0xaa, 0x48, 0x04, 0x3a, 0x0a, 0x80, 0xe3, 0x4a, 0xcf, 0x03, 0x4c, 0xf8, 0xda, 0xde, 0x04, 0x80, 0x44, 0x56, - 0x90, 0xb3, 0x14, 0xe8, 0x06, 0x2c, 0x57, 0xc7, 0xa9, 0x51, 0x91, 0xb8, 0xb8, 0x31, 0x5d, 0xdd, 0xd2, 0x9f, 0xa0, - 0xe5, 0x4c, 0x86, 0x98, 0x0e, 0x82, 0x80, 0x4c, 0x7d, 0xca, 0x9d, 0x9c, 0xa6, 0x13, 0xd6, 0xe7, 0xd4, 0xa9, 0x4d, - 0xdd, 0xe1, 0xd4, 0xcd, 0x93, 0xd4, 0x62, 0x75, 0xda, 0x94, 0x12, 0x31, 0x29, 0x31, 0x8f, 0x65, 0x2a, 0xb6, 0x12, - 0x77, 0x6e, 0x7d, 0xa3, 0x85, 0xb4, 0xd1, 0x8e, 0x65, 0x0e, 0xb6, 0x96, 0xf7, 0x42, 0xb4, 0xbf, 0x24, 0xc2, 0xb3, - 0x12, 0x19, 0x6b, 0x3e, 0xe3, 0x8e, 0x89, 0x60, 0xf5, 0x60, 0x2a, 0xf2, 0x0f, 0x8e, 0x4e, 0xb3, 0x37, 0xe8, 0x41, - 0xea, 0x0d, 0x24, 0x66, 0x4d, 0x7c, 0xe7, 0xd2, 0x50, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0xa5, 0xe2, - 0x92, 0x7c, 0xf5, 0x5e, 0x1f, 0xe7, 0x1b, 0xdf, 0x17, 0x56, 0x23, 0x88, 0xc1, 0x5b, 0x28, 0xfa, 0x9e, 0x14, 0xe1, - 0x39, 0x2c, 0xcf, 0xf6, 0x76, 0xa7, 0xd8, 0x63, 0x55, 0x88, 0xa4, 0x82, 0x31, 0xc6, 0x8c, 0x62, 0xdc, 0x13, 0x35, - 0xb5, 0x88, 0xc4, 0x96, 0xad, 0xc3, 0x02, 0x0f, 0x00, 0xa0, 0xa5, 0x29, 0xbd, 0xcc, 0xb6, 0xea, 0x3c, 0x97, 0xf0, - 0x31, 0xf2, 0x50, 0x64, 0xe3, 0xf7, 0x6b, 0x32, 0x50, 0x10, 0xee, 0x8d, 0x96, 0x87, 0x89, 0x71, 0xb0, 0x8a, 0x42, - 0x16, 0xe8, 0x0d, 0xda, 0xa9, 0x12, 0xa1, 0xb8, 0x39, 0x59, 0x87, 0x1b, 0x4e, 0x2a, 0xd8, 0x42, 0x25, 0x2c, 0x95, - 0x16, 0xf8, 0xd5, 0x46, 0x58, 0x3c, 0x65, 0xdc, 0x7f, 0x53, 0xe1, 0x0c, 0xfa, 0x83, 0x7b, 0xcb, 0x8c, 0xfa, 0x7e, - 0xe9, 0x44, 0xa6, 0x02, 0x13, 0x37, 0xb3, 0xd4, 0x7e, 0xbf, 0xac, 0xd2, 0x7e, 0x5e, 0x2e, 0xf7, 0x39, 0x69, 0xbe, - 0xd6, 0x1d, 0x34, 0x9f, 0x0c, 0xf7, 0x2b, 0xe5, 0x87, 0x16, 0x46, 0x4d, 0xf9, 0xd5, 0x97, 0x34, 0xcc, 0x3d, 0x15, - 0xde, 0xea, 0xb6, 0x51, 0xe8, 0xa2, 0x3e, 0x07, 0x43, 0x48, 0x7f, 0x05, 0xd7, 0xd0, 0xe0, 0x41, 0x91, 0x2c, 0x16, - 0x6b, 0x17, 0xc4, 0xf5, 0x31, 0xa7, 0xda, 0xa1, 0x8c, 0x31, 0xe2, 0x69, 0xc9, 0x41, 0x92, 0xc1, 0xc1, 0xf8, 0x0d, - 0x0c, 0x88, 0x49, 0x49, 0x48, 0x87, 0xd0, 0x59, 0x99, 0x89, 0xa8, 0xdc, 0xc5, 0xdb, 0x8d, 0xcb, 0x9a, 0x42, 0x11, - 0x76, 0x82, 0x99, 0x4a, 0xa9, 0x20, 0x90, 0x26, 0xdf, 0x46, 0xab, 0x16, 0x0c, 0x05, 0xd1, 0x60, 0x28, 0x20, 0x0f, - 0xd3, 0x55, 0xc2, 0x8d, 0x8f, 0xe2, 0xe0, 0x79, 0x85, 0x1a, 0xf1, 0x52, 0x83, 0xaf, 0x61, 0xf3, 0xd7, 0x44, 0x49, - 0x11, 0x72, 0x11, 0x7b, 0x05, 0x9f, 0x08, 0xd9, 0x94, 0x87, 0x39, 0xd0, 0x0f, 0xed, 0xca, 0x4e, 0xb6, 0x97, 0x57, - 0x2e, 0x2d, 0x1a, 0x5b, 0x89, 0x9a, 0xb5, 0x38, 0x8a, 0xb7, 0xb3, 0x3e, 0x4c, 0x4d, 0x09, 0x04, 0xa4, 0xa9, 0x9c, - 0xa4, 0x9a, 0xf7, 0x28, 0xeb, 0x03, 0x48, 0xb0, 0xfb, 0x09, 0x2c, 0xf4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, - 0x53, 0xd0, 0x9a, 0x53, 0xd2, 0x7c, 0x53, 0x84, 0x70, 0x5b, 0x59, 0xcf, 0x98, 0x1d, 0x60, 0xdb, 0xee, 0x76, 0x7e, - 0x94, 0x6d, 0xb7, 0xfa, 0x86, 0xe0, 0xc2, 0xe3, 0xff, 0xa4, 0xc4, 0x34, 0x90, 0x42, 0xea, 0xc6, 0x4f, 0xa8, 0xc3, - 0x3e, 0x91, 0x3a, 0x11, 0x03, 0x9a, 0xab, 0xb1, 0xe8, 0xdc, 0x6b, 0x8e, 0x92, 0xcb, 0xaa, 0xda, 0xd5, 0x12, 0x34, - 0x74, 0x23, 0x19, 0x13, 0xc5, 0x3c, 0x27, 0x00, 0x46, 0xb1, 0xf9, 0x73, 0xae, 0x93, 0xbc, 0x7f, 0x59, 0x99, 0xda, - 0xed, 0xfb, 0x7e, 0x94, 0x9f, 0xd1, 0x91, 0x8a, 0xca, 0xe6, 0x24, 0xe6, 0xdf, 0x95, 0x60, 0x1a, 0x13, 0x1f, 0xe9, - 0xb9, 0xfa, 0xa1, 0x00, 0x5f, 0xd9, 0x50, 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x07, 0x72, 0x36, 0xc1, 0x02, 0x0b, - 0x74, 0x59, 0x83, 0x2f, 0x60, 0x19, 0xdc, 0x91, 0x7e, 0x0a, 0xbe, 0x9f, 0xd6, 0xc1, 0x67, 0xec, 0x7f, 0x01, 0x68, - 0x55, 0x60, 0x40, 0xf9, 0x70, 0xd1, 0xb0, 0x12, 0xe2, 0x12, 0x15, 0x66, 0x15, 0xe7, 0x8f, 0xeb, 0xbc, 0x6e, 0x5a, - 0x96, 0x18, 0x94, 0x9f, 0xba, 0x86, 0x1b, 0xdf, 0x59, 0xc8, 0x1f, 0xdf, 0x7f, 0x09, 0xba, 0x9d, 0x48, 0xbb, 0xb5, - 0x55, 0x6c, 0x90, 0x85, 0x86, 0xf7, 0xc2, 0xa6, 0xd0, 0x16, 0x2f, 0x02, 0x14, 0xea, 0x3b, 0x16, 0xe3, 0x6d, 0x11, - 0x2a, 0xc3, 0x2f, 0x58, 0x30, 0x05, 0x0c, 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x1d, 0x36, 0x9a, 0x62, 0xd7, 0x42, 0x18, - 0x7c, 0x39, 0xa8, 0x4a, 0xc9, 0x8b, 0x75, 0xb2, 0xbd, 0x38, 0x87, 0xef, 0xaf, 0xe3, 0x02, 0xa8, 0x83, 0xe8, 0x6b, - 0x2a, 0x8b, 0x0d, 0xe4, 0xe2, 0xa6, 0xac, 0xf5, 0x8a, 0x86, 0xc3, 0x1b, 0xbb, 0xf0, 0xba, 0x02, 0x1f, 0x47, 0xe9, - 0x30, 0x11, 0x93, 0x98, 0x49, 0x95, 0x2b, 0x72, 0x6d, 0x74, 0x2f, 0x6d, 0xd1, 0xbc, 0x14, 0x12, 0xbc, 0x22, 0xf0, - 0x82, 0xd0, 0x57, 0xfa, 0x72, 0xb5, 0x81, 0x82, 0x47, 0xed, 0x8b, 0x8b, 0x60, 0x62, 0xe2, 0x71, 0x43, 0x6a, 0xfa, - 0x75, 0x38, 0xb5, 0xb2, 0x58, 0x72, 0xf8, 0x75, 0xce, 0xd8, 0x82, 0x02, 0x20, 0x3e, 0x79, 0xb4, 0xde, 0x4d, 0x7a, - 0xa3, 0xb4, 0x83, 0xd2, 0x08, 0xf1, 0x5d, 0x85, 0xaf, 0x3b, 0x57, 0x7c, 0xe5, 0xaa, 0x7b, 0x5f, 0x57, 0xdc, 0xb8, - 0x60, 0xf4, 0x92, 0x4f, 0x92, 0x85, 0x6b, 0x37, 0x74, 0x57, 0xe7, 0x3b, 0xef, 0x0b, 0x99, 0xb7, 0x70, 0x05, 0x76, - 0xfe, 0x15, 0x77, 0x5e, 0x7a, 0x1f, 0x8c, 0x13, 0xe5, 0xef, 0xcd, 0x23, 0x5e, 0x39, 0xcc, 0xaa, 0x93, 0xe4, 0xef, - 0x7b, 0xdf, 0x07, 0xeb, 0x5b, 0x1a, 0x27, 0xc8, 0x6d, 0x75, 0x82, 0x4c, 0x94, 0x1b, 0xe9, 0x0d, 0xb7, 0x7f, 0x57, - 0x81, 0x20, 0x4e, 0xc5, 0xf4, 0x51, 0x39, 0xae, 0x1f, 0x2d, 0x50, 0xa9, 0x88, 0xf8, 0x5c, 0xe5, 0xae, 0xac, 0x4d, - 0x0d, 0xf5, 0x98, 0x4e, 0x66, 0xa1, 0x69, 0x56, 0xe4, 0x52, 0x2e, 0x7a, 0x8c, 0x5c, 0xb3, 0x53, 0x6d, 0x7e, 0x77, - 0xed, 0x21, 0x1d, 0xc7, 0xfb, 0x9e, 0xb5, 0x5a, 0x70, 0xbf, 0xab, 0x28, 0xbc, 0xeb, 0xc5, 0x46, 0x2a, 0x43, 0xcd, - 0x7a, 0x14, 0x7d, 0x1c, 0x77, 0x31, 0x97, 0x47, 0xd9, 0x9f, 0x35, 0x00, 0x4c, 0x47, 0x58, 0x74, 0x37, 0x3d, 0x63, - 0x4f, 0xa0, 0xa7, 0x27, 0x32, 0x48, 0xf4, 0x56, 0xe7, 0xab, 0x56, 0x89, 0xa5, 0x2b, 0x08, 0xec, 0xde, 0x90, 0xb1, - 0x2a, 0x69, 0xb7, 0x5c, 0xbf, 0x9c, 0xe7, 0xf3, 0x9c, 0x2f, 0xe5, 0xf9, 0xd4, 0x2c, 0xba, 0x8d, 0xa6, 0x7b, 0x73, - 0x6a, 0xa8, 0x98, 0x6b, 0x75, 0x93, 0xdf, 0x30, 0x5d, 0x0b, 0x43, 0x2d, 0x82, 0xcc, 0x6a, 0x57, 0xbd, 0x28, 0xcb, - 0x51, 0x3d, 0x93, 0x63, 0x24, 0x7c, 0x53, 0xe9, 0x0e, 0xd1, 0x0d, 0x53, 0x35, 0xd3, 0x77, 0x0b, 0xdb, 0x42, 0xb6, - 0x79, 0x79, 0x35, 0xcc, 0x81, 0xd2, 0x72, 0x7f, 0x99, 0x30, 0x7c, 0x77, 0x7d, 0xfd, 0x9d, 0x90, 0x53, 0x55, 0x47, - 0x6f, 0xfe, 0x5a, 0xf7, 0x0c, 0x46, 0xa5, 0x72, 0x22, 0x4e, 0xf9, 0xea, 0xc1, 0x17, 0x77, 0xaf, 0x80, 0xe5, 0x14, - 0xb0, 0x3b, 0xe5, 0xce, 0xc2, 0x50, 0xd5, 0x06, 0xfe, 0x62, 0xf5, 0x60, 0xab, 0xf6, 0xf0, 0x17, 0xbd, 0x2f, 0x82, - 0x1b, 0x1b, 0x1b, 0xdb, 0x78, 0xb7, 0x96, 0x08, 0xf2, 0x16, 0x0f, 0xf4, 0xf1, 0xea, 0xa3, 0xa0, 0xe5, 0x0a, 0xb1, - 0xcd, 0x7a, 0x0e, 0x85, 0xad, 0x41, 0xbe, 0x49, 0x99, 0x34, 0x98, 0x15, 0x3c, 0x9b, 0xc8, 0x19, 0x0a, 0x79, 0xcd, - 0xc7, 0x41, 0xdb, 0x11, 0xfe, 0x0f, 0x9c, 0xda, 0xf1, 0xf2, 0xfc, 0x13, 0xf4, 0x01, 0x4f, 0x57, 0x4a, 0x53, 0x8a, - 0x53, 0xaa, 0xa0, 0xce, 0x72, 0x9d, 0x07, 0x23, 0xc5, 0xc5, 0x18, 0x16, 0x17, 0x5c, 0x96, 0x1b, 0x67, 0x23, 0xa7, - 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab, 0x96, 0xde, 0x2b, 0x7d, 0xba, 0x6d, 0x4f, 0x18, 0x1f, 0x67, - 0x43, 0x3a, 0x98, 0xf1, 0x71, 0x22, 0xbc, 0x3e, 0x31, 0xd4, 0x77, 0x8b, 0xc0, 0x74, 0x73, 0x6c, 0xf2, 0xc3, 0xf1, - 0x7a, 0xb3, 0x59, 0xe3, 0xf6, 0xde, 0x39, 0x9f, 0x9c, 0x79, 0x89, 0x11, 0x95, 0xb9, 0x86, 0x07, 0xb4, 0x42, 0xbc, - 0x78, 0xcf, 0x04, 0xc6, 0x65, 0x57, 0x24, 0xb5, 0xdd, 0x40, 0xe0, 0x62, 0x8f, 0x62, 0x96, 0x0c, 0x6d, 0x0f, 0xca, - 0x03, 0x7d, 0x31, 0x9a, 0x6e, 0x01, 0xd3, 0xf2, 0xda, 0xd9, 0x45, 0x6a, 0x7b, 0xd5, 0x54, 0x01, 0xcc, 0x92, 0xe5, - 0xf1, 0x19, 0xb2, 0xee, 0x57, 0xd0, 0x45, 0x0c, 0x18, 0x1b, 0x57, 0xe6, 0xdc, 0xf9, 0xaa, 0x15, 0xf1, 0x8d, 0x26, - 0xd2, 0xa4, 0x3e, 0xa2, 0xbe, 0xfd, 0xb0, 0x56, 0x57, 0x39, 0x48, 0xe0, 0x1e, 0x79, 0x77, 0xc4, 0xa5, 0xa3, 0xcf, - 0x2c, 0x36, 0xab, 0xf4, 0x2d, 0x75, 0x2d, 0x6e, 0x31, 0xec, 0x15, 0xf7, 0xc0, 0xfe, 0xc0, 0xb8, 0x45, 0x2c, 0xe2, - 0xed, 0xac, 0x96, 0xc2, 0xba, 0x30, 0x47, 0x8e, 0xb1, 0xf6, 0xe0, 0x15, 0xaf, 0xd6, 0x0c, 0xcc, 0x30, 0xe3, 0x8c, - 0xe4, 0x8d, 0x71, 0xaf, 0x6a, 0xd3, 0x91, 0xab, 0x00, 0xa2, 0x6f, 0x4e, 0x97, 0xe4, 0xf0, 0x4a, 0x96, 0xab, 0xce, - 0x90, 0x7f, 0x86, 0x75, 0xd6, 0x8b, 0x13, 0x70, 0x93, 0xa6, 0xac, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, - 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0x85, 0x91, 0xc0, 0x11, 0xfb, 0xc6, 0x32, 0x2c, 0x26, 0x6c, 0xc4, 0x44, 0x1a, 0x95, - 0x52, 0xc2, 0x7a, 0x72, 0xa9, 0x25, 0x7f, 0x99, 0xcb, 0xab, 0x2f, 0xb7, 0x09, 0x0e, 0x28, 0x6a, 0x60, 0x39, 0x34, - 0x8e, 0x5b, 0x06, 0x12, 0xb1, 0x18, 0x10, 0xa3, 0x56, 0xe5, 0x72, 0x32, 0xaa, 0x93, 0xfa, 0x0a, 0xb9, 0x50, 0x91, - 0x07, 0xb7, 0x04, 0x4a, 0xfe, 0x02, 0x53, 0x07, 0xd3, 0x52, 0xbb, 0x69, 0xb1, 0x49, 0xf2, 0x8e, 0x19, 0x90, 0x5c, - 0x7d, 0x0d, 0x0f, 0x8d, 0x5f, 0x86, 0x37, 0x14, 0x3d, 0x1d, 0x23, 0xe4, 0xb4, 0x34, 0xe6, 0xd2, 0x7f, 0x23, 0xcf, - 0xbe, 0x24, 0x60, 0x3f, 0x83, 0x98, 0x32, 0x70, 0x89, 0x8d, 0x0b, 0x92, 0xf2, 0x5a, 0x9e, 0xb2, 0xfb, 0x16, 0x94, - 0xef, 0x92, 0x49, 0x57, 0xa9, 0xac, 0x35, 0x56, 0xdd, 0xcf, 0x33, 0x96, 0x5f, 0x1d, 0x30, 0xcc, 0x4d, 0x46, 0x83, - 0x6c, 0xc9, 0xcc, 0xa6, 0xfc, 0x6a, 0xef, 0xc6, 0xb7, 0x3c, 0x94, 0x74, 0xa8, 0x56, 0xe9, 0xe6, 0xa5, 0x1b, 0x8e, - 0xf1, 0xc2, 0x0d, 0xc7, 0xb8, 0x43, 0xe7, 0xca, 0x15, 0xa9, 0x75, 0xfe, 0xfb, 0x52, 0xf8, 0x49, 0xec, 0xb5, 0xbe, - 0xde, 0x75, 0xfd, 0x95, 0xe9, 0xe9, 0x37, 0xa0, 0x6a, 0x64, 0x09, 0xdd, 0x84, 0x2a, 0x26, 0x23, 0x51, 0x62, 0xba, - 0x4a, 0x79, 0xd4, 0xd7, 0x88, 0x0b, 0x10, 0x37, 0x94, 0xbf, 0xf8, 0x97, 0xf0, 0xe2, 0x24, 0x40, 0x23, 0x6a, 0x3e, - 0xca, 0x52, 0xde, 0x18, 0x45, 0x93, 0x38, 0xb9, 0x0a, 0x66, 0x71, 0x63, 0x92, 0xa5, 0x59, 0x31, 0x05, 0xae, 0xf4, - 0x8a, 0x2b, 0xb0, 0xe1, 0x27, 0x8d, 0x59, 0xec, 0xbd, 0x64, 0xc9, 0x39, 0xe3, 0xf1, 0x20, 0xf2, 0xec, 0xfd, 0x1c, - 0xc4, 0x83, 0xf5, 0x36, 0xca, 0xf3, 0xec, 0xc2, 0xf6, 0x3e, 0x64, 0xa7, 0xc0, 0xb4, 0xde, 0xbb, 0xcb, 0xab, 0x33, - 0x96, 0x7a, 0x1f, 0x4f, 0x67, 0x29, 0x9f, 0x79, 0x45, 0x94, 0x16, 0x8d, 0x82, 0xe5, 0xf1, 0x08, 0xd4, 0x44, 0x92, - 0xe5, 0x0d, 0xcc, 0x7f, 0x9e, 0xb0, 0x20, 0x89, 0xcf, 0xc6, 0xdc, 0x1a, 0x46, 0xf9, 0xa7, 0x4e, 0xa3, 0x31, 0xcd, - 0xe3, 0x49, 0x94, 0x5f, 0x35, 0xa8, 0x45, 0x70, 0xaf, 0xb9, 0x1b, 0x7d, 0x36, 0x7a, 0xd0, 0xe1, 0x39, 0xf4, 0x8d, - 0x91, 0x8a, 0x01, 0x08, 0x1f, 0x6b, 0xf7, 0x61, 0x73, 0x52, 0x6c, 0x88, 0x13, 0xa5, 0x28, 0xe5, 0xe5, 0x89, 0x77, - 0x01, 0xb6, 0xed, 0x89, 0x7f, 0xca, 0x53, 0x0f, 0x7c, 0x39, 0x9e, 0xa5, 0xf3, 0xc1, 0x2c, 0x2f, 0x60, 0x80, 0x69, - 0x16, 0xa7, 0x9c, 0xe5, 0x9d, 0xd3, 0x2c, 0x07, 0xb2, 0x35, 0xf2, 0x68, 0x18, 0xcf, 0x8a, 0xe0, 0xc1, 0xf4, 0xb2, - 0x83, 0xb6, 0xc2, 0x59, 0x9e, 0xcd, 0xd2, 0xa1, 0x9c, 0x2b, 0x4e, 0x61, 0x63, 0xc4, 0xdc, 0xac, 0xa0, 0x37, 0xa1, - 0x00, 0x7c, 0x29, 0x8b, 0xf2, 0xc6, 0x19, 0x76, 0x46, 0x43, 0xbf, 0x39, 0x64, 0x67, 0x5e, 0x7e, 0x76, 0x1a, 0x39, - 0xad, 0xf6, 0x63, 0x4f, 0xfd, 0xf9, 0x0f, 0x5d, 0x30, 0xdc, 0x57, 0x16, 0xb7, 0x9a, 0xcd, 0xbf, 0x71, 0x3b, 0x0b, - 0xb3, 0x10, 0x40, 0x41, 0x6b, 0x7a, 0x69, 0x15, 0x59, 0x02, 0xeb, 0xb3, 0xaa, 0x67, 0x67, 0x0a, 0x7e, 0x53, 0x9c, - 0x9e, 0x05, 0xed, 0xe9, 0x65, 0x89, 0xd8, 0x05, 0x22, 0x21, 0x53, 0x22, 0x29, 0x9f, 0xe6, 0xbf, 0x15, 0xe2, 0x27, - 0xab, 0x21, 0x6e, 0x2b, 0x88, 0x2b, 0xaa, 0x37, 0x86, 0xb0, 0x0f, 0x88, 0xfc, 0xad, 0x42, 0x00, 0x32, 0x06, 0x27, - 0x30, 0x57, 0x70, 0xd0, 0xc3, 0x6f, 0x06, 0xa3, 0xbd, 0x1a, 0x8c, 0x27, 0xb7, 0x81, 0x91, 0xa7, 0xc3, 0x79, 0x7d, - 0x5d, 0x5b, 0xe0, 0x9c, 0x76, 0xc6, 0x0c, 0xf9, 0x29, 0x68, 0xe3, 0xf7, 0x8b, 0x78, 0xc8, 0xc7, 0xe2, 0x2b, 0xb1, - 0xf3, 0x85, 0xa8, 0x7b, 0xd8, 0x6c, 0x8a, 0xe7, 0x02, 0x14, 0x5a, 0xd0, 0xf2, 0xb1, 0x01, 0x30, 0xd1, 0xe7, 0xeb, - 0x5e, 0x62, 0xf3, 0xed, 0xad, 0x6f, 0xaa, 0xf1, 0xb8, 0xca, 0x1b, 0x14, 0x2a, 0x42, 0xbd, 0xb3, 0x05, 0x33, 0xde, - 0x8a, 0x6e, 0x4b, 0x1f, 0x54, 0xf5, 0xbe, 0xe5, 0xa4, 0xf5, 0x02, 0xe6, 0x99, 0xb9, 0x40, 0x9d, 0xac, 0x8b, 0x21, - 0xa9, 0x46, 0xc3, 0x05, 0xbd, 0xc1, 0x31, 0x84, 0x44, 0x07, 0x82, 0x4e, 0xd1, 0xcb, 0xe9, 0x9d, 0x1a, 0xa9, 0x1b, - 0xe4, 0x4e, 0xea, 0xc2, 0x96, 0x4f, 0xb5, 0x5c, 0x2f, 0xb6, 0xb6, 0xc0, 0xcb, 0xfe, 0x9c, 0xcb, 0x06, 0x20, 0xbd, - 0x2b, 0x49, 0x6b, 0xbc, 0x87, 0x44, 0xb9, 0x7c, 0xd9, 0x80, 0x28, 0x07, 0xbe, 0x3e, 0x1f, 0xa3, 0xdf, 0xad, 0xaf, - 0xae, 0x1b, 0x29, 0x35, 0x3b, 0xb6, 0xdb, 0xe3, 0x3a, 0x2b, 0x0b, 0xb3, 0xcf, 0x78, 0x89, 0xa3, 0x7c, 0xc9, 0x43, - 0x1c, 0xd1, 0x7b, 0x15, 0x0a, 0x37, 0x4d, 0x39, 0x69, 0xa3, 0xbb, 0x3a, 0x69, 0xf0, 0x35, 0xa6, 0xcc, 0x67, 0x15, - 0x27, 0x07, 0x37, 0xe6, 0x78, 0x20, 0xae, 0x20, 0x16, 0x55, 0x96, 0x7d, 0x44, 0xd0, 0x0b, 0xbf, 0x0b, 0x94, 0x14, - 0x46, 0x2e, 0xbf, 0xe2, 0xbf, 0xc3, 0xe3, 0x70, 0x34, 0xfa, 0x45, 0x36, 0xcb, 0x07, 0x78, 0x39, 0x60, 0x45, 0x28, - 0xc2, 0x26, 0x4b, 0xc0, 0xf6, 0xb8, 0x56, 0x40, 0x0c, 0xf3, 0x2c, 0xcc, 0xb7, 0x2f, 0x30, 0x3a, 0x9d, 0x11, 0x97, - 0x1f, 0x64, 0xf8, 0x45, 0xa1, 0x84, 0x3a, 0x75, 0x48, 0x89, 0x78, 0x74, 0x31, 0xd4, 0x9f, 0xa5, 0x31, 0x88, 0xe0, - 0xe3, 0x78, 0x48, 0x17, 0x62, 0xe2, 0x21, 0x9d, 0x90, 0x34, 0x28, 0x23, 0x0a, 0x43, 0xee, 0x50, 0x20, 0x17, 0x06, - 0xbf, 0xcb, 0x0c, 0x1b, 0xbb, 0x61, 0xe3, 0x29, 0x87, 0xa1, 0xc3, 0x87, 0xd9, 0x24, 0x8a, 0xd3, 0x00, 0x5f, 0x5c, - 0xe2, 0xe9, 0x11, 0x03, 0xec, 0xe2, 0xc1, 0xa7, 0x5a, 0xa3, 0x96, 0xeb, 0xff, 0x04, 0x02, 0x8e, 0xfa, 0x63, 0x32, - 0x0b, 0x91, 0x55, 0x10, 0x31, 0x54, 0x64, 0xde, 0x57, 0x7a, 0xde, 0x33, 0xab, 0x55, 0xcc, 0xb4, 0xbe, 0x0e, 0xcd, - 0x85, 0xe5, 0xd2, 0x67, 0xd8, 0xf5, 0x52, 0x10, 0xac, 0x5c, 0xe7, 0xd1, 0x53, 0x10, 0x67, 0x8f, 0xd1, 0x47, 0xaf, - 0xd1, 0x0a, 0x5a, 0xda, 0x2f, 0xaf, 0x5d, 0xb5, 0x15, 0x89, 0x3a, 0xf2, 0xba, 0x26, 0xe1, 0xa1, 0xbf, 0x0b, 0x5c, - 0xab, 0x67, 0x8d, 0xaf, 0x27, 0x37, 0x1d, 0x46, 0xa7, 0xce, 0x52, 0xa7, 0x06, 0x04, 0x1d, 0x74, 0xac, 0x99, 0xca, - 0x2d, 0x2b, 0xbc, 0xb5, 0xf1, 0x67, 0x0b, 0xcd, 0x89, 0xaf, 0x1e, 0x90, 0x33, 0xd2, 0x2b, 0x9e, 0x56, 0xf0, 0x5d, - 0x29, 0x09, 0xb2, 0x78, 0x21, 0x7f, 0xa1, 0x99, 0x00, 0xe5, 0x4a, 0x1f, 0x64, 0x2f, 0xd4, 0x8a, 0x47, 0x26, 0x22, - 0xde, 0xab, 0x9b, 0x50, 0xd6, 0xd8, 0x32, 0x5c, 0xe8, 0x8b, 0x16, 0x5c, 0xc1, 0x8f, 0x06, 0xd3, 0x88, 0xe1, 0xbd, - 0x94, 0x93, 0xcd, 0xf9, 0x97, 0xbc, 0xdc, 0xd9, 0x9c, 0xab, 0x86, 0xe2, 0x7b, 0x3c, 0xc4, 0x4f, 0x06, 0xf2, 0x6b, - 0x2e, 0xac, 0xc7, 0xc0, 0x7e, 0xff, 0xee, 0xe0, 0xd0, 0xf6, 0x4e, 0xb3, 0xe1, 0x55, 0x60, 0xc3, 0xee, 0x64, 0x76, - 0xe9, 0xfa, 0x7c, 0xcc, 0x52, 0x47, 0xb1, 0x78, 0x96, 0x30, 0x90, 0x08, 0x67, 0xe2, 0xb2, 0xe3, 0xa2, 0xe7, 0x3b, - 0x3c, 0xd9, 0xa3, 0xb7, 0x21, 0x75, 0xf7, 0xb8, 0x78, 0x51, 0x18, 0xcf, 0xf1, 0x6b, 0x17, 0x63, 0xff, 0x7b, 0x3b, - 0xf0, 0x05, 0x1f, 0x0e, 0x70, 0xcf, 0xd0, 0xd3, 0xe6, 0x7c, 0x89, 0x93, 0x7a, 0x38, 0xc4, 0xb8, 0x2b, 0x50, 0x28, - 0xa8, 0xd5, 0x49, 0x30, 0x3c, 0x39, 0x29, 0xe1, 0x2b, 0x8c, 0xb5, 0xa3, 0xc6, 0x45, 0x08, 0x55, 0x7f, 0xcd, 0x5d, - 0xf2, 0x75, 0x3b, 0x38, 0x04, 0xce, 0x3b, 0xc4, 0x06, 0xc4, 0x5d, 0xd8, 0x7b, 0xa8, 0x4b, 0x68, 0xd3, 0x8a, 0xa2, - 0x75, 0x10, 0x88, 0x86, 0x15, 0xd3, 0x8b, 0x10, 0x61, 0xb5, 0xba, 0x0a, 0xa4, 0xa1, 0x09, 0xdd, 0x89, 0x8b, 0x9f, - 0x04, 0x19, 0x7c, 0x12, 0x1d, 0x4e, 0xcc, 0x37, 0x84, 0x88, 0xcb, 0xfc, 0x9a, 0x5a, 0x47, 0x7f, 0x01, 0xdb, 0xc3, - 0xbb, 0x38, 0xa1, 0x96, 0x4a, 0x1d, 0xa1, 0x9d, 0x84, 0x6a, 0xbb, 0xa9, 0xec, 0x0e, 0xd0, 0xfd, 0x49, 0x34, 0x2d, - 0x58, 0xa0, 0xbe, 0x48, 0xcd, 0x84, 0x0a, 0x6e, 0xd9, 0x14, 0x90, 0x79, 0x31, 0xcf, 0xd0, 0x60, 0x58, 0xb6, 0x53, - 0x40, 0xf4, 0x39, 0x8d, 0xc6, 0xa0, 0x71, 0x7a, 0xe6, 0x96, 0x7c, 0x3c, 0x37, 0xf5, 0xda, 0x23, 0xd0, 0x6b, 0x98, - 0x93, 0xd7, 0x00, 0x4f, 0xed, 0x2c, 0x0d, 0x12, 0x36, 0xe2, 0x25, 0xc7, 0x4b, 0x5f, 0x73, 0x65, 0x48, 0xf8, 0xed, - 0x87, 0xa0, 0xeb, 0x2c, 0x1f, 0xff, 0xbd, 0x79, 0x62, 0xe8, 0x18, 0xa4, 0xa0, 0x9b, 0x28, 0x0b, 0x14, 0x33, 0xec, - 0x01, 0x5c, 0xf3, 0x79, 0x6e, 0x4c, 0x34, 0x60, 0x68, 0x64, 0x95, 0x1c, 0x64, 0xf2, 0xd8, 0xe3, 0xb9, 0xd9, 0x2e, - 0x75, 0xe7, 0x4b, 0x18, 0x2c, 0xeb, 0xfa, 0x5d, 0xb7, 0x2c, 0xc8, 0x64, 0x5d, 0x6e, 0xac, 0x0c, 0xa6, 0xfa, 0xd3, - 0x12, 0xf9, 0x0c, 0xd3, 0xae, 0x14, 0xc1, 0xd2, 0xb9, 0xe8, 0x71, 0x17, 0x62, 0xd6, 0x8c, 0x4e, 0xcf, 0xec, 0xe1, - 0x96, 0x71, 0x3a, 0x9d, 0xf1, 0x23, 0x0a, 0xd4, 0xe6, 0x78, 0x9d, 0xa0, 0x3f, 0x17, 0x73, 0x83, 0x17, 0x3c, 0x70, - 0x10, 0x00, 0xab, 0x61, 0x3d, 0x01, 0x6a, 0xba, 0xca, 0xf0, 0xf0, 0x1f, 0x23, 0x71, 0x4b, 0x9f, 0x5a, 0xaf, 0xa0, - 0xd2, 0x09, 0x58, 0xdd, 0x1d, 0xce, 0x9d, 0xa3, 0x37, 0x8e, 0xdb, 0xf7, 0x5e, 0x19, 0x2f, 0x2f, 0xb1, 0xd5, 0x1e, - 0xb0, 0x3d, 0xa4, 0xf7, 0xca, 0x26, 0x26, 0x93, 0x53, 0xb3, 0x57, 0x21, 0x36, 0x7c, 0xeb, 0xd8, 0xac, 0x98, 0x36, - 0x84, 0x48, 0x6a, 0x10, 0x33, 0xda, 0xd8, 0x55, 0x05, 0x56, 0xbf, 0xe2, 0x73, 0x92, 0x36, 0x5c, 0xbf, 0x29, 0xe4, - 0x88, 0xf7, 0xcd, 0x5b, 0x2d, 0xb5, 0x80, 0x3a, 0xd4, 0xb9, 0x86, 0xe4, 0x83, 0x47, 0xf9, 0xd6, 0x1b, 0x25, 0x37, - 0x4e, 0xf6, 0xeb, 0x92, 0x0c, 0xf6, 0x59, 0xa9, 0xdf, 0xa8, 0x06, 0x5a, 0x18, 0xe7, 0x87, 0x8d, 0x24, 0xf7, 0xdd, - 0x53, 0xb2, 0x12, 0x55, 0x1c, 0x9c, 0xae, 0x2c, 0xaa, 0x13, 0x0d, 0xa1, 0x50, 0x63, 0x3c, 0x77, 0xad, 0x25, 0xdd, - 0x76, 0x2a, 0x59, 0x24, 0x6c, 0x4c, 0x8b, 0xf0, 0x08, 0x6d, 0x30, 0xfa, 0x6c, 0xeb, 0xcf, 0x03, 0x50, 0x7f, 0x9f, - 0x42, 0x7b, 0x73, 0xee, 0xb8, 0xab, 0xef, 0xcd, 0x29, 0xcf, 0x50, 0x49, 0x61, 0x23, 0x63, 0xb1, 0x26, 0x5c, 0xd1, - 0x41, 0xb5, 0xbb, 0x28, 0x3e, 0xf7, 0x76, 0xc4, 0x44, 0xb0, 0xdb, 0x8f, 0xe5, 0x8b, 0x9e, 0xb8, 0x29, 0x12, 0x91, - 0xbc, 0xa2, 0xdc, 0x22, 0x36, 0x09, 0xed, 0x5b, 0x79, 0xc7, 0xb6, 0x84, 0x94, 0x42, 0x40, 0x95, 0xc0, 0x02, 0xe0, - 0x75, 0x19, 0x93, 0xb0, 0xc7, 0x92, 0x0c, 0x36, 0xce, 0x05, 0x8a, 0x00, 0x03, 0x47, 0x3c, 0x8a, 0x13, 0xd1, 0x45, - 0x06, 0xf6, 0x94, 0x03, 0xa8, 0x31, 0xc2, 0x23, 0xf5, 0x3a, 0x2e, 0x75, 0x12, 0x12, 0x66, 0x7b, 0x3b, 0x15, 0xdc, - 0x84, 0x19, 0xed, 0x32, 0xf3, 0x00, 0xab, 0xc2, 0x50, 0xd4, 0x01, 0x71, 0xe9, 0xda, 0x0c, 0x02, 0x58, 0xa8, 0x60, - 0x87, 0x97, 0xaa, 0x2b, 0x2c, 0x02, 0x96, 0x1c, 0x13, 0x85, 0xc1, 0xc8, 0x63, 0x5c, 0x13, 0x36, 0x17, 0xd9, 0x8f, - 0x0a, 0xda, 0x74, 0x09, 0xda, 0xb4, 0x0e, 0xed, 0x09, 0x12, 0xbd, 0xb7, 0x39, 0x8f, 0xcb, 0x10, 0xbe, 0xa5, 0x83, - 0x6c, 0xc8, 0x3e, 0x7e, 0x78, 0x85, 0x77, 0x00, 0xa1, 0x3d, 0x38, 0x0b, 0x99, 0x5b, 0x9e, 0xc8, 0xc5, 0x31, 0x75, - 0x82, 0xd8, 0xdb, 0x16, 0xcd, 0x45, 0x74, 0x05, 0x8a, 0xf6, 0x04, 0xe4, 0x6c, 0x48, 0x05, 0x61, 0x98, 0x53, 0x2f, - 0x0e, 0x4b, 0x2a, 0x5a, 0x0b, 0x99, 0x2e, 0x1a, 0x21, 0x11, 0x68, 0x67, 0x56, 0x34, 0xc0, 0x9c, 0x59, 0x93, 0x0e, - 0xc3, 0xf8, 0x5c, 0x73, 0x1b, 0x5d, 0x20, 0xea, 0xee, 0x01, 0x43, 0xb3, 0x04, 0xc6, 0xcc, 0xaf, 0xaf, 0x9b, 0x30, - 0x94, 0x78, 0xb4, 0xf6, 0x48, 0x36, 0x88, 0x77, 0x61, 0xc2, 0xcc, 0x2d, 0x4c, 0x4f, 0xc2, 0xab, 0x7a, 0x3d, 0x95, - 0x6f, 0x13, 0xc8, 0x01, 0x00, 0x46, 0x3a, 0xea, 0x27, 0x3e, 0xd0, 0xe6, 0x0d, 0x94, 0xc6, 0xc3, 0xe5, 0x32, 0xb0, - 0x4a, 0xa7, 0x58, 0x9a, 0x5d, 0x5f, 0xb7, 0xe0, 0x71, 0x12, 0xa7, 0xf8, 0x04, 0x33, 0xd3, 0x0d, 0x38, 0x78, 0x04, - 0xd3, 0x1c, 0xd8, 0x16, 0x6a, 0xa2, 0x4b, 0xac, 0x49, 0x55, 0x4d, 0x74, 0x09, 0xf2, 0x48, 0x54, 0x69, 0xf2, 0x14, - 0xc8, 0x70, 0xff, 0x1f, 0x16, 0x34, 0x93, 0x8b, 0x67, 0x69, 0xd2, 0x01, 0x98, 0x20, 0x2d, 0x35, 0xf1, 0xf6, 0x76, - 0x80, 0xcc, 0xb0, 0x18, 0xd2, 0xfa, 0x91, 0x3b, 0xae, 0x7a, 0x8f, 0x91, 0x90, 0x64, 0x6e, 0x2d, 0x0d, 0x81, 0x8a, - 0xd0, 0x1a, 0x04, 0xdf, 0x62, 0x78, 0x4c, 0x9b, 0x03, 0xf4, 0xbc, 0xd4, 0xfe, 0x0b, 0xb2, 0xa6, 0xea, 0xe0, 0xd9, - 0x7f, 0xfd, 0xc7, 0xbf, 0xb3, 0x3d, 0xb1, 0xb9, 0xb2, 0xd1, 0x08, 0x4c, 0x65, 0xeb, 0x0e, 0x7d, 0xfe, 0xd7, 0xdf, - 0xff, 0xdf, 0xff, 0xf3, 0x5f, 0x75, 0xb7, 0x14, 0x7a, 0x9d, 0xc8, 0x83, 0x3f, 0x25, 0x1d, 0x0c, 0x30, 0x15, 0x1a, - 0xa3, 0x28, 0x5d, 0x87, 0xc3, 0x91, 0x89, 0x43, 0x31, 0x65, 0x6c, 0xe8, 0xd9, 0x96, 0xed, 0x2d, 0x95, 0x1e, 0x27, - 0xec, 0x9c, 0xc9, 0xb7, 0x9e, 0xad, 0x9a, 0x6a, 0x45, 0x8f, 0x01, 0x28, 0x34, 0x2e, 0xcf, 0x3f, 0x25, 0x6f, 0x9b, - 0xa8, 0x48, 0xa9, 0x52, 0xeb, 0x87, 0xb4, 0xab, 0x8b, 0x0b, 0xcf, 0x36, 0xa6, 0x5f, 0x0b, 0x57, 0x6f, 0x4d, 0x79, - 0xd0, 0xf4, 0x9a, 0xeb, 0x20, 0xf3, 0xc0, 0x8f, 0xb4, 0xed, 0xbe, 0xa2, 0x11, 0x85, 0x7b, 0xcc, 0x0b, 0xec, 0xeb, - 0x68, 0x75, 0x2b, 0xf6, 0xa7, 0x39, 0x0e, 0x95, 0xb2, 0xa2, 0xb8, 0x05, 0x79, 0x58, 0x3e, 0xcf, 0xae, 0x5a, 0xdb, - 0x6b, 0x46, 0x01, 0x14, 0xda, 0x0f, 0x1f, 0x0a, 0x70, 0x3d, 0x47, 0xbb, 0x90, 0x66, 0x63, 0x36, 0x1a, 0x81, 0x10, - 0x29, 0xdc, 0x2a, 0x1f, 0x74, 0x14, 0x27, 0x1c, 0xcf, 0xb3, 0xc3, 0xae, 0xfd, 0x16, 0x36, 0x06, 0x5e, 0x0f, 0x75, - 0xa5, 0x5f, 0xaf, 0x32, 0xfd, 0x94, 0xd0, 0x5d, 0x0d, 0x97, 0x18, 0xb2, 0x0e, 0x93, 0x9c, 0xe6, 0xfa, 0x5a, 0xf9, - 0xcb, 0xb5, 0xf2, 0x3a, 0x39, 0x33, 0x72, 0x88, 0x57, 0xef, 0x9b, 0xbb, 0xec, 0x8e, 0x7f, 0xfd, 0xa7, 0xbf, 0xff, - 0x6f, 0x00, 0x06, 0x8e, 0x73, 0xb7, 0xad, 0x01, 0x1d, 0xfe, 0x27, 0x74, 0x98, 0xa5, 0x77, 0xef, 0xf2, 0xd7, 0xff, - 0xf2, 0xdf, 0xa1, 0x07, 0x5d, 0x60, 0x86, 0x7d, 0xa4, 0x40, 0x1f, 0x60, 0xd8, 0xe8, 0x77, 0xc1, 0x5e, 0x1b, 0xf7, - 0x2e, 0x70, 0xfc, 0x03, 0xa2, 0x5a, 0xf0, 0x6c, 0x7a, 0x57, 0xb8, 0x11, 0xd3, 0x41, 0x92, 0x15, 0xcc, 0x04, 0x5c, - 0x58, 0x0a, 0xbf, 0x0f, 0x72, 0x82, 0x64, 0x0a, 0x12, 0xb4, 0xb0, 0xcc, 0xa1, 0x25, 0xaf, 0xdc, 0x28, 0x08, 0x57, - 0x32, 0x54, 0xc1, 0x38, 0x91, 0x82, 0xac, 0xb9, 0x1a, 0xd3, 0x88, 0xb2, 0x25, 0x5e, 0x22, 0xe9, 0xae, 0x25, 0x97, - 0xd0, 0x58, 0xb7, 0xcc, 0xbb, 0x62, 0x7f, 0x89, 0x69, 0xc5, 0x99, 0xd7, 0xf2, 0xf0, 0xb5, 0x12, 0x50, 0x5d, 0xc7, - 0x2b, 0x4a, 0xa3, 0xcb, 0x15, 0xa5, 0xa8, 0x04, 0x35, 0x6c, 0x60, 0xed, 0x4d, 0xc4, 0x4b, 0x2f, 0xf4, 0xeb, 0x2e, - 0x6a, 0xd0, 0x91, 0x2a, 0xc3, 0x53, 0xfc, 0xfa, 0x2b, 0x00, 0xe4, 0x50, 0x42, 0xad, 0x1d, 0xe3, 0xbd, 0x1a, 0xbc, - 0x45, 0x3d, 0xcb, 0x19, 0xec, 0x99, 0x0b, 0xf3, 0x68, 0xfe, 0xe6, 0xc6, 0x63, 0x10, 0x0f, 0x3d, 0xb0, 0x27, 0xf5, - 0xaa, 0xde, 0x38, 0x6e, 0xf9, 0x2f, 0xff, 0xec, 0xfb, 0xff, 0xf2, 0xcf, 0xb7, 0x36, 0xc5, 0x51, 0xc1, 0x65, 0xe7, - 0xd5, 0xb0, 0xeb, 0xa9, 0xbb, 0x7a, 0xa6, 0x3a, 0xb9, 0x57, 0xb7, 0x59, 0xa2, 0x3f, 0xd6, 0x2f, 0x91, 0x7f, 0xa9, - 0x50, 0x50, 0xdf, 0xfa, 0x2d, 0x80, 0x21, 0x5e, 0xb7, 0x42, 0x86, 0x8d, 0x7e, 0x17, 0x68, 0x27, 0x6e, 0x70, 0xa7, - 0x15, 0xf9, 0xed, 0x14, 0xbe, 0x0d, 0x87, 0xdf, 0x09, 0xbe, 0x48, 0x07, 0x06, 0xd0, 0x4e, 0xd4, 0x8d, 0xa9, 0x5a, - 0x57, 0xbc, 0x74, 0xd9, 0x5b, 0x2a, 0x91, 0x6a, 0x25, 0x68, 0xba, 0xdd, 0xe6, 0xd6, 0x96, 0x83, 0xdd, 0xdf, 0xe0, - 0x9b, 0x21, 0xf6, 0x4e, 0x73, 0x15, 0x03, 0xb9, 0x41, 0x34, 0xe0, 0x10, 0x75, 0xac, 0x68, 0xd0, 0x25, 0xb9, 0x80, - 0xa5, 0x98, 0x61, 0x8a, 0x60, 0x7a, 0x60, 0x0e, 0x0b, 0x7b, 0xed, 0x99, 0x70, 0x6c, 0x82, 0x45, 0xd6, 0x96, 0x0e, - 0x4f, 0x8d, 0xe8, 0x9e, 0x75, 0x48, 0xf4, 0xa2, 0xc6, 0xac, 0xb2, 0x97, 0xc9, 0x4b, 0x44, 0x03, 0xf1, 0x44, 0xbc, - 0x2b, 0xe3, 0xeb, 0x75, 0xf1, 0xf6, 0xef, 0x6f, 0x8f, 0xb7, 0xc7, 0x77, 0x8c, 0xb7, 0x7f, 0xff, 0x07, 0xc7, 0xdb, - 0xbf, 0x36, 0xe3, 0xed, 0xb8, 0x88, 0x3f, 0xdf, 0x29, 0x26, 0xae, 0x22, 0x95, 0xd9, 0x45, 0x11, 0xb6, 0xa4, 0xa5, - 0x04, 0x8e, 0x34, 0x06, 0xc4, 0xff, 0xed, 0xe3, 0xdb, 0x30, 0xd1, 0x42, 0x74, 0x9b, 0xc2, 0xd9, 0x92, 0x07, 0x99, - 0x0a, 0x26, 0x37, 0x75, 0xee, 0x77, 0xe3, 0x81, 0xba, 0xec, 0x0a, 0x2e, 0x8c, 0xab, 0x0f, 0x04, 0xda, 0x2a, 0xdc, - 0x1c, 0xd0, 0xdb, 0xaa, 0x75, 0xc7, 0xf6, 0xb6, 0x4a, 0x3a, 0x36, 0x47, 0xe8, 0xa8, 0xb3, 0x64, 0x71, 0x53, 0x72, - 0x6e, 0xff, 0xa7, 0xa3, 0x56, 0x67, 0xb7, 0x35, 0x81, 0xde, 0xc0, 0x87, 0xf0, 0xd4, 0xec, 0xec, 0xee, 0xe2, 0xd3, - 0x85, 0x7a, 0x6a, 0xe3, 0x53, 0xac, 0x9e, 0x1e, 0xe2, 0xd3, 0x40, 0x3d, 0x3d, 0xc2, 0xa7, 0xa1, 0x7a, 0x7a, 0x8c, - 0x4f, 0xe7, 0x76, 0x79, 0xc4, 0x34, 0x70, 0x8f, 0xdd, 0xbe, 0x27, 0x4c, 0x51, 0x55, 0xf6, 0xd8, 0x6b, 0x61, 0x40, - 0x3b, 0x3a, 0x0b, 0x62, 0x4f, 0x38, 0xd4, 0x41, 0xe1, 0x5d, 0x8c, 0x59, 0x1a, 0x50, 0x4e, 0xf4, 0x73, 0x7c, 0x5b, - 0x10, 0xd8, 0xc0, 0x87, 0xf1, 0x84, 0xa9, 0xd7, 0xa6, 0x2b, 0xac, 0x41, 0x25, 0x1f, 0x35, 0xfb, 0x65, 0x47, 0xaf, - 0x93, 0x88, 0x84, 0xab, 0xf4, 0x4e, 0x5a, 0xb9, 0xaa, 0x4e, 0x4c, 0xd7, 0xd0, 0x2b, 0xbc, 0x26, 0xa8, 0x6a, 0xf8, - 0x95, 0x23, 0x90, 0xcd, 0x8d, 0x4b, 0x70, 0x2c, 0x57, 0x06, 0x5a, 0x11, 0x22, 0x1d, 0x68, 0x25, 0x9c, 0xf4, 0xd3, - 0x61, 0x74, 0xa6, 0xbf, 0xbf, 0x01, 0xdb, 0x21, 0x3a, 0x93, 0x2d, 0xd7, 0x07, 0x56, 0x09, 0x44, 0x33, 0xa8, 0xaa, - 0x80, 0x40, 0xc7, 0x13, 0x97, 0x06, 0xc3, 0x04, 0x32, 0x56, 0x8a, 0xd4, 0xa9, 0x87, 0x59, 0x69, 0xfa, 0x7a, 0x11, - 0x50, 0xb4, 0x2a, 0xd8, 0x03, 0x13, 0x86, 0x4a, 0x05, 0x85, 0xa1, 0x02, 0x0b, 0x44, 0xf5, 0x9a, 0x70, 0xaa, 0x72, - 0xfd, 0xd6, 0x07, 0x55, 0x2d, 0x15, 0x4f, 0x35, 0xcf, 0xa0, 0xf5, 0x01, 0xf4, 0x72, 0x14, 0xef, 0x5e, 0x6b, 0x80, - 0xff, 0xc9, 0x18, 0xe1, 0xbd, 0xd1, 0x68, 0x74, 0x63, 0x7c, 0xf5, 0xde, 0x70, 0xc4, 0xda, 0xec, 0x61, 0x07, 0xcf, - 0x27, 0x1b, 0x32, 0x6a, 0xd7, 0x2a, 0x89, 0x76, 0xf3, 0xbb, 0x35, 0xc6, 0x00, 0x1f, 0x1f, 0xcf, 0xef, 0x1e, 0x6b, - 0x2d, 0x81, 0x2a, 0xf3, 0x09, 0x48, 0xc5, 0x38, 0x0d, 0x9a, 0xa5, 0x7f, 0x2e, 0x83, 0x93, 0xf7, 0x9e, 0x3c, 0x79, - 0x52, 0xfa, 0x43, 0xf5, 0xd4, 0x1c, 0x0e, 0x4b, 0x7f, 0x30, 0xd7, 0x68, 0x34, 0x9b, 0xa3, 0x51, 0xe9, 0xc7, 0xaa, - 0x60, 0xb7, 0x3d, 0x18, 0xee, 0xb6, 0x4b, 0xff, 0xc2, 0x68, 0x51, 0xfa, 0x4c, 0x3e, 0xe5, 0x6c, 0x58, 0x3b, 0xe4, - 0x7c, 0x0c, 0xde, 0xb6, 0x2f, 0x18, 0x6d, 0x8e, 0x86, 0xb6, 0xf8, 0x1a, 0x44, 0x33, 0x9e, 0xa1, 0x00, 0xee, 0x00, - 0x9f, 0x1f, 0x6d, 0xca, 0x6b, 0xcc, 0xe2, 0xad, 0xe4, 0x25, 0x6c, 0xa1, 0x9f, 0xcd, 0x60, 0x23, 0x32, 0x33, 0x05, - 0x19, 0x63, 0x15, 0x8b, 0xac, 0x55, 0x23, 0x67, 0x51, 0xf5, 0xcf, 0x61, 0x5c, 0xc5, 0x20, 0x51, 0xda, 0x60, 0x4b, - 0x91, 0x8c, 0xf3, 0xdd, 0x3a, 0x19, 0xff, 0xc5, 0xed, 0x32, 0xfe, 0xea, 0x6e, 0x22, 0xfe, 0x8b, 0x3f, 0x58, 0xc4, - 0x7f, 0x67, 0x8a, 0x78, 0x21, 0xc4, 0xf6, 0x79, 0x68, 0x0f, 0xc6, 0x6c, 0xf0, 0xe9, 0x34, 0xbb, 0x6c, 0xe0, 0x96, - 0xc8, 0x6d, 0x92, 0x9e, 0x93, 0xdf, 0x7a, 0x20, 0xaa, 0x06, 0x33, 0x5e, 0x71, 0x4e, 0x4a, 0xf2, 0x5d, 0x1a, 0xda, - 0xef, 0x94, 0xfd, 0x2e, 0x4a, 0x46, 0x23, 0x28, 0x1a, 0x8d, 0x6c, 0x75, 0x79, 0x03, 0xc4, 0x16, 0xb5, 0x7a, 0x5b, - 0x2b, 0xa1, 0x56, 0x9f, 0x7f, 0x6e, 0x96, 0x99, 0x05, 0x32, 0x64, 0x69, 0x86, 0x27, 0x65, 0xcd, 0x30, 0x2e, 0x70, - 0xab, 0xe1, 0x9b, 0xd7, 0x97, 0x5e, 0x69, 0x25, 0x02, 0xab, 0xcb, 0x00, 0x57, 0xf1, 0x55, 0xe3, 0xed, 0xa9, 0x55, - 0x84, 0x15, 0x16, 0x54, 0x66, 0xd6, 0x3d, 0xbd, 0x7a, 0x35, 0x74, 0xf6, 0xb9, 0x5b, 0xc6, 0xc5, 0xbb, 0x74, 0x21, - 0x63, 0x59, 0xc0, 0x18, 0x86, 0x26, 0x5a, 0x25, 0xcf, 0xce, 0xce, 0x92, 0xe5, 0x1c, 0x58, 0xd1, 0xbd, 0x57, 0xc3, - 0x37, 0x30, 0x3b, 0x4a, 0x5d, 0x46, 0x3f, 0x99, 0x21, 0x52, 0xfb, 0x28, 0x27, 0x5b, 0x1d, 0xed, 0xce, 0xa5, 0xfc, - 0x97, 0x49, 0x5f, 0x8c, 0x0e, 0x51, 0x69, 0xe0, 0x61, 0x59, 0xca, 0xcc, 0x5a, 0x20, 0xc4, 0x14, 0xdf, 0xff, 0x26, - 0x7a, 0xc6, 0xb7, 0x89, 0xf0, 0xe2, 0xc2, 0x88, 0x0b, 0xd6, 0x96, 0xab, 0x54, 0x81, 0x41, 0x11, 0xdd, 0xdb, 0xc7, - 0x10, 0xa5, 0x88, 0x11, 0x2a, 0x22, 0xda, 0x56, 0x8f, 0xbe, 0xca, 0x88, 0x65, 0x85, 0x21, 0x06, 0x33, 0xf5, 0x82, - 0xa8, 0x2a, 0x55, 0x50, 0x9a, 0x81, 0x6f, 0xaa, 0x11, 0xd4, 0xa2, 0x30, 0x1b, 0xc0, 0x9e, 0x0a, 0x31, 0x0a, 0xd3, - 0x90, 0x3c, 0xd8, 0x9c, 0x57, 0x2b, 0x0f, 0x5d, 0x25, 0xd8, 0x82, 0x79, 0x41, 0x06, 0x63, 0x87, 0xae, 0x55, 0x03, - 0x3d, 0x5d, 0x8a, 0xce, 0xdd, 0x7c, 0xee, 0x75, 0xe2, 0x17, 0x17, 0x1e, 0xfc, 0x59, 0x7f, 0x9a, 0x83, 0xd0, 0x39, - 0xfd, 0x14, 0xf3, 0x06, 0x8f, 0xa6, 0x0d, 0xb4, 0xee, 0x29, 0xc8, 0x23, 0xa5, 0x33, 0xe5, 0x6f, 0x88, 0x7b, 0x96, - 0x9d, 0x59, 0x81, 0xc7, 0x63, 0x64, 0xa3, 0x06, 0x69, 0x96, 0xb2, 0x4e, 0x3d, 0x4f, 0xc7, 0x3c, 0x6d, 0x51, 0xc4, - 0xea, 0xcf, 0x33, 0x3c, 0x4e, 0xe3, 0x57, 0x41, 0x53, 0x4a, 0xf5, 0xa6, 0x3a, 0x6a, 0x69, 0xae, 0x6c, 0x1f, 0x48, - 0xda, 0x6e, 0x93, 0xf2, 0xca, 0x97, 0x8f, 0x94, 0xd6, 0x1d, 0x09, 0xdd, 0x96, 0xb5, 0x82, 0xc1, 0x21, 0xf5, 0x67, - 0xa4, 0xfb, 0x2c, 0x16, 0x53, 0xd6, 0xca, 0x5d, 0x20, 0x0b, 0xa2, 0x11, 0xbe, 0x96, 0xf4, 0x2e, 0x2d, 0x4f, 0x29, - 0x65, 0x7c, 0x8e, 0x5a, 0x26, 0x68, 0x3d, 0x99, 0x5e, 0xde, 0x7d, 0xf8, 0x9b, 0xd1, 0x2f, 0x25, 0x8d, 0xd4, 0xcd, - 0x7f, 0xdb, 0xee, 0xe0, 0x3e, 0x48, 0xa2, 0xab, 0x20, 0x4e, 0x49, 0xe5, 0x9d, 0x62, 0x94, 0xa7, 0x33, 0xcd, 0x64, - 0xfa, 0x55, 0xce, 0x12, 0xfa, 0xed, 0x1f, 0xb9, 0x14, 0xbb, 0x8f, 0xa6, 0x97, 0x6a, 0x35, 0x5a, 0x0b, 0x69, 0x55, - 0x7f, 0x68, 0xf6, 0xd4, 0xfa, 0x74, 0xad, 0x7a, 0x06, 0xd0, 0x43, 0x80, 0x41, 0xe8, 0xd9, 0x46, 0x2e, 0xa0, 0x6a, - 0x42, 0x89, 0x91, 0x3f, 0x56, 0x0d, 0x64, 0xf9, 0xbb, 0x20, 0xb9, 0xa3, 0x82, 0x75, 0xf0, 0xfd, 0xb0, 0xf1, 0x20, - 0x4a, 0xa4, 0x2e, 0x9f, 0xc4, 0xc3, 0x61, 0xc2, 0x3a, 0x4a, 0x5d, 0x5b, 0xad, 0x47, 0x98, 0x7e, 0x65, 0x2e, 0x59, - 0x7d, 0x55, 0x0c, 0xe2, 0x69, 0x3a, 0x45, 0xa7, 0x60, 0x3e, 0xe0, 0x4b, 0x5e, 0x57, 0x92, 0x53, 0xe6, 0x25, 0x35, - 0x2b, 0xe2, 0xd1, 0xf7, 0x3a, 0x2e, 0x0f, 0xc1, 0x76, 0xa1, 0x05, 0x6f, 0x76, 0x78, 0x36, 0x0d, 0x1a, 0xbb, 0x75, - 0x44, 0xb0, 0x4a, 0xa3, 0xe0, 0xad, 0x40, 0xcb, 0x43, 0x65, 0x25, 0x04, 0xb4, 0xe5, 0xb7, 0x64, 0x19, 0x0d, 0x80, - 0x2f, 0x12, 0xd5, 0x45, 0x65, 0x1d, 0x99, 0x7f, 0x9b, 0xdd, 0xf2, 0xd9, 0xea, 0xdd, 0xf2, 0x99, 0xda, 0x2d, 0x37, - 0x73, 0xec, 0xbd, 0x51, 0x0b, 0xff, 0xeb, 0x54, 0x08, 0xc1, 0xaa, 0x00, 0x39, 0x2c, 0x34, 0xd3, 0x1a, 0x6d, 0xf8, - 0x87, 0x86, 0xc6, 0x18, 0x74, 0x13, 0xf3, 0xc9, 0xbc, 0xa6, 0x85, 0x85, 0xf8, 0xd7, 0xac, 0x55, 0xb5, 0x1e, 0x60, - 0x1d, 0xf6, 0x7a, 0xb8, 0x5c, 0xd7, 0xbe, 0x79, 0xd3, 0x82, 0xbc, 0xe2, 0x4e, 0xa0, 0x84, 0x31, 0x78, 0x0e, 0xd1, - 0xe9, 0x29, 0x94, 0x8e, 0xb2, 0xc1, 0xac, 0xf8, 0x5b, 0x09, 0xbf, 0x24, 0xe2, 0x8d, 0x5b, 0x7a, 0x61, 0x1c, 0xd5, - 0x55, 0xe4, 0xf2, 0xa9, 0x11, 0xe6, 0x7a, 0x9d, 0x82, 0x02, 0x18, 0x93, 0x39, 0x6d, 0xff, 0xc1, 0x8a, 0x4d, 0xf0, - 0xef, 0xb2, 0x36, 0x2b, 0x91, 0xf9, 0xbd, 0xc4, 0xb8, 0x91, 0x08, 0xbf, 0x8a, 0x06, 0xe6, 0x1a, 0x36, 0x9f, 0xac, - 0x06, 0xf7, 0x48, 0xcd, 0xd4, 0x57, 0x4a, 0x41, 0xea, 0x1d, 0x30, 0x4a, 0xa3, 0x59, 0xc2, 0x6f, 0x1e, 0x75, 0x1d, - 0x67, 0x2c, 0x8d, 0x7a, 0x83, 0x40, 0xaf, 0xda, 0xde, 0x51, 0x4a, 0xdf, 0xfb, 0xec, 0x01, 0xfe, 0x27, 0xd2, 0x05, - 0xae, 0x2a, 0x53, 0x5d, 0xb8, 0xaa, 0x68, 0xaa, 0x4f, 0x6a, 0xb6, 0xb8, 0xd0, 0xe0, 0x64, 0x8e, 0xdf, 0xb5, 0x35, - 0x1a, 0x95, 0x77, 0x6a, 0x2e, 0x8d, 0xac, 0x5f, 0xd5, 0xfa, 0xd7, 0x0d, 0x7e, 0xc7, 0xb6, 0x03, 0x61, 0xb8, 0xd6, - 0xdb, 0xca, 0xdf, 0x61, 0x5a, 0x6a, 0xac, 0x28, 0x4e, 0xed, 0x27, 0xe1, 0x95, 0xf6, 0x50, 0xc4, 0xb9, 0x12, 0x3a, - 0x29, 0x13, 0xe1, 0xa4, 0xfc, 0x85, 0x87, 0xf7, 0xf1, 0x85, 0x84, 0xd6, 0xe5, 0x24, 0x49, 0xc1, 0x48, 0x1a, 0x73, - 0x3e, 0x0d, 0x76, 0x76, 0x2e, 0x2e, 0x2e, 0xfc, 0x8b, 0x5d, 0x3f, 0xcb, 0xcf, 0x76, 0xda, 0xcd, 0x66, 0x13, 0xdf, - 0x23, 0x67, 0x5b, 0xe7, 0x31, 0xbb, 0x78, 0x0a, 0x76, 0xb0, 0xfd, 0xd8, 0x7a, 0x62, 0x3d, 0xde, 0xb5, 0x1e, 0x3e, - 0xb2, 0x2d, 0x12, 0xe7, 0x50, 0xb2, 0x6b, 0x5b, 0x42, 0x9c, 0x87, 0x36, 0x14, 0x77, 0xf7, 0xce, 0x94, 0x45, 0x86, - 0xf7, 0x74, 0x84, 0xbd, 0x03, 0xce, 0x41, 0xf6, 0x89, 0xd5, 0x37, 0xae, 0x28, 0x6b, 0x48, 0xa5, 0xa0, 0x1e, 0x71, - 0xf7, 0x0e, 0xa2, 0x69, 0x40, 0x4c, 0x61, 0x16, 0x62, 0x0c, 0x46, 0x94, 0xd2, 0x14, 0x68, 0x65, 0x9e, 0xc2, 0x37, - 0x4c, 0xec, 0xb4, 0xe0, 0xfb, 0x9b, 0xf6, 0x63, 0xd0, 0x58, 0xe7, 0x8d, 0x07, 0x83, 0x66, 0xa3, 0x65, 0xb5, 0x1a, - 0x6d, 0xff, 0xb1, 0xd5, 0x16, 0xff, 0x82, 0xc4, 0xdb, 0xb5, 0x5a, 0xf0, 0x6d, 0xd7, 0x82, 0xe7, 0xf3, 0x07, 0xe2, - 0x00, 0x3a, 0xb2, 0x77, 0xba, 0x7b, 0xf8, 0xb3, 0x6a, 0x80, 0xd4, 0x67, 0xb6, 0xf8, 0x21, 0x48, 0xfb, 0x9e, 0x59, - 0xda, 0x7a, 0xb2, 0xb2, 0xb8, 0xfd, 0x78, 0x65, 0xf1, 0xee, 0xa3, 0x95, 0xc5, 0x0f, 0x1e, 0xd6, 0x8b, 0x77, 0xce, - 0x44, 0x95, 0xde, 0xe5, 0xa1, 0x3d, 0x89, 0x60, 0xd9, 0x2f, 0x9d, 0x16, 0xc0, 0xd9, 0xb4, 0x1a, 0xf8, 0xf1, 0xb8, - 0xed, 0xea, 0x5e, 0xa7, 0xd8, 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfd, 0x68, 0x80, 0xed, 0x08, 0x51, - 0xf8, 0x3b, 0xdf, 0x7d, 0x32, 0x00, 0xf9, 0x6e, 0xe1, 0x1f, 0xfc, 0x37, 0x7e, 0xd8, 0x1e, 0x88, 0x87, 0x26, 0xd6, - 0x7f, 0xd3, 0x7a, 0x5c, 0x40, 0x53, 0xfc, 0xef, 0x17, 0x6d, 0x10, 0xa3, 0x39, 0x6e, 0x8e, 0xfb, 0x00, 0x68, 0xf4, - 0x64, 0xdc, 0xf6, 0x3f, 0x3b, 0x7f, 0xec, 0x3f, 0x19, 0xb7, 0x1e, 0x7f, 0x23, 0x9e, 0x12, 0xa0, 0xe0, 0x67, 0xf8, - 0xf7, 0xcd, 0x6e, 0x13, 0xbc, 0x4b, 0xff, 0xc9, 0xf9, 0xae, 0xbf, 0x9b, 0x34, 0x1e, 0xf9, 0x4f, 0xf0, 0xaf, 0x1a, - 0x6e, 0x9c, 0x4d, 0x98, 0x6d, 0xe1, 0x7a, 0x2f, 0x78, 0x5b, 0xe6, 0x1c, 0xed, 0x07, 0xd6, 0xc3, 0x07, 0x2f, 0x9f, - 0xc0, 0x1a, 0x8d, 0x5b, 0x6d, 0xf8, 0x77, 0xdd, 0xd7, 0x6f, 0x90, 0xf0, 0x72, 0xe0, 0x88, 0x61, 0x86, 0xbd, 0x22, - 0x1c, 0xbd, 0xd3, 0xf0, 0xbe, 0x07, 0x0e, 0xd4, 0x6a, 0xef, 0x9a, 0xb1, 0xdb, 0x23, 0xa8, 0xec, 0x6e, 0xee, 0x35, - 0x63, 0x7f, 0xac, 0x7b, 0xcd, 0xd9, 0x42, 0x04, 0xf5, 0x92, 0x2f, 0x79, 0xd1, 0x8b, 0xae, 0xd7, 0x07, 0xee, 0x1c, - 0xfd, 0x85, 0xf7, 0xf1, 0x36, 0x09, 0xb4, 0x8e, 0x99, 0x19, 0x6c, 0xc8, 0x70, 0x23, 0xe3, 0x8f, 0x2b, 0xd2, 0xdd, - 0x9f, 0x75, 0x04, 0xc9, 0x6f, 0x27, 0xc8, 0x37, 0x77, 0xa3, 0x47, 0xfe, 0x07, 0xd3, 0xa3, 0x30, 0xe9, 0x51, 0x0b, - 0xe7, 0x92, 0x3b, 0x4b, 0xee, 0xe8, 0x01, 0x3d, 0x3b, 0x98, 0x84, 0xbd, 0x6d, 0xef, 0x30, 0x2c, 0x2a, 0x6c, 0x71, - 0x88, 0xf0, 0xf4, 0xd7, 0xc4, 0x9f, 0xc5, 0x8d, 0x8b, 0xd0, 0x96, 0xbe, 0xff, 0x14, 0xdf, 0xdb, 0xad, 0x1e, 0xce, - 0xc5, 0xad, 0xbe, 0x90, 0xae, 0xe4, 0x3e, 0xd4, 0x71, 0x03, 0xbc, 0x04, 0x13, 0xce, 0x33, 0x1e, 0xe1, 0x0f, 0xc3, - 0x01, 0xb9, 0xe9, 0x27, 0xe4, 0x62, 0x9e, 0x30, 0x3c, 0x24, 0x1f, 0x88, 0x77, 0x28, 0xc3, 0x57, 0x79, 0xdd, 0x16, - 0x6f, 0x71, 0x7c, 0x8d, 0x37, 0x50, 0x54, 0x60, 0x7a, 0x82, 0x2e, 0xf5, 0x1b, 0x36, 0x8c, 0x23, 0xc7, 0x76, 0xa6, - 0xb0, 0x91, 0x61, 0x96, 0x46, 0xed, 0xfa, 0x07, 0xdd, 0xfc, 0x70, 0x6d, 0xf5, 0xeb, 0x64, 0x39, 0xbe, 0xed, 0x31, - 0x3c, 0x92, 0x41, 0x2d, 0x5b, 0x9a, 0xf9, 0x30, 0xbe, 0x2a, 0xc9, 0x51, 0xa2, 0x57, 0xa6, 0x81, 0x2d, 0x6c, 0x83, - 0x96, 0xdf, 0x06, 0x5f, 0x81, 0x8a, 0xf1, 0xed, 0x79, 0xdf, 0x39, 0x8d, 0x5d, 0xb0, 0x5d, 0x8c, 0x6e, 0x7a, 0xa0, - 0xbe, 0xfe, 0xb1, 0x2b, 0xfd, 0x83, 0x8c, 0xf5, 0x3b, 0x33, 0xb6, 0xe0, 0x88, 0x7b, 0x02, 0x77, 0x5b, 0xbc, 0xa5, - 0x84, 0xa8, 0x47, 0x77, 0x46, 0xa1, 0xcc, 0x31, 0x7f, 0x98, 0x4f, 0xbc, 0x9d, 0x4f, 0xfc, 0x06, 0x67, 0x59, 0x35, - 0xe1, 0xee, 0x9c, 0x02, 0xef, 0x98, 0x64, 0x8c, 0x57, 0x75, 0x31, 0x0e, 0x1b, 0x1a, 0x34, 0xc5, 0x67, 0xb7, 0x46, - 0x64, 0xee, 0x69, 0x80, 0x88, 0xc0, 0xa1, 0xfc, 0xac, 0x8a, 0xd5, 0x17, 0x19, 0x5d, 0x01, 0xb7, 0x1d, 0x7f, 0xf9, - 0x88, 0x3e, 0x96, 0x62, 0x37, 0xe2, 0xec, 0x60, 0xa1, 0xb4, 0x1a, 0xaa, 0x8a, 0xd1, 0x14, 0x4f, 0xaf, 0x0e, 0xe5, - 0x6b, 0x3f, 0x6c, 0x0c, 0x81, 0x52, 0xe8, 0xbb, 0x7a, 0xe5, 0xe0, 0x36, 0xa8, 0x46, 0xfa, 0x21, 0x60, 0xca, 0x60, - 0x42, 0xed, 0x87, 0xb7, 0x6e, 0x2c, 0xe9, 0xf3, 0x84, 0xb6, 0xd0, 0x7d, 0x43, 0x76, 0x1e, 0x0f, 0xa4, 0x0a, 0xf3, - 0x2c, 0x79, 0x5b, 0xb0, 0x41, 0x4b, 0x13, 0xb6, 0x3c, 0xe1, 0xf5, 0xc3, 0x03, 0xea, 0xe3, 0x30, 0xcd, 0xec, 0xee, - 0xfd, 0xce, 0x3a, 0xe2, 0xe3, 0xaf, 0x12, 0x1f, 0x81, 0x97, 0xf9, 0xb7, 0xe1, 0x7d, 0xfc, 0x5d, 0xe2, 0xfb, 0x7d, - 0xdb, 0xf5, 0x49, 0x01, 0xdc, 0xaf, 0x7e, 0x9c, 0x18, 0xa5, 0xdf, 0x36, 0xe8, 0x6a, 0xef, 0xae, 0x4a, 0x5b, 0x2a, - 0xe8, 0xf6, 0xc3, 0x4a, 0x41, 0xc3, 0x77, 0x43, 0x22, 0x83, 0xb2, 0x68, 0xfb, 0x0f, 0x0d, 0xb1, 0x7f, 0xde, 0xc0, - 0xcf, 0x9a, 0xe0, 0x7f, 0x00, 0x0d, 0x94, 0xe4, 0x7f, 0x0d, 0xcd, 0x77, 0x85, 0x92, 0x81, 0x7e, 0xdf, 0x93, 0x58, - 0x96, 0x22, 0xb9, 0xb6, 0x0d, 0x56, 0x9c, 0xc6, 0x88, 0x6c, 0x2c, 0xdb, 0x73, 0xf4, 0x2f, 0x1e, 0xc9, 0x5d, 0x29, - 0xe3, 0x40, 0xcf, 0xa1, 0xaf, 0xa3, 0xdf, 0xe4, 0xbf, 0xaa, 0xce, 0xab, 0x49, 0x89, 0x15, 0x53, 0xe0, 0xbe, 0x5e, - 0x38, 0xf1, 0xe9, 0x88, 0x2b, 0x0c, 0xfa, 0x55, 0x40, 0xeb, 0x19, 0x5a, 0xde, 0x75, 0x70, 0x0d, 0x11, 0xc1, 0xe8, - 0x6d, 0xc3, 0x34, 0xc9, 0xab, 0x61, 0xb9, 0x38, 0x3f, 0xa6, 0x83, 0xe5, 0x99, 0x71, 0xa7, 0x50, 0x46, 0xef, 0x30, - 0x59, 0x74, 0x18, 0xe7, 0xf4, 0x62, 0x04, 0x05, 0x7a, 0x2d, 0x02, 0x58, 0x51, 0x89, 0xa4, 0x04, 0x2b, 0x7a, 0x36, - 0x16, 0xd9, 0x81, 0x4d, 0xe1, 0x23, 0xdb, 0x7c, 0xdd, 0xbe, 0x79, 0x73, 0x9d, 0x38, 0x99, 0x12, 0xbb, 0x71, 0xaf, - 0x22, 0x7d, 0x6c, 0x90, 0xb6, 0x6b, 0x77, 0x09, 0xd9, 0x60, 0x88, 0x6b, 0xf5, 0xfb, 0x72, 0xa6, 0x00, 0xb2, 0x4d, - 0x42, 0xeb, 0x71, 0x89, 0x84, 0xae, 0xa4, 0xd3, 0x29, 0x8b, 0xb8, 0x1f, 0xa5, 0x22, 0x0b, 0xc1, 0x10, 0x53, 0x5e, - 0x8b, 0xed, 0xba, 0x25, 0xc8, 0x46, 0x23, 0x6f, 0x42, 0xee, 0x6e, 0x28, 0x54, 0x17, 0x3d, 0x18, 0xaf, 0xe5, 0xb3, - 0x8e, 0xdb, 0xdd, 0x77, 0x87, 0xfb, 0x96, 0xd8, 0x94, 0x7b, 0x3b, 0xf0, 0xb8, 0x47, 0xfe, 0xb8, 0x48, 0xde, 0x0f, - 0x45, 0xf2, 0xbe, 0x25, 0x6f, 0x71, 0x50, 0x86, 0xe3, 0x8e, 0x40, 0xdb, 0xb6, 0x58, 0x3a, 0x10, 0x81, 0xc4, 0x09, - 0xf8, 0x2c, 0x31, 0xbe, 0xa2, 0x71, 0x07, 0xbb, 0x36, 0x70, 0xc1, 0x80, 0x9b, 0x45, 0xd4, 0x51, 0xd9, 0x35, 0x3c, - 0x55, 0x61, 0x47, 0xb0, 0x46, 0x98, 0xca, 0x40, 0x94, 0x43, 0xe9, 0xe4, 0xc5, 0xe5, 0xd6, 0xc5, 0xec, 0x74, 0x02, - 0x72, 0x52, 0xe5, 0x10, 0x7e, 0x94, 0x1d, 0xf6, 0x68, 0xaa, 0xee, 0x49, 0x29, 0xe3, 0xa2, 0xea, 0xf5, 0xf9, 0x0b, - 0x3f, 0x35, 0x2c, 0xb0, 0x97, 0x7a, 0x01, 0xb3, 0xf0, 0xc7, 0xbb, 0x5d, 0x1d, 0x89, 0x34, 0xeb, 0x4a, 0x40, 0x7d, - 0xb7, 0x7b, 0x12, 0x4c, 0xe5, 0x78, 0xaf, 0xb3, 0xa5, 0x9f, 0x2d, 0xd6, 0x72, 0xb2, 0x47, 0xd9, 0xa9, 0xe2, 0x6a, - 0x93, 0x04, 0x18, 0x56, 0x10, 0x60, 0x92, 0x26, 0x80, 0x45, 0xe7, 0xaa, 0xf6, 0xc3, 0xa6, 0x4a, 0x78, 0x85, 0x32, - 0xdc, 0x90, 0xa2, 0x8b, 0x31, 0x49, 0x2d, 0x98, 0x3b, 0x6e, 0x75, 0xf7, 0x22, 0x69, 0x5c, 0xa2, 0xf0, 0x28, 0x40, - 0x7a, 0x40, 0x67, 0xb4, 0xe0, 0xfc, 0x38, 0xdb, 0xb9, 0x60, 0xa7, 0x8d, 0x68, 0x1a, 0x57, 0x91, 0x53, 0x34, 0x35, - 0xf4, 0x94, 0x59, 0x35, 0x13, 0x7e, 0x8d, 0x16, 0x90, 0x24, 0xc1, 0x5d, 0xca, 0xb0, 0x2c, 0x59, 0xe8, 0xc0, 0x42, - 0x40, 0x61, 0x92, 0xeb, 0x2a, 0x7c, 0x2b, 0x35, 0x6e, 0x69, 0x77, 0xff, 0xfa, 0x8f, 0xff, 0x5b, 0x46, 0x64, 0x81, - 0x2a, 0x2d, 0x35, 0xd6, 0x02, 0xa1, 0xcb, 0x3d, 0xba, 0xb5, 0xa2, 0x8f, 0x10, 0xd9, 0x25, 0xb8, 0xf6, 0xf1, 0xb0, - 0x31, 0x8e, 0x92, 0x11, 0x00, 0xb6, 0x96, 0x40, 0x66, 0x52, 0xb8, 0x84, 0xba, 0x5e, 0x84, 0x2c, 0xf8, 0x9b, 0xd2, - 0x9b, 0x55, 0x96, 0x2c, 0xed, 0x56, 0x33, 0xd9, 0xb9, 0xda, 0x50, 0xb5, 0x84, 0x67, 0xf5, 0xdb, 0x7d, 0x4a, 0xa8, - 0xd5, 0xf2, 0x9c, 0xa1, 0xa5, 0x3e, 0x02, 0xf9, 0xd7, 0x7f, 0xfa, 0xbb, 0xff, 0xa1, 0x1e, 0xf1, 0x64, 0xe3, 0xaf, - 0xff, 0xf0, 0x9f, 0x31, 0x1b, 0xd3, 0xd2, 0xa7, 0x1f, 0x24, 0x27, 0xac, 0xea, 0xe8, 0x43, 0x08, 0x0c, 0x0b, 0x53, - 0x9d, 0x26, 0x20, 0x06, 0xe3, 0x41, 0x3d, 0xf3, 0xf9, 0x80, 0x26, 0xa4, 0xcd, 0x26, 0xa1, 0xa3, 0x4d, 0x5b, 0x56, - 0x3c, 0x52, 0x23, 0x39, 0xf1, 0x22, 0x54, 0x22, 0xbd, 0xef, 0x74, 0xfb, 0xc3, 0xd7, 0xab, 0x31, 0x57, 0xf1, 0x3e, - 0x2c, 0x29, 0xab, 0x72, 0x0b, 0x03, 0xf1, 0x73, 0x7c, 0x0c, 0xda, 0x46, 0x31, 0x2d, 0x5e, 0xad, 0x4f, 0xe7, 0xa7, - 0x19, 0xc0, 0x3f, 0x42, 0x8a, 0x8b, 0xa8, 0x22, 0x9d, 0x79, 0x36, 0xd0, 0xe6, 0x4b, 0xae, 0x4a, 0x1a, 0x45, 0x38, - 0x8a, 0x0f, 0x9e, 0xfc, 0x4d, 0xf9, 0xe7, 0x09, 0x5a, 0x56, 0x96, 0x33, 0x89, 0x2e, 0xa5, 0xfb, 0xf8, 0xa8, 0xd9, - 0x9c, 0x5e, 0xba, 0xf3, 0x6a, 0x06, 0x6f, 0xdd, 0x64, 0x14, 0x89, 0x34, 0x07, 0xa4, 0xc3, 0x52, 0x1d, 0xf4, 0x04, - 0x8f, 0xa9, 0x89, 0x31, 0xb2, 0xb2, 0xfc, 0xd3, 0x9c, 0xe2, 0x6e, 0xf1, 0x2f, 0x78, 0xa8, 0x29, 0x43, 0x94, 0x50, - 0x62, 0x60, 0x31, 0x37, 0x7a, 0xb5, 0x45, 0xaf, 0x71, 0x6b, 0xf9, 0xea, 0x83, 0x79, 0x28, 0x6b, 0x1e, 0xa7, 0x3e, - 0xc0, 0x03, 0xd2, 0x71, 0xcb, 0x1b, 0xb7, 0xe7, 0x7a, 0x78, 0xce, 0xb3, 0x89, 0x79, 0x0a, 0xcb, 0x22, 0x36, 0x60, - 0x23, 0x15, 0xda, 0x95, 0xf5, 0xe2, 0x84, 0xb5, 0x1c, 0xef, 0xae, 0x98, 0x4b, 0x82, 0x44, 0xa7, 0xaf, 0x00, 0xcf, - 0x3d, 0xdc, 0x80, 0x40, 0xff, 0x2c, 0xe2, 0x01, 0xf1, 0x6b, 0xc7, 0x3c, 0xcb, 0x8d, 0x50, 0xca, 0x64, 0x73, 0x03, - 0x9e, 0x8e, 0x68, 0x8a, 0x41, 0xd6, 0xfa, 0xd5, 0x93, 0xd2, 0xa7, 0xee, 0xe6, 0x50, 0x22, 0x46, 0xf3, 0x8d, 0x3c, - 0x22, 0x7d, 0x5a, 0x0b, 0x6e, 0x48, 0x15, 0xd3, 0x76, 0xbd, 0x95, 0xf5, 0x42, 0x53, 0x8b, 0xda, 0x6f, 0xb8, 0x63, - 0x13, 0x98, 0xf6, 0x62, 0x2b, 0x2a, 0xc4, 0x56, 0x4f, 0xc3, 0x6f, 0xb4, 0xeb, 0x13, 0x4d, 0xa7, 0xd4, 0xd0, 0x05, - 0x26, 0x26, 0x83, 0x15, 0x65, 0x07, 0x1d, 0xff, 0x8b, 0xd3, 0x76, 0xd9, 0x46, 0x6e, 0x04, 0xf1, 0x4d, 0x9e, 0xc3, - 0xe3, 0xaf, 0xae, 0x74, 0xff, 0x1f, 0x1c, 0x1d, 0xa5, 0x5f, 0x1b, 0x82, 0x00, 0x00}; - -} // namespace web_server -} // namespace esphome diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h new file mode 100644 index 000000000000..31c2d1fd85e1 --- /dev/null +++ b/esphome/components/web_server/server_index_v2.h @@ -0,0 +1,636 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver + +#ifdef USE_WEBSERVER_LOCAL +#if USE_WEBSERVER_VERSION == 2 + +#include "esphome/core/hal.h" + +namespace esphome { +namespace web_server { + +const uint8_t INDEX_GZ[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xd9, 0x92, 0xdb, 0x46, 0xb6, 0xe0, 0xf3, + 0xdc, 0xaf, 0x40, 0xc1, 0xd5, 0x25, 0x64, 0x33, 0x89, 0x22, 0x59, 0xda, 0x0c, 0x56, 0x92, 0x5d, 0x2a, 0xc9, 0x2d, + 0xbb, 0xb5, 0xd8, 0x2a, 0xc9, 0x6e, 0x9b, 0x66, 0x57, 0xa1, 0x88, 0x24, 0x99, 0x16, 0x88, 0xa4, 0x13, 0xc9, 0x5a, + 0x4c, 0xe2, 0xc6, 0x7c, 0xc0, 0x44, 0x4c, 0xc4, 0x3c, 0xcd, 0xcb, 0xc4, 0xdc, 0x87, 0xf9, 0x88, 0x79, 0xbe, 0x9f, + 0x72, 0x7f, 0x60, 0xe6, 0x13, 0x26, 0x4e, 0x2e, 0x40, 0x82, 0x4b, 0xa9, 0xbc, 0xdc, 0x1b, 0x13, 0x0a, 0x49, 0x44, + 0xae, 0x27, 0x4f, 0x9e, 0x3c, 0x7b, 0x02, 0xc7, 0x7b, 0x09, 0x1f, 0xc9, 0xdb, 0x39, 0xf5, 0xa6, 0x72, 0x96, 0xf6, + 0x8e, 0xcd, 0xbf, 0x34, 0x4e, 0x7a, 0xc7, 0x29, 0xcb, 0x3e, 0x7a, 0x82, 0xa6, 0x84, 0x8d, 0x78, 0xe6, 0x4d, 0x05, + 0x1d, 0x93, 0x24, 0x96, 0x71, 0xc4, 0x66, 0xf1, 0x84, 0x7a, 0x87, 0xbd, 0xe3, 0x19, 0x95, 0xb1, 0x37, 0x9a, 0xc6, + 0x22, 0xa7, 0x92, 0x7c, 0x78, 0xff, 0x45, 0xf3, 0x69, 0xef, 0x38, 0x1f, 0x09, 0x36, 0x97, 0x1e, 0x0c, 0x49, 0x66, + 0x3c, 0x59, 0xa4, 0xb4, 0x77, 0x78, 0x78, 0x7d, 0x7d, 0x1d, 0xfe, 0x94, 0xff, 0xd3, 0x88, 0x67, 0xb9, 0xf4, 0x5e, + 0x91, 0x6b, 0x96, 0x25, 0xfc, 0x1a, 0x53, 0x49, 0x5e, 0x85, 0x67, 0xd3, 0x38, 0xe1, 0xd7, 0xef, 0x38, 0x97, 0x07, + 0x07, 0x81, 0x7e, 0xbc, 0x3d, 0x3d, 0x3b, 0x23, 0x84, 0x5c, 0x71, 0x96, 0x78, 0xad, 0xd5, 0xaa, 0x2a, 0x0c, 0xb3, + 0x58, 0xb2, 0x2b, 0xaa, 0xbb, 0xa0, 0x83, 0x03, 0x3f, 0x4e, 0xf8, 0x5c, 0xd2, 0xe4, 0x4c, 0xde, 0xa6, 0xf4, 0x6c, + 0x4a, 0xa9, 0xcc, 0x7d, 0x96, 0x79, 0xcf, 0xf9, 0x68, 0x31, 0xa3, 0x99, 0x0c, 0xe7, 0x82, 0x4b, 0x0e, 0x90, 0x1c, + 0x1c, 0xf8, 0x82, 0xce, 0xd3, 0x78, 0x44, 0xa1, 0xfe, 0xf4, 0xec, 0xac, 0xea, 0x51, 0x35, 0xc2, 0x4c, 0x92, 0xb3, + 0xdb, 0xd9, 0x25, 0x4f, 0x03, 0x84, 0x53, 0x49, 0x32, 0x7a, 0xed, 0x7d, 0x47, 0xe3, 0x8f, 0xaf, 0xe3, 0x79, 0x77, + 0x94, 0xc6, 0x79, 0xee, 0x5d, 0xca, 0xa5, 0x5a, 0x82, 0x58, 0x8c, 0x24, 0x17, 0x81, 0xc4, 0x14, 0x33, 0xb4, 0x64, + 0xe3, 0x40, 0x4e, 0x59, 0x1e, 0x9e, 0xef, 0x8f, 0xf2, 0xfc, 0x1d, 0xcd, 0x17, 0xa9, 0xdc, 0x27, 0x7b, 0x2d, 0xcc, + 0xf6, 0x08, 0x61, 0x12, 0xc9, 0xa9, 0xe0, 0xd7, 0xde, 0x0b, 0x21, 0xb8, 0x08, 0xfc, 0xd3, 0xb3, 0x33, 0xdd, 0xc2, + 0x63, 0xb9, 0x97, 0x71, 0xe9, 0x95, 0xe3, 0xc5, 0x97, 0x29, 0x0d, 0xbd, 0x0f, 0x39, 0xf5, 0x2e, 0x16, 0x59, 0x1e, + 0x8f, 0xe9, 0xe9, 0xd9, 0xd9, 0x85, 0xc7, 0x85, 0x77, 0x31, 0xca, 0xf3, 0x0b, 0x8f, 0x65, 0xb9, 0xa4, 0x71, 0x12, + 0xfa, 0xa8, 0xab, 0x26, 0x1b, 0xe5, 0xf9, 0x7b, 0x7a, 0x23, 0x89, 0xc4, 0xea, 0x51, 0x12, 0x5a, 0x4c, 0xa8, 0xf4, + 0xf2, 0x72, 0x5d, 0x01, 0x5a, 0xa6, 0x54, 0x7a, 0x92, 0xa8, 0x7a, 0xde, 0xd5, 0xb8, 0xa7, 0xfa, 0x51, 0x76, 0xd9, + 0x38, 0xa0, 0xf2, 0xe0, 0x40, 0x96, 0x78, 0x46, 0x7a, 0x69, 0x1e, 0x23, 0x74, 0xcf, 0x96, 0x1d, 0x1c, 0xd0, 0x30, + 0xa5, 0xd9, 0x44, 0x4e, 0x09, 0x21, 0xed, 0x2e, 0x3b, 0x38, 0x08, 0x24, 0x49, 0x65, 0x38, 0xa1, 0x32, 0xa0, 0x08, + 0xe1, 0xaa, 0xf7, 0xc1, 0x41, 0xa0, 0x91, 0xc0, 0x89, 0x46, 0x5c, 0x0d, 0xc7, 0x28, 0x34, 0xd8, 0x3f, 0xbb, 0xcd, + 0x46, 0x81, 0x0b, 0x3f, 0xc2, 0xec, 0xe0, 0x20, 0x95, 0x61, 0x0e, 0x23, 0x62, 0x89, 0x50, 0x21, 0xa8, 0x5c, 0x88, + 0xcc, 0x93, 0x85, 0xe4, 0x67, 0x52, 0xb0, 0x6c, 0x12, 0xa0, 0xa5, 0x2d, 0x73, 0x3a, 0x16, 0x85, 0x06, 0xf7, 0x6b, + 0x49, 0x04, 0xe9, 0xc1, 0x8c, 0x97, 0x32, 0x80, 0x5d, 0xe4, 0x63, 0x4f, 0x10, 0xe2, 0xe7, 0xaa, 0xaf, 0xdf, 0x17, + 0x91, 0x68, 0xf8, 0x3e, 0xd6, 0x50, 0x62, 0x26, 0x11, 0xfe, 0x48, 0x02, 0x81, 0xc3, 0x30, 0x94, 0x88, 0xf4, 0x96, + 0x16, 0x2b, 0xc2, 0x59, 0x67, 0x5f, 0x0c, 0x5a, 0xc3, 0x48, 0x86, 0x82, 0x26, 0x8b, 0x11, 0x0d, 0x02, 0x86, 0x73, + 0x9c, 0x21, 0xd2, 0x63, 0x8d, 0x80, 0x93, 0x1e, 0x6c, 0x37, 0xaf, 0xef, 0x35, 0x21, 0x7b, 0x2d, 0x64, 0x60, 0xe4, + 0x16, 0x40, 0xc0, 0xb0, 0x81, 0x87, 0x13, 0xe2, 0x67, 0x8b, 0xd9, 0x25, 0x15, 0x7e, 0xd9, 0xac, 0x5b, 0x23, 0x8b, + 0x45, 0x4e, 0xbd, 0x51, 0x9e, 0x7b, 0xe3, 0x45, 0x36, 0x92, 0x8c, 0x67, 0x9e, 0xdf, 0xe0, 0x0d, 0x5f, 0x93, 0x43, + 0x49, 0x0d, 0x3e, 0x2a, 0x50, 0x90, 0xa3, 0x86, 0x18, 0x64, 0x8d, 0xf6, 0x10, 0x03, 0x94, 0xa8, 0x6b, 0xc6, 0x33, + 0x08, 0xa0, 0x58, 0xc0, 0x1a, 0x0b, 0xfc, 0x41, 0xc2, 0x2a, 0xd5, 0x12, 0xa9, 0xec, 0x8b, 0x70, 0xf3, 0xa0, 0x10, + 0x19, 0xce, 0xe2, 0x79, 0x40, 0x49, 0x8f, 0x2a, 0xe2, 0x8a, 0xb3, 0x11, 0xc0, 0x5a, 0xdb, 0xb7, 0x3e, 0x8d, 0x68, + 0x58, 0x91, 0x14, 0x8a, 0x64, 0x38, 0xe6, 0xe2, 0x45, 0x3c, 0x9a, 0x42, 0xbf, 0x92, 0x60, 0x12, 0x7b, 0xde, 0x46, + 0x82, 0xc6, 0x92, 0xbe, 0x48, 0x29, 0x3c, 0x05, 0xbe, 0xea, 0xe9, 0x23, 0x9c, 0x93, 0x57, 0x61, 0xca, 0xe4, 0x1b, + 0x9e, 0x8d, 0x68, 0x37, 0x77, 0xa8, 0x8b, 0xc1, 0xbe, 0x9f, 0x48, 0x29, 0xd8, 0xe5, 0x42, 0xd2, 0xc0, 0xcf, 0xa0, + 0x85, 0x8f, 0x73, 0x84, 0x59, 0x28, 0xe9, 0x8d, 0x3c, 0xe5, 0x99, 0xa4, 0x99, 0x24, 0xd4, 0x22, 0x15, 0x8b, 0x30, + 0x9e, 0xcf, 0x69, 0x96, 0x9c, 0x4e, 0x59, 0x9a, 0x04, 0x0c, 0x15, 0xa8, 0xc0, 0xb1, 0x24, 0xb0, 0x46, 0xd2, 0x13, + 0x11, 0xfc, 0xb3, 0x7b, 0x35, 0x81, 0x24, 0x3d, 0x75, 0x28, 0x28, 0xf1, 0xfd, 0xee, 0x98, 0x8b, 0xc0, 0xac, 0xc0, + 0xe3, 0x63, 0x4f, 0xc2, 0x1c, 0xef, 0x16, 0x29, 0xcd, 0x11, 0x6d, 0x10, 0x56, 0x6e, 0xa3, 0x41, 0xf0, 0xd7, 0x40, + 0xf1, 0x05, 0x0a, 0x04, 0x8a, 0x44, 0xf7, 0x2a, 0x16, 0xde, 0x17, 0xe6, 0x44, 0xfd, 0x64, 0xb9, 0xd9, 0x54, 0x92, + 0x9f, 0x42, 0x29, 0x16, 0xb9, 0xa4, 0xc9, 0xfb, 0xdb, 0x39, 0xcd, 0xf1, 0x4b, 0x49, 0xa6, 0xb2, 0x3f, 0x95, 0x21, + 0x9d, 0xcd, 0xe5, 0xed, 0x99, 0x62, 0x8c, 0x91, 0xef, 0xe3, 0x11, 0xb4, 0x14, 0x34, 0x1e, 0x01, 0x33, 0x33, 0xd8, + 0xfa, 0x9a, 0xa7, 0xb7, 0x63, 0x96, 0xa6, 0x67, 0x8b, 0xf9, 0x9c, 0x0b, 0x89, 0xff, 0x4a, 0x96, 0x92, 0x57, 0xa8, + 0x81, 0xbd, 0x5c, 0xe6, 0xd7, 0x4c, 0x8e, 0xa6, 0x81, 0x44, 0xcb, 0x51, 0x9c, 0x53, 0xef, 0x19, 0xe7, 0x29, 0x8d, + 0xb3, 0x48, 0x10, 0xd1, 0x7f, 0x29, 0xa3, 0x6c, 0x91, 0xa6, 0xdd, 0x4b, 0x41, 0xe3, 0x8f, 0x5d, 0x55, 0xfd, 0xf6, + 0xf2, 0x27, 0x3a, 0x92, 0x91, 0xfa, 0x7d, 0x22, 0x44, 0x7c, 0x0b, 0x0d, 0x09, 0x81, 0x66, 0x7d, 0x11, 0x7d, 0x75, + 0xf6, 0xf6, 0x4d, 0xa8, 0x0f, 0x09, 0x1b, 0xdf, 0x06, 0xa2, 0x3c, 0x78, 0xa2, 0xc0, 0x63, 0xc1, 0x67, 0x6b, 0x53, + 0x6b, 0xac, 0x89, 0xee, 0x0e, 0x10, 0x28, 0x11, 0x7b, 0x7a, 0x68, 0x17, 0x82, 0x37, 0x8a, 0xe6, 0xa1, 0x92, 0x98, + 0x79, 0xe1, 0x9f, 0x48, 0x17, 0x07, 0x02, 0xdd, 0x0d, 0xad, 0x14, 0xb7, 0x4b, 0x4a, 0x14, 0x9c, 0x73, 0x90, 0x30, + 0x00, 0xe3, 0x28, 0x96, 0xa3, 0xe9, 0x92, 0xaa, 0xc1, 0x0a, 0x0b, 0x31, 0x2d, 0x0a, 0x7c, 0x5d, 0xd2, 0xbb, 0xdc, + 0x23, 0x44, 0x28, 0x46, 0x45, 0xe4, 0x6a, 0x25, 0x08, 0x11, 0x08, 0x7f, 0x47, 0x96, 0xb1, 0x5d, 0x4f, 0xb4, 0xd7, + 0xc2, 0x70, 0x2e, 0x23, 0xcd, 0x5d, 0xf0, 0x88, 0x67, 0x57, 0x54, 0x48, 0x2a, 0xa2, 0xbf, 0x62, 0x41, 0xc7, 0x29, + 0x40, 0xb1, 0xd7, 0xc6, 0xd3, 0x38, 0x3f, 0x9d, 0xc6, 0xd9, 0x84, 0x26, 0xd1, 0xb5, 0x2c, 0xf0, 0xdf, 0x89, 0x3f, + 0x66, 0x59, 0x9c, 0xb2, 0x5f, 0x68, 0xe2, 0x1b, 0x69, 0x70, 0xe2, 0xd1, 0x1b, 0x49, 0xb3, 0x24, 0xf7, 0x5e, 0xbe, + 0x7f, 0xfd, 0xca, 0xec, 0x63, 0x4d, 0x40, 0xa0, 0x65, 0xbe, 0x98, 0x53, 0x11, 0x20, 0x6c, 0x04, 0xc4, 0x0b, 0xa6, + 0x98, 0xe3, 0xeb, 0x78, 0xae, 0x4b, 0x58, 0xfe, 0x61, 0x9e, 0xc4, 0x92, 0x7e, 0x4d, 0xb3, 0x84, 0x65, 0x13, 0xb2, + 0xd7, 0xd6, 0xe5, 0xd3, 0xd8, 0x54, 0x24, 0x65, 0xd1, 0xf9, 0xfe, 0x8b, 0x54, 0xad, 0xbb, 0x7c, 0x5c, 0x04, 0xa8, + 0xc8, 0x65, 0x2c, 0xd9, 0xc8, 0x8b, 0x93, 0xe4, 0xcb, 0x8c, 0x49, 0xa6, 0x00, 0x14, 0xb0, 0x3d, 0x40, 0xa2, 0x54, + 0x8b, 0x0a, 0x0b, 0x78, 0x80, 0x70, 0x10, 0x18, 0x01, 0x30, 0x45, 0x66, 0xbf, 0x0e, 0x0e, 0x2a, 0x76, 0xdf, 0xa7, + 0x91, 0xae, 0x24, 0x83, 0x21, 0x0a, 0xe7, 0x8b, 0x1c, 0x36, 0xda, 0x4e, 0x01, 0xd2, 0x85, 0x5f, 0xe6, 0x54, 0x5c, + 0xd1, 0xa4, 0x24, 0x8e, 0x3c, 0x40, 0xcb, 0xb5, 0x39, 0xcc, 0xb1, 0x90, 0x64, 0x30, 0xec, 0xba, 0x7c, 0x9b, 0x1a, + 0x3a, 0x17, 0x7c, 0x4e, 0x85, 0x64, 0x34, 0x2f, 0x59, 0x49, 0x00, 0x52, 0xb4, 0x64, 0x27, 0x39, 0xb1, 0xeb, 0x9b, + 0x07, 0x0c, 0x53, 0x54, 0x63, 0x18, 0x56, 0xd0, 0xbe, 0xb8, 0x52, 0x12, 0x23, 0xc7, 0x0c, 0x61, 0xa9, 0x21, 0xcd, + 0x11, 0x2a, 0x10, 0x96, 0x16, 0x5c, 0xcd, 0x8a, 0xcc, 0x6c, 0xb7, 0x20, 0xaa, 0xc9, 0x77, 0x4a, 0x54, 0x03, 0x43, + 0x8b, 0x25, 0x3d, 0x38, 0x08, 0x68, 0x58, 0x12, 0x05, 0xd9, 0x6b, 0x9b, 0x3d, 0x72, 0x90, 0xb5, 0x03, 0x6c, 0x98, + 0x58, 0x62, 0x8a, 0xf0, 0x1e, 0x0d, 0x33, 0x7e, 0x32, 0x1a, 0xd1, 0x3c, 0xe7, 0xe2, 0xe0, 0x60, 0x4f, 0xb5, 0x2f, + 0xb5, 0x09, 0xd8, 0xc3, 0xb7, 0xd7, 0x59, 0x05, 0x01, 0xaa, 0x24, 0xac, 0x91, 0x0b, 0x12, 0xe4, 0x94, 0x52, 0x38, + 0xfc, 0xbe, 0x55, 0x3c, 0x22, 0xff, 0xfc, 0xdc, 0x6f, 0x48, 0x6c, 0xd0, 0x30, 0xa1, 0x76, 0xea, 0xdb, 0xe7, 0x54, + 0xab, 0x56, 0x4a, 0xf1, 0xd8, 0xc0, 0x8c, 0x3e, 0x3f, 0x61, 0x42, 0xc7, 0x2c, 0x73, 0x96, 0x5d, 0x03, 0x09, 0x4b, + 0x9c, 0xa3, 0xc2, 0xd9, 0xd0, 0xad, 0x43, 0x2b, 0x9d, 0x46, 0xef, 0xdc, 0x72, 0xa2, 0xf4, 0x08, 0x67, 0x1b, 0x07, + 0x74, 0x58, 0x60, 0x85, 0x7a, 0xbb, 0x9a, 0x4c, 0x01, 0x3a, 0x90, 0xc3, 0xae, 0xa9, 0x27, 0xb9, 0xc6, 0x9c, 0xa0, + 0x3f, 0x2f, 0x68, 0x2e, 0x35, 0x1d, 0x07, 0x12, 0x67, 0x98, 0xa1, 0x02, 0x8e, 0xdb, 0x98, 0x4d, 0x16, 0x02, 0xd4, + 0x1d, 0x38, 0x8a, 0x34, 0x5b, 0xcc, 0xa8, 0x7d, 0xda, 0x06, 0xdb, 0xdb, 0x39, 0x08, 0xc4, 0x1c, 0x68, 0xfa, 0x6e, + 0x72, 0x02, 0x58, 0x25, 0x5a, 0xad, 0xbe, 0xb3, 0x83, 0x54, 0x5b, 0x59, 0xaa, 0x68, 0x6b, 0x7b, 0xf2, 0x77, 0x64, + 0xe4, 0xf1, 0x5e, 0x5b, 0x43, 0xff, 0xf7, 0x21, 0xd9, 0x6b, 0x95, 0x14, 0x6c, 0x70, 0xaa, 0x81, 0xd1, 0x28, 0x7c, + 0xab, 0x07, 0x42, 0x4a, 0xba, 0xd7, 0x88, 0x25, 0x9c, 0x6e, 0xd0, 0xe9, 0x94, 0x0c, 0x40, 0xcf, 0x08, 0xa7, 0xc3, + 0x5d, 0xc4, 0x64, 0xb9, 0x41, 0x20, 0x37, 0xeb, 0x2a, 0xa6, 0x71, 0x55, 0x67, 0x1a, 0x6b, 0x8b, 0xf0, 0xe7, 0x65, + 0x17, 0xbf, 0xa4, 0x31, 0x73, 0xcc, 0xab, 0x2a, 0xcc, 0x14, 0x30, 0xd5, 0x92, 0x9c, 0x21, 0xde, 0xc4, 0x33, 0x9a, + 0x07, 0x14, 0xe1, 0x5d, 0x0d, 0x34, 0x71, 0x42, 0x93, 0xa1, 0x23, 0x36, 0x73, 0x10, 0x9b, 0x0c, 0x69, 0xad, 0xac, + 0x7e, 0xdc, 0x72, 0x4c, 0x07, 0xf9, 0xb0, 0x52, 0xe6, 0x9c, 0xc5, 0x2b, 0x79, 0x6c, 0xa8, 0xdb, 0xe2, 0x4f, 0x97, + 0x69, 0xa4, 0x29, 0xa5, 0x21, 0x47, 0x78, 0xaf, 0xb5, 0xbe, 0x8f, 0xb6, 0x55, 0xb5, 0xc6, 0xc1, 0x10, 0xf6, 0x41, + 0x89, 0x8b, 0x90, 0xe5, 0xea, 0xff, 0xda, 0x39, 0x03, 0xb4, 0x9d, 0x01, 0x59, 0x84, 0xe3, 0x34, 0x96, 0x41, 0xfb, + 0xb0, 0x05, 0x9a, 0xe8, 0x15, 0x05, 0x69, 0x82, 0xd0, 0xe6, 0x52, 0x68, 0xb8, 0xc8, 0xf2, 0x29, 0x1b, 0xcb, 0x20, + 0x96, 0x8a, 0xa1, 0xd0, 0x34, 0xa7, 0x9e, 0xac, 0xe9, 0xc3, 0x8a, 0xd9, 0xc4, 0x40, 0x6a, 0xa5, 0xf2, 0x45, 0x2d, + 0xa4, 0x8a, 0x69, 0x01, 0x6f, 0xa8, 0x74, 0xe9, 0x8a, 0xc7, 0xd8, 0xd6, 0x0c, 0xf4, 0xc5, 0x76, 0x5f, 0x8f, 0x18, + 0x19, 0x56, 0xc0, 0x1c, 0x95, 0x95, 0x45, 0x2e, 0x7f, 0x30, 0x85, 0x32, 0x94, 0xfc, 0x15, 0xbf, 0xa6, 0xe2, 0x34, + 0x06, 0xe0, 0x23, 0xdd, 0xbd, 0xd0, 0x62, 0x40, 0x71, 0x7b, 0xd9, 0xb5, 0xf4, 0x72, 0xae, 0x16, 0xfe, 0xb5, 0xe0, + 0x33, 0x96, 0x53, 0xd0, 0xd4, 0x34, 0xfe, 0x33, 0x38, 0x65, 0xea, 0x38, 0x82, 0xa8, 0xa1, 0x25, 0x7d, 0x9d, 0xbc, + 0xaa, 0xd3, 0xd7, 0xf9, 0xfe, 0x8b, 0x89, 0x65, 0x7f, 0xf5, 0x43, 0x8c, 0x70, 0x60, 0xec, 0x09, 0x47, 0xca, 0x85, + 0x53, 0x64, 0xc4, 0xfb, 0x6a, 0x25, 0x1d, 0xb3, 0xad, 0xa6, 0x2b, 0x52, 0x7d, 0x6c, 0x50, 0x11, 0x27, 0x09, 0x68, + 0x75, 0x82, 0xa7, 0xa9, 0x23, 0xa8, 0x30, 0xeb, 0x96, 0xa2, 0xe9, 0x7c, 0xff, 0xc5, 0xd9, 0x5d, 0xd2, 0x09, 0xea, + 0x5d, 0x01, 0x65, 0x01, 0xcd, 0x12, 0x2a, 0xc0, 0x8c, 0x74, 0x76, 0xcb, 0xc8, 0xd8, 0x53, 0x9e, 0x65, 0x74, 0x24, + 0x69, 0x02, 0x56, 0x0a, 0x23, 0x32, 0x9c, 0xf2, 0x5c, 0x96, 0x85, 0x15, 0xf4, 0xcc, 0x81, 0x9e, 0x85, 0xa3, 0x38, + 0x4d, 0x03, 0x6d, 0x91, 0xcc, 0xf8, 0x15, 0xdd, 0x02, 0x75, 0xb7, 0x06, 0x72, 0x39, 0x0c, 0x75, 0x86, 0xa1, 0x61, + 0x3e, 0x4f, 0xd9, 0x88, 0x96, 0x82, 0xeb, 0x2c, 0x64, 0x59, 0x42, 0x6f, 0x80, 0x8f, 0xa0, 0x5e, 0xaf, 0xd7, 0xc2, + 0x6d, 0x54, 0x68, 0x84, 0x2f, 0x37, 0x10, 0x7b, 0x87, 0xc8, 0x04, 0x22, 0x23, 0xbd, 0xe5, 0x36, 0x7e, 0x40, 0x91, + 0x23, 0x27, 0x99, 0xb5, 0xac, 0x34, 0x6f, 0x46, 0x38, 0xa1, 0x29, 0x95, 0xd4, 0xf2, 0x72, 0xd0, 0x9f, 0xf5, 0xd1, + 0x7d, 0x57, 0xe2, 0xaf, 0x24, 0x27, 0x7b, 0xca, 0xec, 0x9e, 0xe7, 0xa5, 0xa5, 0x5e, 0x6d, 0x4f, 0x85, 0xed, 0xbe, + 0xd4, 0xdb, 0x13, 0x4b, 0x19, 0x8f, 0xa6, 0xda, 0x44, 0x0f, 0x36, 0x96, 0x54, 0x8d, 0x61, 0xf8, 0x7a, 0x79, 0x88, + 0x3e, 0x58, 0x30, 0xb7, 0xa1, 0xe0, 0xcc, 0x30, 0x05, 0x0a, 0x56, 0x9f, 0xde, 0xb6, 0xd3, 0x38, 0x4d, 0x2f, 0xe3, + 0xd1, 0xc7, 0x3a, 0xf5, 0x57, 0x64, 0x40, 0xd6, 0xb9, 0xb1, 0x53, 0xe5, 0xb0, 0x2c, 0x77, 0xdd, 0x96, 0x4b, 0xd7, + 0x0e, 0x4a, 0xb0, 0xd7, 0xaa, 0xc8, 0xbe, 0xbe, 0xd1, 0x3b, 0xa9, 0x5d, 0x41, 0xc4, 0xcc, 0xca, 0x02, 0xe0, 0x02, + 0x9f, 0xa4, 0x38, 0xcb, 0x0f, 0x0c, 0xdd, 0x81, 0xad, 0x51, 0xac, 0x01, 0x22, 0xd1, 0xb2, 0x48, 0x58, 0xbe, 0x1b, + 0x03, 0x7f, 0x08, 0x94, 0xcf, 0x9d, 0x19, 0xee, 0x0b, 0x68, 0xc9, 0xe3, 0x8c, 0xca, 0x5c, 0x42, 0x66, 0xb4, 0x09, + 0xcb, 0x68, 0xfe, 0x06, 0x9a, 0x8b, 0xa2, 0xf7, 0xb7, 0xba, 0x0a, 0x74, 0x32, 0x80, 0x22, 0xef, 0xba, 0xca, 0x44, + 0x8d, 0x02, 0x0c, 0x4f, 0x65, 0x4a, 0xe4, 0x66, 0x35, 0xe3, 0xd1, 0xa8, 0xeb, 0xda, 0xfe, 0x36, 0x2c, 0x97, 0x93, + 0x20, 0x08, 0x72, 0xb0, 0xdf, 0xac, 0x5e, 0x5f, 0x2d, 0x22, 0xdf, 0x58, 0x44, 0x1e, 0x3a, 0x46, 0x16, 0xaa, 0x68, + 0xd9, 0xe9, 0x1e, 0xfd, 0x15, 0xb9, 0x8d, 0x40, 0x59, 0x0d, 0x81, 0x3f, 0xa3, 0x92, 0xdd, 0xa6, 0x44, 0x62, 0x6e, + 0x0c, 0x1c, 0x43, 0x69, 0xc0, 0x30, 0xaa, 0x2e, 0x19, 0xd2, 0x47, 0xa3, 0x66, 0xec, 0x66, 0x98, 0xa3, 0x35, 0xcd, + 0xbe, 0x28, 0x0c, 0x8e, 0x28, 0x32, 0x7b, 0x53, 0x53, 0x89, 0x1d, 0xac, 0xe0, 0x8c, 0x18, 0x35, 0x58, 0x6b, 0x3d, + 0xeb, 0xb8, 0x29, 0xc7, 0x85, 0x83, 0x5a, 0xa1, 0xa6, 0xa6, 0x4f, 0x5a, 0xc5, 0x2a, 0x43, 0x78, 0x6a, 0x35, 0x52, + 0x5e, 0xad, 0x9b, 0x10, 0xdf, 0x7a, 0x23, 0xfc, 0xfe, 0xb2, 0x66, 0x12, 0x46, 0x4e, 0xb3, 0x22, 0x02, 0x96, 0xca, + 0xb7, 0xa1, 0x7b, 0x1b, 0xcd, 0xd4, 0xc6, 0x71, 0x10, 0xce, 0x5d, 0x84, 0x3b, 0x98, 0xcd, 0x34, 0xe7, 0xca, 0x86, + 0x64, 0x5a, 0xef, 0x1b, 0x50, 0xcc, 0xf5, 0x3e, 0x6c, 0x20, 0x71, 0x5d, 0xf1, 0x54, 0x24, 0x08, 0x06, 0x6c, 0x0e, + 0xca, 0x9d, 0x2b, 0x1f, 0x02, 0x80, 0x9d, 0xad, 0x56, 0x1b, 0x44, 0xb7, 0x55, 0xff, 0x44, 0x61, 0x65, 0x14, 0xae, + 0x56, 0xd7, 0x12, 0x05, 0x46, 0xf3, 0xc5, 0x14, 0xf5, 0x2d, 0xc7, 0x3d, 0x79, 0x05, 0xad, 0x94, 0x22, 0x5a, 0x95, + 0x94, 0x26, 0x43, 0x9d, 0x66, 0xeb, 0xfb, 0x24, 0x1d, 0xb6, 0x7d, 0xba, 0xc1, 0xbd, 0x54, 0xa1, 0x11, 0xd3, 0xd5, + 0x92, 0x4f, 0xcd, 0xd0, 0x0c, 0x21, 0x14, 0xe5, 0xca, 0x8a, 0xd9, 0xdb, 0x66, 0x58, 0x1e, 0x1c, 0xe4, 0xce, 0x40, + 0xe7, 0x25, 0x9b, 0xf8, 0x29, 0x00, 0x91, 0x9c, 0xdf, 0x66, 0x4a, 0x77, 0xf9, 0xc9, 0x0a, 0xa1, 0x0d, 0xb3, 0xb4, + 0xd5, 0x05, 0x6b, 0x3c, 0xbe, 0x8e, 0x99, 0xf4, 0xca, 0x51, 0xb4, 0x35, 0x1e, 0x50, 0xb4, 0x34, 0xaa, 0x46, 0x28, + 0x28, 0x28, 0x8f, 0xc0, 0x13, 0xac, 0x0a, 0xad, 0xe9, 0x7e, 0x34, 0xa5, 0xe0, 0x08, 0xb6, 0x5a, 0x44, 0x69, 0x17, + 0xee, 0x19, 0x29, 0x62, 0x06, 0xde, 0x0e, 0x7b, 0xb1, 0xde, 0xbd, 0x66, 0x07, 0xcc, 0xa9, 0x18, 0x73, 0x31, 0xb3, + 0x75, 0xc5, 0xda, 0xb3, 0xe1, 0x8c, 0x6c, 0x1c, 0x6c, 0x1d, 0xdb, 0xa8, 0xff, 0xdd, 0x35, 0xa3, 0xbb, 0x32, 0xd7, + 0x6b, 0xa2, 0xb4, 0x94, 0xbe, 0xda, 0x1f, 0x68, 0x29, 0x33, 0x77, 0xcd, 0x7b, 0xe3, 0x4c, 0xed, 0x6a, 0x87, 0xc9, + 0x5e, 0xbb, 0x5b, 0xda, 0x7c, 0x96, 0x1a, 0xba, 0xda, 0xb1, 0x61, 0x44, 0x2a, 0x5f, 0xa4, 0x89, 0x01, 0x96, 0x21, + 0x4c, 0x0d, 0x1d, 0x5d, 0xb3, 0x34, 0xad, 0x4a, 0x7f, 0x0d, 0x5f, 0xcf, 0x0d, 0x5f, 0xcf, 0x2c, 0x5f, 0x07, 0x4e, + 0x01, 0x7c, 0x5d, 0x0f, 0x57, 0x75, 0xcf, 0x36, 0x4e, 0x67, 0xa6, 0x39, 0x7a, 0xae, 0xec, 0x68, 0x98, 0x6f, 0x61, + 0x21, 0x40, 0xa5, 0xe6, 0xf5, 0x31, 0x30, 0x4e, 0x18, 0x30, 0x00, 0xb5, 0x0b, 0x93, 0xba, 0x2e, 0x8a, 0x8f, 0x01, + 0xc2, 0x79, 0x41, 0x4b, 0xca, 0x3e, 0x79, 0x01, 0x4e, 0x3a, 0x67, 0x39, 0x20, 0xc4, 0x54, 0xf1, 0xaf, 0x52, 0xa2, + 0xec, 0xea, 0x98, 0x59, 0x5d, 0x6e, 0x57, 0x07, 0x9c, 0xbe, 0x5a, 0x5d, 0x72, 0x37, 0xaf, 0x57, 0xcb, 0x63, 0xe5, + 0xf2, 0xaa, 0xfd, 0x5e, 0xad, 0x82, 0xb5, 0x12, 0xf0, 0xdf, 0x1b, 0x13, 0x45, 0x94, 0xa3, 0x03, 0x0f, 0x70, 0x31, + 0x03, 0x05, 0x85, 0x5e, 0x74, 0x29, 0xe2, 0x5e, 0x7d, 0xca, 0xc1, 0xa3, 0xdc, 0xf4, 0xba, 0xff, 0x29, 0x9f, 0xcd, + 0x41, 0x1b, 0x5b, 0x23, 0xe9, 0x09, 0x35, 0x13, 0x56, 0xf5, 0xc5, 0x96, 0xb2, 0x5a, 0x1f, 0x75, 0x1e, 0x6b, 0xd4, + 0x54, 0xda, 0xcb, 0x7b, 0xad, 0x62, 0x51, 0x16, 0x95, 0x8c, 0x63, 0x9b, 0x53, 0xe5, 0x74, 0xdd, 0x25, 0x63, 0x2b, + 0xde, 0x06, 0x4c, 0xf3, 0x61, 0x06, 0xbc, 0xce, 0x61, 0x3f, 0x96, 0xdc, 0xdd, 0xfd, 0x2f, 0x2a, 0xe4, 0x2c, 0x8b, + 0x35, 0xf4, 0x2d, 0x8b, 0xe2, 0x44, 0x1b, 0xd9, 0xf8, 0x64, 0xb7, 0x35, 0x5c, 0xd5, 0x19, 0x63, 0x71, 0x30, 0xc4, + 0x27, 0x9b, 0xaa, 0x23, 0x59, 0xce, 0x78, 0x42, 0x23, 0x9f, 0xcf, 0x69, 0xe6, 0x17, 0xe0, 0x55, 0x35, 0x7b, 0x3f, + 0x92, 0xc1, 0xf2, 0x5d, 0xdd, 0xbd, 0x1a, 0x9d, 0x14, 0xe0, 0xfd, 0xfa, 0x62, 0xd3, 0xf1, 0xfa, 0x2d, 0x15, 0xb9, + 0x52, 0x44, 0x4b, 0x9d, 0xf6, 0x8b, 0x4a, 0x2c, 0x7d, 0x11, 0xed, 0x6c, 0x5f, 0x99, 0x20, 0x7e, 0x3b, 0x7c, 0x1c, + 0x1e, 0xf9, 0x48, 0xb9, 0x85, 0xbf, 0x32, 0x07, 0xfe, 0xb9, 0x75, 0x0b, 0xbf, 0x20, 0xcf, 0xeb, 0x5e, 0xe1, 0x44, + 0x92, 0x17, 0xfd, 0x17, 0xd6, 0x62, 0xe6, 0x29, 0x1b, 0xdd, 0x06, 0x7e, 0xca, 0x64, 0x13, 0x42, 0x6f, 0x3e, 0x5e, + 0xea, 0x0a, 0x70, 0x29, 0x2a, 0x77, 0x76, 0x61, 0x6d, 0x3d, 0x2c, 0x25, 0xf1, 0xf7, 0x53, 0x26, 0xf7, 0x7d, 0x3c, + 0x23, 0x17, 0xf0, 0x63, 0x7f, 0x19, 0xbc, 0x8e, 0xe5, 0x34, 0x14, 0x71, 0x96, 0xf0, 0x59, 0x80, 0x1a, 0xbe, 0x8f, + 0xc2, 0x5c, 0xd9, 0x1b, 0x9f, 0xa3, 0x62, 0xff, 0x02, 0xdf, 0x48, 0xe2, 0xf7, 0xfd, 0xc6, 0x0c, 0xbf, 0x91, 0xe4, + 0xe2, 0x78, 0x7f, 0x79, 0x23, 0x8b, 0xde, 0x05, 0xbe, 0x29, 0x3d, 0xf6, 0xf8, 0x6b, 0x12, 0x20, 0xd2, 0xbb, 0x31, + 0xd0, 0x9c, 0xf2, 0x99, 0xf6, 0xdc, 0xfb, 0x08, 0x7f, 0x80, 0xb8, 0x8a, 0xa8, 0xb8, 0x8d, 0x09, 0xad, 0xec, 0x11, + 0x9f, 0x2b, 0x17, 0x81, 0x7f, 0x70, 0xe0, 0x94, 0x95, 0xaa, 0x02, 0x3e, 0x91, 0xa4, 0x66, 0x90, 0xe3, 0xf7, 0x2a, + 0x42, 0x73, 0x22, 0x03, 0x81, 0xec, 0x30, 0x81, 0xf5, 0x43, 0x9b, 0xa3, 0x29, 0x06, 0xda, 0xc3, 0x10, 0x32, 0x49, + 0x45, 0x2c, 0xb9, 0x18, 0x22, 0x57, 0xfd, 0xc0, 0x7f, 0x23, 0x17, 0x03, 0xef, 0x3f, 0xfd, 0xd3, 0x8f, 0xe3, 0x1f, + 0xc5, 0xf0, 0x02, 0xbf, 0x25, 0x87, 0xc7, 0x41, 0x3f, 0x0a, 0xf6, 0x9a, 0xcd, 0xd5, 0x8f, 0x87, 0x83, 0x7f, 0xc4, + 0xcd, 0x5f, 0x4e, 0x9a, 0x3f, 0x0c, 0xd1, 0x2a, 0xf8, 0xf1, 0xb0, 0x3f, 0x30, 0x4f, 0x83, 0x7f, 0xf4, 0x7e, 0xcc, + 0x87, 0x7f, 0xd6, 0x85, 0xfb, 0x08, 0x1d, 0x4e, 0xf0, 0x42, 0x92, 0xc3, 0x66, 0xb3, 0x77, 0x38, 0xc1, 0x73, 0x49, + 0x0e, 0xe1, 0xff, 0x4b, 0xf2, 0x8e, 0x4e, 0x5e, 0xdc, 0xcc, 0x83, 0x8b, 0xde, 0x6a, 0x7f, 0xf9, 0xb7, 0x02, 0x46, + 0x1d, 0xfc, 0xe3, 0xc7, 0x1f, 0x73, 0xff, 0x41, 0x8f, 0x1c, 0x0e, 0x1b, 0x28, 0x80, 0xd2, 0x3f, 0x13, 0xf5, 0x6f, + 0xd0, 0x8f, 0x06, 0xff, 0x30, 0x50, 0xf8, 0x0f, 0x7e, 0xbc, 0x38, 0xee, 0x91, 0xe1, 0x2a, 0xf0, 0x57, 0x0f, 0xd0, + 0x0a, 0xa1, 0xd5, 0x3e, 0xba, 0xc0, 0xfe, 0xc4, 0x47, 0x78, 0x22, 0xc9, 0xe1, 0x83, 0xc3, 0x09, 0xbe, 0x92, 0xe4, + 0xd0, 0x3f, 0x9c, 0xe0, 0x17, 0x92, 0x1c, 0xfe, 0x23, 0xe8, 0x47, 0xda, 0xc3, 0xb6, 0x52, 0xee, 0x8d, 0x15, 0x04, + 0x37, 0x62, 0x41, 0xe3, 0x95, 0x64, 0x32, 0xa5, 0x68, 0xff, 0x90, 0xe1, 0x33, 0x85, 0xa6, 0x40, 0x82, 0x13, 0x06, + 0x6c, 0xbb, 0x60, 0x79, 0x0e, 0x9b, 0x0d, 0x34, 0xb3, 0x1f, 0x09, 0xac, 0xfd, 0x00, 0x79, 0x24, 0xf1, 0x55, 0x9c, + 0x2e, 0x68, 0x1e, 0xd1, 0x02, 0xe1, 0x11, 0x39, 0x93, 0x41, 0x1b, 0xe1, 0x77, 0x12, 0x7e, 0x74, 0x10, 0x3e, 0x33, + 0x01, 0x4c, 0x38, 0xc8, 0x9a, 0xa8, 0x32, 0xae, 0x35, 0x16, 0x1f, 0xe1, 0xf9, 0x96, 0x4a, 0x39, 0x05, 0xef, 0x02, + 0xc2, 0xe3, 0x5a, 0xb8, 0x13, 0x5f, 0x13, 0x4b, 0x12, 0xef, 0x05, 0xa5, 0xdf, 0xc5, 0xe9, 0x47, 0x2a, 0x82, 0x1b, + 0xdc, 0xee, 0x7c, 0x8e, 0x95, 0x0b, 0x7a, 0xaf, 0x8d, 0xba, 0x65, 0xac, 0xea, 0x54, 0xea, 0x18, 0x01, 0x08, 0xd9, + 0xba, 0x2f, 0x06, 0x76, 0x7c, 0x4f, 0x6c, 0x38, 0xac, 0x44, 0x7c, 0xed, 0xa3, 0x7a, 0x5c, 0x94, 0x65, 0x57, 0x71, + 0xca, 0x12, 0x4f, 0xd2, 0xd9, 0x3c, 0x8d, 0x25, 0xf5, 0xcc, 0x7a, 0xbd, 0x18, 0x06, 0xf2, 0x4b, 0x95, 0x21, 0x71, + 0x0c, 0xce, 0xc4, 0x06, 0x9c, 0xe0, 0xac, 0x04, 0x10, 0x9d, 0x32, 0x6a, 0xc7, 0xeb, 0x2a, 0xf8, 0xb5, 0x1e, 0xdf, + 0x6b, 0xb6, 0xc1, 0x11, 0x36, 0x54, 0xe2, 0x39, 0xc7, 0x19, 0x01, 0x21, 0xda, 0xe9, 0xfb, 0xc7, 0xf9, 0xd5, 0xa4, + 0xe7, 0x43, 0x6c, 0x86, 0x93, 0xb7, 0xca, 0x2f, 0x04, 0x0d, 0xa6, 0xa4, 0xd5, 0x9d, 0x1e, 0xd3, 0xee, 0xb4, 0xd1, + 0xb0, 0x3a, 0x74, 0x4a, 0xc4, 0x60, 0xaa, 0xbb, 0xc7, 0x38, 0xc1, 0x0b, 0xd2, 0x6c, 0xe3, 0x09, 0x69, 0xa9, 0x2e, + 0xdd, 0xc9, 0x71, 0x6a, 0xa6, 0x39, 0x38, 0x08, 0x78, 0x98, 0xc6, 0xb9, 0xfc, 0x12, 0x8c, 0x7d, 0x32, 0xc1, 0x09, + 0xe1, 0x21, 0xbd, 0xa1, 0xa3, 0x20, 0x45, 0x38, 0x31, 0x9c, 0x06, 0x75, 0xd1, 0x84, 0x38, 0xcd, 0xc0, 0x88, 0x20, + 0x6f, 0xfb, 0xc9, 0xa0, 0x3d, 0x24, 0x84, 0xf8, 0x7b, 0xcd, 0xa6, 0xdf, 0xe7, 0x64, 0x21, 0x23, 0x28, 0x71, 0x54, + 0x65, 0x32, 0x87, 0xa2, 0x8e, 0x53, 0x14, 0xbc, 0x90, 0xa1, 0xa4, 0xb9, 0x0c, 0xa0, 0x18, 0xcc, 0xff, 0xdc, 0x12, + 0xb6, 0x7f, 0x7c, 0xe8, 0x37, 0xa0, 0x54, 0x11, 0x27, 0xc2, 0x9c, 0x5c, 0xa2, 0x28, 0x19, 0x1c, 0x0d, 0x5d, 0xfe, + 0xaf, 0x0a, 0x61, 0xf2, 0xcb, 0x7e, 0x32, 0x68, 0xa9, 0xc9, 0x7b, 0x7e, 0x3f, 0xe0, 0x24, 0xd7, 0x0a, 0x5a, 0x3f, + 0x8f, 0xde, 0xaa, 0xa5, 0xa2, 0xc8, 0x00, 0x67, 0xe6, 0x5d, 0x90, 0x66, 0x27, 0x0a, 0x16, 0xee, 0x22, 0x9a, 0x30, + 0x99, 0xc1, 0x02, 0x8e, 0x09, 0xb4, 0xc7, 0x9c, 0xc0, 0x8c, 0x55, 0xb7, 0xcb, 0xc8, 0x3c, 0x3f, 0xf0, 0x1f, 0xf4, + 0xaf, 0x64, 0x34, 0x91, 0x7a, 0xfa, 0x2b, 0xb9, 0x5a, 0xc1, 0xff, 0x13, 0xd9, 0xe7, 0xe4, 0x52, 0x15, 0x2d, 0x4c, + 0xd1, 0x1c, 0x8a, 0xde, 0x46, 0x00, 0x2a, 0xce, 0x4b, 0x25, 0x4b, 0xef, 0xc9, 0x15, 0x51, 0xb0, 0x1f, 0x1c, 0x88, + 0xc1, 0xb4, 0xd1, 0x1e, 0x82, 0x7f, 0x5f, 0xc8, 0xfc, 0x3b, 0x26, 0xa7, 0x81, 0x7f, 0xd8, 0xf3, 0x51, 0xdf, 0xf7, + 0x60, 0x6b, 0xbb, 0x59, 0x83, 0x68, 0x0c, 0xa7, 0x8d, 0x37, 0x32, 0x5a, 0xf4, 0x48, 0xab, 0x1f, 0x30, 0xe3, 0xcf, + 0x43, 0x38, 0x35, 0x8c, 0xb3, 0x85, 0x17, 0xa8, 0x21, 0x65, 0xc3, 0x3e, 0x2f, 0x50, 0x63, 0xd6, 0xb8, 0x42, 0x51, + 0xda, 0x98, 0x35, 0x82, 0x05, 0x21, 0xa4, 0xd9, 0x29, 0xbb, 0x59, 0xe9, 0x37, 0x45, 0xd1, 0x95, 0x75, 0x76, 0x0e, + 0xd4, 0x71, 0xc8, 0x1a, 0x81, 0x18, 0xd0, 0xe1, 0x6a, 0xe5, 0x1f, 0xf7, 0x7b, 0x3e, 0x6a, 0x04, 0x96, 0xd0, 0x0e, + 0x2d, 0xa5, 0x21, 0x84, 0xd9, 0xb0, 0x30, 0xa1, 0xa4, 0x97, 0xb5, 0xb0, 0xd1, 0xb2, 0x3a, 0xec, 0x0e, 0x0f, 0xa0, + 0x45, 0x69, 0xc7, 0x68, 0x7d, 0x75, 0x0e, 0xcb, 0xb4, 0xc4, 0x9c, 0x91, 0x16, 0xe6, 0xc4, 0xfa, 0xae, 0xa7, 0x44, + 0x56, 0x04, 0x9f, 0x92, 0xaa, 0x39, 0x1e, 0xc4, 0x38, 0x19, 0x92, 0xd7, 0xda, 0x1e, 0xe9, 0x5a, 0xbf, 0x38, 0x4d, + 0xc9, 0xcb, 0xb5, 0xe8, 0x6d, 0x0c, 0xb1, 0x95, 0xeb, 0x70, 0xb4, 0x10, 0x82, 0x66, 0xf2, 0x0d, 0x4f, 0x8c, 0x9a, + 0x46, 0x53, 0xb0, 0x94, 0x20, 0x2c, 0x8b, 0x41, 0x47, 0xeb, 0xd8, 0x93, 0xb1, 0xd8, 0xa8, 0x9e, 0x90, 0x85, 0x56, + 0x9f, 0x54, 0xb0, 0xb6, 0x3b, 0x31, 0x76, 0x71, 0x80, 0xf0, 0xc2, 0x44, 0x71, 0x83, 0x30, 0x0c, 0x27, 0xe1, 0x08, + 0xaa, 0x61, 0x82, 0x1c, 0x15, 0xea, 0x1c, 0x05, 0x39, 0xb9, 0x0e, 0x33, 0x7a, 0xa3, 0x66, 0x0d, 0x50, 0x25, 0x99, + 0xed, 0xf1, 0x3a, 0x9e, 0x76, 0x15, 0xbb, 0xc9, 0xc3, 0x8c, 0x27, 0x14, 0xd0, 0x03, 0x71, 0x7b, 0x53, 0x34, 0x8d, + 0x73, 0x37, 0x3e, 0x55, 0xc1, 0x37, 0x70, 0x9d, 0xd7, 0x13, 0xf0, 0xf8, 0x2a, 0x5d, 0xab, 0x6c, 0xac, 0xdd, 0xe0, + 0x08, 0xb1, 0x71, 0x30, 0x09, 0x21, 0xae, 0xa7, 0x48, 0x48, 0x82, 0x29, 0x37, 0x71, 0x89, 0x6a, 0x56, 0x8e, 0x79, + 0x45, 0x92, 0x01, 0x6f, 0x34, 0x94, 0x17, 0x7a, 0xa1, 0x49, 0x62, 0x82, 0xf0, 0x55, 0x79, 0xb6, 0x6c, 0xbb, 0xb7, + 0x92, 0xd4, 0xa7, 0x0a, 0xae, 0xea, 0xee, 0xdc, 0x86, 0x94, 0x48, 0x79, 0x0a, 0x65, 0x30, 0x43, 0xf8, 0x19, 0x39, + 0x0c, 0x06, 0x61, 0xff, 0x2f, 0x43, 0xd4, 0x0f, 0xc2, 0x3f, 0xa3, 0x43, 0xcd, 0x39, 0xae, 0x50, 0x37, 0xd5, 0x73, + 0x2c, 0x55, 0xfc, 0xb2, 0x8d, 0x95, 0x27, 0x31, 0xca, 0x70, 0x16, 0xcf, 0x68, 0xf4, 0x0c, 0x0e, 0xb9, 0x25, 0x9c, + 0xb7, 0x12, 0x03, 0x25, 0x45, 0xcf, 0x0c, 0x2f, 0x09, 0xfd, 0xfe, 0x2b, 0x59, 0x3e, 0xf5, 0xfd, 0xfe, 0xf3, 0xea, + 0xe9, 0x2f, 0x7e, 0xff, 0x17, 0x19, 0xfd, 0x5c, 0x18, 0x6f, 0x77, 0x6d, 0x8e, 0xc7, 0x76, 0x8e, 0x42, 0x6f, 0x8d, + 0x83, 0xbb, 0x05, 0xda, 0x74, 0x74, 0x4c, 0x50, 0xc1, 0xc6, 0x25, 0x33, 0xca, 0x43, 0x19, 0x4f, 0x00, 0xa9, 0xce, + 0x1e, 0xe4, 0x6e, 0x5c, 0xbf, 0x5a, 0x31, 0x90, 0x8a, 0xa5, 0x57, 0x40, 0xe6, 0xa4, 0xd7, 0x42, 0xcb, 0x5a, 0x5b, + 0xa5, 0x33, 0xd5, 0xe3, 0xe8, 0x25, 0x9f, 0xbe, 0x22, 0xad, 0xee, 0xd5, 0xf1, 0xa4, 0x7b, 0xd5, 0x68, 0xa0, 0xdc, + 0x92, 0xd6, 0x62, 0x70, 0x35, 0xc4, 0x5f, 0x83, 0x53, 0xcf, 0xa5, 0x25, 0x5c, 0x5b, 0x5e, 0xc7, 0x2c, 0xaf, 0xd1, + 0xc8, 0x0a, 0xd4, 0x75, 0xba, 0x4e, 0x74, 0xd7, 0xa2, 0xd0, 0x38, 0x59, 0x27, 0xb5, 0xa7, 0x48, 0x95, 0x40, 0x32, + 0x14, 0x21, 0xe4, 0x46, 0xa2, 0xad, 0xa3, 0xc2, 0x98, 0xd0, 0x5d, 0x9d, 0x59, 0x60, 0x9f, 0x5a, 0x4a, 0x04, 0x80, + 0x05, 0xe8, 0x5a, 0x7a, 0x82, 0x67, 0x78, 0xd1, 0x68, 0x2b, 0x32, 0x6f, 0xb6, 0xbb, 0xf5, 0xb1, 0x9e, 0x54, 0x63, + 0xe1, 0x45, 0x83, 0xcc, 0x4a, 0x2c, 0x15, 0x59, 0xa3, 0x51, 0xd4, 0x83, 0x9d, 0xf6, 0xe4, 0xd6, 0x02, 0x10, 0x37, + 0xeb, 0x49, 0x19, 0x56, 0xc2, 0x56, 0x32, 0x95, 0x85, 0x2c, 0xcb, 0xa8, 0x00, 0x29, 0x4a, 0x24, 0x66, 0x45, 0x51, + 0x49, 0x76, 0x10, 0xa3, 0x98, 0x12, 0x01, 0x9c, 0x47, 0xd9, 0x5d, 0x38, 0xc3, 0x1c, 0x4f, 0x15, 0xdf, 0x20, 0x84, + 0x9c, 0xd9, 0x74, 0x16, 0xa9, 0x78, 0x50, 0x4a, 0x98, 0x23, 0x93, 0x72, 0x42, 0xc3, 0xf3, 0xfd, 0x53, 0x7e, 0xa7, + 0x4d, 0x36, 0x60, 0xc3, 0x48, 0x35, 0x4b, 0x0d, 0xe7, 0x8a, 0xc9, 0x87, 0x40, 0xa2, 0x32, 0x3a, 0x12, 0x2a, 0x06, + 0xf8, 0x9c, 0x09, 0xaa, 0x74, 0xf0, 0x7d, 0x6b, 0xf7, 0xa5, 0x75, 0x05, 0x32, 0x75, 0xbd, 0x37, 0x80, 0xc8, 0x18, + 0x9c, 0x3b, 0x19, 0xd9, 0x68, 0x76, 0xbe, 0x7f, 0xf2, 0x76, 0x9b, 0x0d, 0xbc, 0x5a, 0x19, 0xeb, 0x57, 0xe9, 0x36, + 0x38, 0xae, 0x20, 0x4d, 0xcd, 0x8f, 0x28, 0x48, 0x95, 0x8a, 0x14, 0x07, 0x02, 0xa8, 0xe8, 0x7c, 0xff, 0xe4, 0x7d, + 0x20, 0x94, 0x6f, 0x09, 0x61, 0x77, 0xd9, 0x01, 0x27, 0xc1, 0x94, 0x50, 0xa4, 0xd7, 0x5e, 0xb2, 0x2e, 0xee, 0x08, + 0xf0, 0x68, 0xaa, 0x2a, 0xc1, 0x82, 0x18, 0xb0, 0x21, 0x49, 0x0d, 0x06, 0x48, 0x8a, 0x70, 0x5a, 0xb3, 0xcb, 0x08, + 0x6c, 0x80, 0x9a, 0xeb, 0x0c, 0x76, 0x22, 0xd4, 0xaa, 0x1f, 0xc2, 0xa9, 0x9a, 0x55, 0x16, 0x5a, 0x78, 0x3c, 0xdb, + 0xc8, 0x4a, 0xab, 0xcc, 0xd1, 0x6f, 0xc1, 0x76, 0xb2, 0x0f, 0x6f, 0x88, 0xb5, 0x24, 0x4c, 0xc1, 0x73, 0x9b, 0x3e, + 0x76, 0xbe, 0x7f, 0xf2, 0xda, 0x64, 0x90, 0xcd, 0x63, 0xcb, 0xef, 0x37, 0x4c, 0xcc, 0x93, 0xd7, 0x61, 0x55, 0xab, + 0x1a, 0x9f, 0xef, 0x9f, 0x7c, 0xd8, 0xd6, 0x0c, 0xca, 0x8b, 0x45, 0x65, 0xe3, 0x2b, 0xf8, 0x96, 0x34, 0x8d, 0x96, + 0x46, 0x38, 0x44, 0xac, 0xc0, 0x4a, 0x20, 0x45, 0x79, 0x51, 0xba, 0x46, 0x9e, 0xe3, 0x8c, 0xa8, 0x30, 0x50, 0x7d, + 0xd7, 0x8c, 0x9a, 0xc7, 0x78, 0x76, 0x36, 0xe2, 0x73, 0xba, 0x23, 0x36, 0x74, 0x83, 0x42, 0x36, 0x83, 0xd4, 0x19, + 0x05, 0x3a, 0xc3, 0x7b, 0x2d, 0xd4, 0xad, 0x8b, 0xaf, 0x4c, 0x11, 0x29, 0xaf, 0xc9, 0x16, 0x3c, 0x25, 0x2d, 0x9c, + 0x92, 0x16, 0x8e, 0x49, 0x3e, 0x68, 0x69, 0x01, 0xd1, 0x8d, 0xcb, 0x71, 0xb5, 0x98, 0x81, 0xac, 0x30, 0x73, 0x5a, + 0xb5, 0x00, 0x4e, 0xba, 0xb1, 0xf2, 0x3d, 0x2a, 0x99, 0x9e, 0x28, 0xb2, 0x78, 0x1f, 0x70, 0xcc, 0xd5, 0xc0, 0x67, + 0xec, 0x32, 0x85, 0xc4, 0x12, 0x58, 0x15, 0x96, 0x28, 0x2a, 0x9b, 0xb6, 0x4d, 0xd3, 0x38, 0x54, 0xfb, 0xc4, 0x71, + 0x1c, 0x02, 0xe7, 0xc6, 0xb1, 0xc9, 0xc3, 0xc9, 0x37, 0xbb, 0x3c, 0x3e, 0x38, 0x08, 0x74, 0xa7, 0x2f, 0x65, 0xc0, + 0x6d, 0x7d, 0x15, 0xb9, 0xfb, 0x56, 0xf3, 0x8a, 0x04, 0x29, 0xf8, 0x1b, 0x8d, 0x74, 0x58, 0x40, 0x18, 0x3a, 0x88, + 0xeb, 0x18, 0xb4, 0xc0, 0x2b, 0x5d, 0xaf, 0xbe, 0xfc, 0x46, 0xa3, 0x8c, 0xd2, 0xd6, 0xb1, 0x75, 0x83, 0xb3, 0xe2, + 0x2a, 0x28, 0x53, 0x7f, 0x5a, 0x1b, 0xf9, 0x52, 0x16, 0x04, 0xc4, 0x5c, 0x9a, 0x65, 0x76, 0x31, 0xce, 0x91, 0x60, + 0xd0, 0xee, 0x4b, 0x93, 0xb5, 0x80, 0x55, 0x76, 0x95, 0x69, 0x64, 0xd9, 0x59, 0x07, 0x45, 0xb6, 0x11, 0x44, 0xa5, + 0xa0, 0x51, 0xa3, 0x30, 0xe4, 0xfd, 0x7e, 0x33, 0xe7, 0x12, 0xe7, 0xc8, 0x38, 0xb9, 0x14, 0x14, 0x0a, 0x59, 0x9d, + 0x12, 0x29, 0x2f, 0xc9, 0x7c, 0x37, 0xc9, 0x9f, 0x38, 0x24, 0xff, 0x8c, 0x50, 0x87, 0xfc, 0xb5, 0x8b, 0x23, 0xe4, + 0xc6, 0xb9, 0x90, 0xdb, 0xaa, 0xd3, 0x39, 0x01, 0x27, 0x5a, 0x1d, 0xa3, 0xb5, 0xb0, 0xe2, 0x0e, 0x86, 0xe2, 0x9e, + 0x10, 0xe5, 0x86, 0xc4, 0x36, 0x06, 0x1c, 0x54, 0x41, 0x35, 0x98, 0x7a, 0x9b, 0x4f, 0xcf, 0xe5, 0x80, 0x27, 0x1f, + 0xee, 0x8e, 0x87, 0x9e, 0xce, 0x37, 0x4f, 0xae, 0x93, 0xfb, 0x09, 0xab, 0x76, 0x0e, 0x6e, 0x3d, 0x13, 0x14, 0xe6, + 0x2f, 0xe3, 0xd8, 0x75, 0xe6, 0xb3, 0x76, 0x08, 0xad, 0xfc, 0x03, 0x68, 0xdb, 0x6d, 0xd5, 0x82, 0x3a, 0xc3, 0x02, + 0x3f, 0xd2, 0x19, 0xa8, 0xb1, 0xd8, 0xc1, 0x3e, 0x4e, 0x54, 0x03, 0x9a, 0x25, 0xdb, 0xab, 0x9f, 0x15, 0x86, 0x4c, + 0x34, 0x68, 0x68, 0x09, 0xfc, 0x4f, 0x93, 0x3c, 0xd0, 0x8d, 0x92, 0x0b, 0x80, 0xa0, 0xb9, 0xc2, 0x53, 0x85, 0x30, + 0xdf, 0xaf, 0xbc, 0xef, 0x2f, 0xf7, 0x08, 0x99, 0x57, 0xde, 0xc7, 0x77, 0x55, 0xea, 0x15, 0x90, 0x05, 0x8a, 0xc0, + 0x7c, 0x2c, 0x0b, 0x74, 0xf8, 0xf2, 0xcc, 0x36, 0x57, 0x26, 0x64, 0x58, 0x69, 0xdc, 0x4e, 0x68, 0x53, 0xb9, 0xe5, + 0x74, 0xbd, 0x45, 0xc3, 0x5a, 0xed, 0x3e, 0xd4, 0xbe, 0x97, 0x0a, 0x46, 0x78, 0x7e, 0xaf, 0x5a, 0xdb, 0x71, 0x8b, + 0x8f, 0xeb, 0xf9, 0x2b, 0x6b, 0x9b, 0x12, 0xb2, 0x2c, 0xa7, 0x42, 0x3e, 0xa3, 0x63, 0x2e, 0x20, 0x66, 0x51, 0xe2, + 0x04, 0x15, 0xfb, 0x8e, 0xdf, 0x4e, 0xad, 0xcf, 0x09, 0x14, 0xac, 0x2d, 0x50, 0xfd, 0xfa, 0xa8, 0x82, 0xd6, 0xe7, + 0xeb, 0xbd, 0xe6, 0x07, 0x07, 0x1f, 0x2a, 0x34, 0x19, 0x28, 0x15, 0x14, 0x0e, 0xd3, 0xd2, 0x2a, 0x8d, 0x89, 0xe4, + 0xee, 0x07, 0xa5, 0x13, 0xc0, 0x32, 0x0c, 0x97, 0xf7, 0xbc, 0x24, 0xb2, 0x98, 0xac, 0xb3, 0x78, 0xe3, 0x9c, 0x60, + 0xae, 0xe1, 0x02, 0x1c, 0x1e, 0x4c, 0x6d, 0xed, 0x2d, 0xca, 0xab, 0x64, 0xd8, 0x12, 0x86, 0x53, 0x40, 0x56, 0xa0, + 0xcc, 0x10, 0x87, 0x02, 0xb7, 0x9a, 0x25, 0xa7, 0xa0, 0x57, 0x4e, 0x71, 0x1e, 0x4e, 0x21, 0xfd, 0xb5, 0x76, 0x64, + 0x11, 0xc2, 0x3a, 0x31, 0xc7, 0x49, 0x25, 0x38, 0x79, 0xb9, 0xcd, 0xa5, 0x6c, 0x89, 0x9a, 0x2a, 0xa9, 0xa3, 0x5a, + 0xa0, 0xb2, 0x43, 0x78, 0x15, 0x30, 0xa3, 0xb8, 0xd9, 0xb8, 0x19, 0x30, 0xe0, 0x67, 0x32, 0xd0, 0xc1, 0x28, 0x90, + 0x19, 0x3c, 0x5c, 0x04, 0xb5, 0xa9, 0xbb, 0x5c, 0x75, 0xc3, 0x06, 0x71, 0x53, 0x17, 0x4d, 0x5c, 0xc5, 0xf5, 0x4e, + 0x2b, 0x5e, 0x3a, 0xd6, 0x19, 0xd4, 0xd2, 0x72, 0xc1, 0x2a, 0x91, 0xc4, 0x59, 0xfe, 0x58, 0x27, 0x45, 0x97, 0x8d, + 0x30, 0x55, 0x60, 0xbc, 0x54, 0x7b, 0x40, 0x0b, 0xa0, 0xaf, 0xe5, 0x89, 0x74, 0x76, 0xd4, 0x3a, 0xb1, 0xd5, 0x9c, + 0x8e, 0xd4, 0x7f, 0x07, 0xa9, 0x2e, 0xeb, 0x67, 0xfe, 0xa5, 0x92, 0x85, 0x0c, 0xe7, 0x35, 0xc6, 0x9e, 0x29, 0xc6, + 0x8e, 0x40, 0x4f, 0xb3, 0x89, 0xdf, 0x7d, 0x93, 0xf1, 0xc2, 0x8c, 0x94, 0x33, 0x24, 0xf6, 0x75, 0x19, 0x2d, 0x77, + 0x7e, 0xaf, 0xed, 0x46, 0xc4, 0x08, 0x64, 0x01, 0x61, 0xc3, 0xd9, 0x33, 0x84, 0xf3, 0x46, 0xa3, 0x9b, 0x1f, 0xd3, + 0xca, 0x49, 0x52, 0xc1, 0xc8, 0x20, 0xa0, 0x0b, 0x04, 0x5f, 0x93, 0xa1, 0x10, 0xf2, 0xb7, 0x99, 0xd9, 0x39, 0xf8, + 0xda, 0x4f, 0xde, 0x05, 0x2e, 0x57, 0x73, 0xdb, 0x96, 0x41, 0x53, 0x58, 0x4f, 0x50, 0x05, 0x5c, 0xbe, 0xbe, 0x3b, + 0xc1, 0x03, 0xe0, 0xde, 0x6b, 0x63, 0x48, 0x45, 0x43, 0x5d, 0xa9, 0x59, 0x42, 0x79, 0xfa, 0xba, 0xa8, 0xca, 0x4a, + 0x74, 0x27, 0xeb, 0xca, 0xca, 0x98, 0x95, 0x24, 0x2f, 0x8a, 0x9c, 0x56, 0xe1, 0xfd, 0xb5, 0xf4, 0x4b, 0x25, 0x5c, + 0x36, 0xbd, 0xed, 0xa7, 0x73, 0x22, 0xb1, 0x43, 0xa8, 0x5f, 0xef, 0x8a, 0x7d, 0x54, 0x60, 0xc2, 0xb9, 0x36, 0x42, + 0xf1, 0xe7, 0x6d, 0x42, 0x11, 0x67, 0xe6, 0xc8, 0x2b, 0x81, 0xd8, 0xbe, 0x87, 0x40, 0x34, 0x6e, 0x76, 0x2b, 0x13, + 0x41, 0x1d, 0xa9, 0xc9, 0xc4, 0xfa, 0x96, 0x92, 0x0c, 0x33, 0xb3, 0x1b, 0xbd, 0xce, 0x6a, 0xc5, 0x06, 0x2d, 0x70, + 0x23, 0xf9, 0x3e, 0xfc, 0x6c, 0xeb, 0x9f, 0x0e, 0x27, 0xd6, 0x6e, 0xe0, 0x80, 0x95, 0x26, 0x0b, 0x0a, 0x21, 0xc1, + 0x39, 0x50, 0x49, 0x59, 0x8a, 0xa6, 0x0d, 0x05, 0x19, 0x02, 0x27, 0xac, 0x0c, 0x33, 0x01, 0xc4, 0x4a, 0x56, 0x18, + 0x03, 0x32, 0xd8, 0x9a, 0xfb, 0x67, 0xcd, 0xcb, 0x4f, 0x6b, 0xa2, 0x35, 0xb9, 0xa2, 0xd5, 0x87, 0x5a, 0xbe, 0x81, + 0x81, 0xc0, 0xe8, 0x87, 0x7b, 0xca, 0x04, 0xad, 0x44, 0x39, 0x72, 0xe5, 0x10, 0x6e, 0x81, 0x13, 0x6d, 0xef, 0x83, + 0x8e, 0xf0, 0x6e, 0x91, 0x26, 0x98, 0x3b, 0x74, 0xfd, 0x92, 0xc8, 0x1a, 0x2b, 0x99, 0x12, 0x63, 0x29, 0xe1, 0x58, + 0x91, 0xa9, 0x24, 0xd9, 0xa0, 0x35, 0x04, 0x05, 0xb4, 0x9b, 0x1e, 0x67, 0x95, 0x09, 0x9c, 0x36, 0x1a, 0x28, 0xb6, + 0xb3, 0x4e, 0x07, 0xac, 0x91, 0x0e, 0x31, 0xc5, 0xa9, 0x36, 0x4c, 0xce, 0x0e, 0x0e, 0x82, 0xb8, 0x9a, 0x77, 0x90, + 0x0e, 0x11, 0xe6, 0xab, 0x55, 0xa0, 0xc0, 0x8a, 0xd1, 0x6a, 0x15, 0xbb, 0x60, 0xa9, 0x6a, 0xe8, 0x36, 0xef, 0x4b, + 0x32, 0x57, 0x02, 0x70, 0x0e, 0x10, 0x36, 0x48, 0x10, 0x1b, 0xf7, 0x5e, 0x0c, 0xee, 0xa8, 0x46, 0x36, 0x48, 0x1b, + 0xed, 0xa1, 0xc3, 0xb8, 0x06, 0xe9, 0x90, 0xc4, 0x05, 0x3f, 0x38, 0xd8, 0xcb, 0x8d, 0x88, 0xfc, 0x09, 0x44, 0xd9, + 0x4f, 0x4a, 0xb2, 0xe8, 0x01, 0xdd, 0xdd, 0x58, 0x77, 0x06, 0x94, 0x14, 0x65, 0xb6, 0xd5, 0xb6, 0xab, 0x65, 0x41, + 0x94, 0x8d, 0xb0, 0x09, 0x06, 0xf7, 0xc1, 0xb2, 0x2f, 0xc9, 0xfc, 0x95, 0x2c, 0x73, 0xac, 0x7f, 0xde, 0x9a, 0x59, + 0x1d, 0x86, 0x61, 0x2c, 0x26, 0x2a, 0x96, 0x61, 0xc3, 0xb0, 0x8a, 0xf8, 0x8f, 0x0c, 0x98, 0xce, 0xc4, 0x83, 0x72, + 0xae, 0x21, 0xd1, 0xe0, 0x5b, 0xd5, 0xc6, 0xde, 0x25, 0xf9, 0x69, 0xab, 0x97, 0x41, 0x43, 0xf2, 0xfc, 0xb7, 0x42, + 0xf2, 0xd0, 0x40, 0xa2, 0xc9, 0x63, 0x0d, 0x67, 0x3b, 0x70, 0xf1, 0x93, 0x5c, 0xc3, 0xd9, 0x6e, 0xdc, 0x5a, 0x4c, + 0xfd, 0xb2, 0x0b, 0x3e, 0x87, 0x37, 0x68, 0x40, 0xab, 0x02, 0x07, 0xca, 0x47, 0xeb, 0xba, 0x97, 0x66, 0xa5, 0x20, + 0x4c, 0x25, 0x09, 0x58, 0xfd, 0x00, 0x54, 0xda, 0xa8, 0x63, 0xf8, 0xb2, 0x68, 0x8e, 0x1c, 0x97, 0x40, 0x3d, 0x75, + 0x05, 0xc8, 0xc9, 0x78, 0xdb, 0xe7, 0x07, 0x07, 0x60, 0x1b, 0x80, 0x12, 0x17, 0x8e, 0xe2, 0xb9, 0x5c, 0x08, 0x50, + 0xa5, 0x72, 0xfb, 0x1b, 0x8a, 0xe1, 0x16, 0x88, 0x2a, 0x83, 0x1f, 0x50, 0x30, 0x8f, 0xf3, 0x9c, 0x5d, 0xe9, 0x32, + 0xf3, 0x1b, 0x73, 0x62, 0x49, 0x39, 0xd7, 0x3a, 0x61, 0x86, 0xba, 0x99, 0xa1, 0xd3, 0x3a, 0xda, 0x5e, 0x5c, 0xd1, + 0x4c, 0xbe, 0x62, 0xb9, 0xa4, 0x19, 0x2c, 0xbf, 0xa2, 0x38, 0x58, 0x51, 0x8e, 0xe0, 0xc0, 0xd6, 0x7a, 0xc5, 0x49, + 0x72, 0x67, 0x17, 0x59, 0xd7, 0x81, 0xa6, 0x71, 0x96, 0xa4, 0x7a, 0x12, 0x37, 0x9f, 0xd1, 0xe6, 0x70, 0x96, 0x2d, + 0xdd, 0x7c, 0x9a, 0x4a, 0xd9, 0x50, 0xdc, 0x3d, 0x60, 0xc4, 0x4a, 0x02, 0x2b, 0x3d, 0xef, 0xd4, 0x5a, 0x20, 0xe2, + 0xbd, 0x63, 0x13, 0xdc, 0x95, 0x60, 0xe9, 0x70, 0xd4, 0xb0, 0x0e, 0xa7, 0xa5, 0x9b, 0x2f, 0xb7, 0x5e, 0x69, 0xdb, + 0x26, 0x1c, 0x14, 0x9d, 0x3c, 0xde, 0x6d, 0x59, 0xbd, 0xb6, 0x92, 0xc3, 0x4a, 0x0b, 0x76, 0x5f, 0xc6, 0x8c, 0x96, + 0x96, 0xbc, 0x90, 0x3d, 0x8a, 0xfb, 0x92, 0x3c, 0x87, 0x3b, 0x43, 0x2f, 0xe5, 0x2c, 0x5d, 0xbb, 0x1a, 0xd3, 0xdd, + 0x2f, 0xb5, 0xff, 0x7d, 0x19, 0xbc, 0xc4, 0xef, 0x21, 0xb0, 0xfb, 0x55, 0xd5, 0x7c, 0x33, 0xa0, 0xfb, 0x55, 0x85, + 0xa0, 0xaf, 0xa2, 0x8d, 0x76, 0x4e, 0x20, 0xb7, 0x13, 0x3e, 0x0d, 0x5b, 0xbe, 0xd5, 0x96, 0x7e, 0xd6, 0x61, 0x24, + 0x9d, 0x69, 0xa9, 0xce, 0x03, 0xae, 0xf2, 0xd4, 0x20, 0x5f, 0xae, 0x6e, 0x21, 0x51, 0x93, 0x61, 0xa8, 0x75, 0xf8, + 0x5d, 0xdb, 0x63, 0x64, 0x4c, 0xa6, 0xed, 0x8c, 0xaf, 0x63, 0x21, 0xf7, 0xe1, 0x94, 0xf1, 0x8d, 0x7b, 0x78, 0x53, + 0x02, 0x1e, 0xb4, 0xfb, 0x4d, 0xe1, 0x18, 0xdb, 0xb9, 0xbe, 0x07, 0xe4, 0x8e, 0x4f, 0xb8, 0xd5, 0xdd, 0xea, 0x56, + 0xc6, 0xd7, 0x60, 0xff, 0x23, 0x3c, 0xb5, 0x97, 0xe3, 0xa8, 0xe1, 0xc0, 0x34, 0x5a, 0x16, 0xa5, 0x53, 0x80, 0x6b, + 0xe5, 0x4d, 0x20, 0xcc, 0x0b, 0x15, 0xe0, 0xfe, 0x01, 0x7f, 0x63, 0x58, 0xe2, 0xb8, 0xe4, 0x38, 0x27, 0xf7, 0xe5, + 0x88, 0x1a, 0xfc, 0x32, 0x7e, 0x0f, 0x74, 0xac, 0x28, 0xb4, 0xb0, 0x54, 0xf4, 0x9c, 0x9b, 0x85, 0xec, 0x4c, 0x4b, + 0xc5, 0xb4, 0x4c, 0xa9, 0x51, 0xd3, 0x6c, 0xc9, 0xe3, 0xb4, 0x56, 0xb6, 0x2c, 0x4f, 0x55, 0x6d, 0x5e, 0xb4, 0x03, + 0x8b, 0x55, 0x68, 0x71, 0xb5, 0x0a, 0xea, 0xa8, 0x26, 0xcc, 0x89, 0x64, 0x20, 0xcc, 0x9c, 0x8c, 0x8a, 0x9a, 0x66, + 0xad, 0xfb, 0x04, 0x68, 0x3d, 0xa1, 0xc8, 0xea, 0xe6, 0x35, 0x38, 0x5c, 0x17, 0x82, 0xee, 0xee, 0xfa, 0x14, 0xb0, + 0x5e, 0x5d, 0x39, 0x91, 0x83, 0xa1, 0x9f, 0xcb, 0x54, 0xd9, 0x2a, 0xa7, 0x75, 0x0b, 0x7e, 0xd1, 0x1d, 0xc9, 0xb2, + 0x06, 0x75, 0x9b, 0xf5, 0x4e, 0xb2, 0xd1, 0x73, 0xbe, 0x2b, 0xd9, 0xa8, 0xa6, 0xed, 0xee, 0xb5, 0xd0, 0xdd, 0x69, + 0xa9, 0x7a, 0xae, 0xed, 0x4d, 0x7e, 0xc3, 0x74, 0x6d, 0xa0, 0x4d, 0x8d, 0x66, 0xcb, 0x55, 0xce, 0x8a, 0x62, 0x5c, + 0x5e, 0x26, 0x50, 0xb9, 0x3b, 0x63, 0x4d, 0xff, 0xc6, 0x6a, 0x54, 0xd7, 0x71, 0x83, 0x1f, 0xc8, 0x24, 0xe5, 0x97, + 0x71, 0xfa, 0x1e, 0xe6, 0xab, 0x2a, 0x5f, 0xde, 0x26, 0x22, 0x96, 0xd4, 0x70, 0x97, 0x0a, 0x86, 0x1f, 0x1c, 0x18, + 0x7e, 0xd0, 0x7c, 0xba, 0xea, 0x8f, 0x97, 0xaf, 0xca, 0x01, 0xa2, 0x71, 0x61, 0x59, 0xc6, 0xb9, 0xdc, 0x3e, 0xc7, + 0x3a, 0x0b, 0x3b, 0x2f, 0x59, 0xd8, 0xb9, 0x0c, 0xd6, 0x87, 0x0a, 0x82, 0x6f, 0xb6, 0x8f, 0xb2, 0xc9, 0xd9, 0xbe, + 0xa9, 0x0e, 0xfe, 0x37, 0xd1, 0x9d, 0x7d, 0x1c, 0x2e, 0x77, 0x14, 0x1e, 0xa9, 0x74, 0x15, 0x0d, 0xf2, 0x3b, 0x48, + 0x3b, 0x90, 0xa4, 0xe7, 0xdc, 0x39, 0xa8, 0xe4, 0x94, 0x4d, 0x04, 0x0a, 0x46, 0x8b, 0x5c, 0xf2, 0x99, 0x19, 0x33, + 0x37, 0xd7, 0x8c, 0x54, 0x25, 0xb8, 0xa2, 0x55, 0xb4, 0x3d, 0xaa, 0x5f, 0xe4, 0x5a, 0x7e, 0x64, 0x59, 0x12, 0xe5, + 0xd8, 0x48, 0x91, 0x3c, 0xca, 0x0a, 0x62, 0x93, 0x8d, 0x37, 0xeb, 0xf0, 0x98, 0x65, 0x2c, 0x9f, 0x52, 0x11, 0x70, + 0xb4, 0xdc, 0x35, 0x19, 0x87, 0x80, 0x8c, 0x9e, 0x0c, 0x7f, 0x5b, 0x5d, 0xf8, 0x0b, 0x61, 0x34, 0xf0, 0x03, 0xcd, + 0xa8, 0x9c, 0xf2, 0x04, 0x12, 0x53, 0xc2, 0xa4, 0xbc, 0xd1, 0x74, 0x70, 0xb0, 0x17, 0xf8, 0xca, 0x2d, 0x01, 0x57, + 0xbf, 0xdd, 0x1a, 0xd4, 0x5f, 0xc2, 0xf5, 0x9c, 0x6a, 0x6a, 0x8a, 0x96, 0x74, 0xfd, 0x26, 0x8b, 0x0c, 0x3f, 0xd2, + 0x5b, 0x2c, 0x50, 0x51, 0x44, 0x1a, 0x6a, 0x7f, 0xcc, 0x68, 0x9a, 0xf8, 0xf8, 0x23, 0xbd, 0x8d, 0xca, 0xdb, 0xe2, + 0xea, 0x72, 0xb3, 0xda, 0x40, 0x9f, 0x5f, 0x67, 0x3e, 0xae, 0x26, 0x89, 0x96, 0x05, 0xe6, 0x82, 0x4d, 0x80, 0x38, + 0xff, 0x46, 0x6f, 0x23, 0x3d, 0x1e, 0x73, 0x2e, 0xeb, 0xa1, 0xa5, 0x45, 0x7d, 0xe8, 0x14, 0xbb, 0xdb, 0x60, 0x0c, + 0x8a, 0x81, 0xea, 0x3b, 0x24, 0xb5, 0x76, 0x95, 0x79, 0x88, 0x50, 0x71, 0xdf, 0xa5, 0xe0, 0x2f, 0x5c, 0xd1, 0x26, + 0x6b, 0xa9, 0xaf, 0x6b, 0x9d, 0x28, 0x74, 0xa8, 0x72, 0x3d, 0xce, 0x03, 0x61, 0x4f, 0x9d, 0xb9, 0x83, 0xe0, 0x38, + 0xc2, 0xbe, 0x90, 0x66, 0xd0, 0xe8, 0x5b, 0x9d, 0x12, 0x52, 0x45, 0x92, 0x5e, 0x57, 0xfd, 0xbc, 0xf3, 0x00, 0xf0, + 0x0e, 0x29, 0x2d, 0xb1, 0xba, 0x8e, 0x59, 0xd8, 0x74, 0xd1, 0xef, 0x24, 0x09, 0x96, 0x76, 0x09, 0x91, 0x70, 0xb1, + 0x28, 0x0b, 0xa0, 0x42, 0x43, 0x5f, 0x3a, 0x03, 0x90, 0x8d, 0x03, 0xb6, 0x21, 0x35, 0x33, 0x25, 0x35, 0x43, 0x07, + 0xe3, 0x3b, 0xa4, 0x24, 0x55, 0xc8, 0x50, 0x4a, 0xa4, 0x12, 0x7a, 0x66, 0x73, 0x0d, 0x09, 0xb9, 0x1b, 0x5a, 0x5e, + 0x9f, 0xd3, 0x7b, 0x9e, 0xd5, 0xc0, 0x0a, 0xd4, 0x38, 0xa8, 0x88, 0x60, 0x49, 0x54, 0x37, 0x28, 0xac, 0x3b, 0x47, + 0xd8, 0xfc, 0xd6, 0x80, 0x87, 0x76, 0x59, 0xc4, 0xa2, 0x24, 0x98, 0xa2, 0xa5, 0x08, 0xa6, 0x38, 0x83, 0x7c, 0x44, + 0x5e, 0x94, 0xf0, 0x53, 0x77, 0x37, 0x6a, 0xd9, 0xca, 0xdb, 0xaf, 0xf8, 0x81, 0x32, 0x2f, 0x21, 0x47, 0x13, 0x0b, + 0xcb, 0x53, 0x44, 0xa0, 0xee, 0xda, 0x39, 0xdb, 0xf6, 0x95, 0x49, 0xd1, 0x31, 0x80, 0x7d, 0x27, 0x83, 0xa5, 0xb3, + 0x0a, 0xf7, 0x2e, 0xb7, 0xb9, 0xf2, 0x67, 0x82, 0x7d, 0x55, 0x12, 0x69, 0x90, 0x93, 0x35, 0x89, 0x73, 0x77, 0xae, + 0xe5, 0xcf, 0x0b, 0x2a, 0x6e, 0xcf, 0x28, 0xe4, 0x3a, 0x73, 0xb8, 0xeb, 0x5b, 0x6d, 0x43, 0x95, 0xa7, 0xde, 0xcf, + 0x94, 0xb2, 0x52, 0xd4, 0x2f, 0x01, 0xae, 0x5f, 0x11, 0x2c, 0x54, 0xb4, 0xd1, 0x71, 0xc4, 0xe8, 0xd3, 0x42, 0x77, + 0x5e, 0x9e, 0xa4, 0x5d, 0x06, 0xfe, 0xb5, 0x0a, 0xd3, 0x26, 0x58, 0x80, 0xb9, 0x7b, 0x21, 0x75, 0x90, 0x0f, 0xd7, + 0xbd, 0x32, 0x50, 0x04, 0xe1, 0xbb, 0x6c, 0xf7, 0x52, 0xb7, 0x65, 0xcd, 0xee, 0x5e, 0x6a, 0x2d, 0xe8, 0xa7, 0x52, + 0x7e, 0xb0, 0x99, 0xa7, 0xbc, 0xbc, 0xcc, 0x8a, 0x02, 0x15, 0x00, 0xde, 0xf7, 0xdd, 0x20, 0xf8, 0xde, 0x24, 0x0d, + 0x86, 0x10, 0x8b, 0x3d, 0x4b, 0xb9, 0x65, 0xe2, 0xd5, 0xfc, 0xdf, 0x6f, 0xcc, 0xff, 0xbd, 0x73, 0xe5, 0x14, 0x4c, + 0xa3, 0x49, 0x46, 0x13, 0xcb, 0x3a, 0x91, 0x26, 0x40, 0xa5, 0xb7, 0xe5, 0x92, 0x7c, 0xbc, 0x88, 0x40, 0xe3, 0x5a, + 0x8e, 0x79, 0x26, 0x9b, 0xe3, 0x78, 0xc6, 0xd2, 0xdb, 0x68, 0xc1, 0x9a, 0x33, 0x9e, 0xf1, 0x7c, 0x1e, 0x8f, 0x28, + 0xce, 0x6f, 0x73, 0x49, 0x67, 0xcd, 0x05, 0xc3, 0x2f, 0x69, 0x7a, 0x45, 0x25, 0x1b, 0xc5, 0xd8, 0x3f, 0x11, 0x2c, + 0x4e, 0xbd, 0x37, 0xb1, 0x10, 0xfc, 0xda, 0xc7, 0xef, 0xf8, 0x25, 0x97, 0x1c, 0xbf, 0xbd, 0xb9, 0x9d, 0xd0, 0x0c, + 0x7f, 0xb8, 0x5c, 0x64, 0x72, 0x81, 0xf3, 0x38, 0xcb, 0x9b, 0x39, 0x15, 0x6c, 0xdc, 0x1d, 0xf1, 0x94, 0x8b, 0x26, + 0xa4, 0x6c, 0xcf, 0x68, 0x94, 0xb2, 0xc9, 0x54, 0x7a, 0x49, 0x2c, 0x3e, 0x76, 0x9b, 0xcd, 0xb9, 0x60, 0xb3, 0x58, + 0xdc, 0x36, 0x55, 0x8b, 0xe8, 0xb3, 0xd6, 0x51, 0xfc, 0xf9, 0xf8, 0x61, 0x57, 0x8a, 0x38, 0xcb, 0x19, 0x6c, 0x53, + 0x14, 0xa7, 0xa9, 0x77, 0xf4, 0xa8, 0x35, 0xcb, 0xf7, 0x74, 0x20, 0x2f, 0xce, 0x64, 0x71, 0x81, 0x3f, 0x02, 0xdc, + 0xe1, 0xa5, 0xcc, 0xf0, 0xe5, 0x42, 0x4a, 0x9e, 0x2d, 0x47, 0x0b, 0x91, 0x73, 0x11, 0xcd, 0x39, 0xcb, 0x24, 0x15, + 0xdd, 0x4b, 0x2e, 0x12, 0x2a, 0x9a, 0x22, 0x4e, 0xd8, 0x22, 0x8f, 0x1e, 0xce, 0x6f, 0xba, 0xa0, 0x59, 0x4c, 0x04, + 0x5f, 0x64, 0x89, 0x99, 0x8b, 0x65, 0x53, 0x2a, 0x98, 0x74, 0x2b, 0xd4, 0x2b, 0x4c, 0xa2, 0x94, 0x65, 0x34, 0x16, + 0xcd, 0x09, 0x74, 0x06, 0xb3, 0xa8, 0x95, 0xd0, 0x09, 0x16, 0x93, 0xcb, 0x38, 0x68, 0x77, 0x9e, 0x60, 0xfb, 0x37, + 0x7c, 0x84, 0xbc, 0xd6, 0xf6, 0xe2, 0x76, 0xab, 0xf5, 0x27, 0xd4, 0x5d, 0x9b, 0x45, 0x01, 0x14, 0xb5, 0xe7, 0x37, + 0x5e, 0xce, 0x21, 0xa7, 0x6d, 0x5b, 0xcf, 0xee, 0x3c, 0x4e, 0x20, 0x21, 0x38, 0xea, 0xcc, 0x6f, 0x0a, 0x58, 0x5d, + 0xa4, 0x93, 0x4c, 0xcd, 0x22, 0xcd, 0xd3, 0xf2, 0xb7, 0x42, 0xfc, 0x74, 0x3b, 0xc4, 0x1d, 0x0b, 0x71, 0x85, 0xf5, + 0x66, 0xb2, 0x10, 0x2a, 0xb6, 0x1a, 0xb5, 0x73, 0x0d, 0xc8, 0x94, 0x5f, 0x51, 0x61, 0xe1, 0x50, 0x0f, 0xbf, 0x19, + 0x8c, 0xce, 0x76, 0x30, 0x9e, 0x7e, 0x0a, 0x0c, 0x91, 0x25, 0xcb, 0xfa, 0xbe, 0xb6, 0x05, 0x9d, 0x75, 0xa7, 0x14, + 0xe8, 0x29, 0xea, 0xc0, 0xef, 0x6b, 0x96, 0xc8, 0xa9, 0xfe, 0xa9, 0xc8, 0xf9, 0x5a, 0xd7, 0x3d, 0x6a, 0xb5, 0xf4, + 0x73, 0xce, 0x7e, 0xa1, 0x51, 0x3b, 0x84, 0x06, 0xc5, 0x05, 0xfe, 0x5b, 0x79, 0x99, 0xb7, 0xce, 0x3d, 0xf1, 0x0f, + 0xee, 0x2d, 0x5f, 0x27, 0x49, 0xb1, 0xba, 0x11, 0x8d, 0x85, 0x95, 0x95, 0x5a, 0xf8, 0x80, 0xdb, 0x4e, 0x9d, 0x27, + 0xc2, 0x7a, 0xe5, 0x2d, 0x4e, 0xd6, 0xff, 0x41, 0xe7, 0x5d, 0x44, 0x10, 0xe9, 0x70, 0x92, 0x0d, 0x79, 0x37, 0xeb, + 0x91, 0x56, 0x37, 0x6b, 0x36, 0x51, 0xc0, 0x89, 0x18, 0x64, 0x26, 0x3d, 0x2f, 0x60, 0x7d, 0xae, 0x8c, 0xed, 0x1c, + 0x45, 0x1c, 0xae, 0x9a, 0xae, 0x56, 0x55, 0x18, 0x80, 0xa9, 0xeb, 0x1a, 0x7f, 0x93, 0xa6, 0x01, 0xce, 0x1d, 0x4e, + 0x9e, 0xd9, 0x17, 0xbb, 0x08, 0xcb, 0x2b, 0x52, 0x3e, 0x52, 0x98, 0x0b, 0xe7, 0xb1, 0x9c, 0x82, 0x97, 0xa2, 0x14, + 0x3f, 0x55, 0x12, 0x93, 0x7f, 0xe8, 0xa3, 0xbe, 0x28, 0x33, 0xdc, 0x20, 0x93, 0x4f, 0x14, 0x30, 0xca, 0x37, 0x92, + 0xc0, 0x88, 0xf8, 0x17, 0xa2, 0x6d, 0x3a, 0x6b, 0xd1, 0x8d, 0xef, 0x6b, 0xd1, 0xd1, 0x4c, 0x32, 0x95, 0xbb, 0x6d, + 0x23, 0x0e, 0xd3, 0x38, 0x3f, 0x1f, 0xe9, 0xbb, 0x92, 0x79, 0x75, 0x33, 0x20, 0x56, 0xd0, 0x6b, 0x23, 0x8d, 0x0a, + 0x65, 0x8f, 0x7e, 0x2f, 0x77, 0xda, 0x27, 0xe2, 0x2e, 0xfb, 0xa4, 0x5c, 0x78, 0xce, 0x17, 0x62, 0x04, 0xe1, 0x48, + 0x23, 0xf5, 0x36, 0x1d, 0x37, 0xbe, 0x52, 0x31, 0x7c, 0x2c, 0x9d, 0x4c, 0x50, 0x89, 0x99, 0xfb, 0x52, 0x09, 0xaa, + 0x42, 0x5e, 0xfa, 0xbe, 0x86, 0x11, 0x71, 0x76, 0x49, 0x20, 0xb3, 0x13, 0x95, 0xd4, 0x18, 0x64, 0xa4, 0x97, 0x85, + 0x8b, 0x8c, 0xfd, 0xbc, 0xa0, 0xe7, 0x0c, 0x74, 0x4d, 0x16, 0xb2, 0x44, 0xc5, 0x9a, 0x40, 0xf6, 0x35, 0xdb, 0x10, + 0xbc, 0x60, 0x89, 0xde, 0x98, 0x4c, 0x55, 0x9a, 0xdc, 0x26, 0xbf, 0xe9, 0x83, 0xbf, 0x18, 0xb4, 0x03, 0x86, 0x13, + 0x3e, 0x8b, 0x59, 0x16, 0x29, 0x97, 0x6f, 0x39, 0x58, 0x04, 0xad, 0x31, 0x4b, 0xa2, 0xcc, 0x6c, 0x4f, 0x1b, 0x85, + 0x3f, 0x71, 0x96, 0xa9, 0xae, 0x45, 0x97, 0x2b, 0x84, 0x6a, 0xf4, 0x11, 0x8b, 0xe0, 0x13, 0x2d, 0xd7, 0x38, 0xc2, + 0x6e, 0x75, 0x79, 0xed, 0xbc, 0xb6, 0x03, 0xad, 0xb5, 0x8d, 0xd2, 0x46, 0x00, 0x5f, 0x2f, 0xcd, 0xb9, 0x90, 0x41, + 0x30, 0xc5, 0x29, 0x22, 0xbd, 0xa9, 0x72, 0x76, 0x1d, 0xa7, 0xea, 0xbf, 0x7e, 0xb3, 0x1d, 0xb5, 0x4b, 0xf3, 0xbd, + 0x76, 0x1b, 0x58, 0x27, 0x47, 0x99, 0x1b, 0xa5, 0x6a, 0x19, 0xe5, 0x6f, 0xbd, 0xd4, 0xea, 0xb9, 0x5c, 0x2e, 0x36, + 0xc7, 0x4d, 0x8b, 0xaa, 0xa0, 0x06, 0x84, 0x0a, 0x16, 0xed, 0x98, 0x0a, 0x15, 0xd5, 0xba, 0x4b, 0x55, 0xf2, 0x42, + 0x8b, 0xe8, 0xf3, 0xfd, 0xa5, 0x30, 0x33, 0x16, 0x17, 0xcc, 0x3a, 0x99, 0xea, 0x24, 0x57, 0x18, 0x8c, 0x38, 0x7a, + 0xe8, 0xb6, 0x66, 0x1a, 0x96, 0x5b, 0x22, 0xb6, 0xd2, 0x6d, 0xa8, 0x1f, 0xa9, 0x20, 0x55, 0xb8, 0x6b, 0x63, 0x00, + 0xc8, 0xd5, 0xdb, 0x06, 0x18, 0x98, 0xad, 0xb9, 0xb4, 0x4b, 0x00, 0x6d, 0x6c, 0x4c, 0xe1, 0x22, 0xcd, 0xc5, 0xfe, + 0xf2, 0x1b, 0x59, 0x1c, 0x3a, 0x4d, 0xd5, 0x6f, 0x96, 0xc0, 0xff, 0x20, 0x01, 0x97, 0x5a, 0x29, 0x8d, 0xfc, 0xaf, + 0xdf, 0x9e, 0xbd, 0xf7, 0xf1, 0x25, 0x4f, 0x6e, 0x23, 0x5f, 0x8a, 0x05, 0xf5, 0x0b, 0x14, 0xca, 0x29, 0xcd, 0xca, + 0x97, 0xf1, 0xf0, 0x94, 0x86, 0x29, 0x9f, 0xe8, 0x4b, 0x99, 0xeb, 0x46, 0xf2, 0xe8, 0xe2, 0x58, 0xbd, 0x64, 0xaa, + 0x77, 0x2c, 0xf5, 0xeb, 0xbd, 0xa4, 0x80, 0x9f, 0x3d, 0x08, 0xa1, 0x1c, 0x1f, 0xca, 0xa9, 0x7a, 0x38, 0x83, 0x03, + 0xa3, 0x9e, 0xf6, 0x97, 0x1b, 0xc4, 0xd4, 0x87, 0x21, 0xa6, 0x3d, 0xbd, 0x84, 0x5c, 0xb5, 0xba, 0x88, 0x46, 0x17, + 0x17, 0xc5, 0xf1, 0x21, 0x8c, 0x75, 0x68, 0xc7, 0x05, 0x08, 0x6d, 0xff, 0x92, 0xc0, 0xe0, 0x65, 0x43, 0x82, 0xf4, + 0x60, 0x08, 0x98, 0x37, 0xe9, 0xc1, 0x22, 0x81, 0xc0, 0xa0, 0x77, 0x52, 0x96, 0xa8, 0x13, 0xab, 0x8b, 0x76, 0x41, + 0xa0, 0x1b, 0x56, 0x74, 0xaf, 0xbd, 0xa9, 0xd5, 0xfe, 0x5a, 0x90, 0x12, 0x17, 0xba, 0x0b, 0x04, 0xff, 0x2b, 0xc8, + 0x8e, 0x0f, 0x35, 0x1e, 0x2e, 0xdc, 0x57, 0x9b, 0xe8, 0xd7, 0x0e, 0x94, 0xd8, 0x1a, 0xe4, 0x12, 0x7f, 0x94, 0xf8, + 0xe3, 0x85, 0x6a, 0x6a, 0x85, 0x11, 0x68, 0x49, 0x20, 0xb4, 0x5b, 0x56, 0xeb, 0x18, 0xf1, 0x34, 0x8d, 0xe7, 0x39, + 0x8d, 0xec, 0x0f, 0x23, 0x97, 0x40, 0xbc, 0x6d, 0x2a, 0x02, 0x26, 0xbd, 0xe6, 0x14, 0xd4, 0x85, 0x4d, 0x2d, 0xe5, + 0x2a, 0x16, 0x41, 0xb3, 0x39, 0x6a, 0x5e, 0x4e, 0x50, 0x21, 0xa7, 0x4b, 0x57, 0xaa, 0x3d, 0x6e, 0xb5, 0xba, 0x90, + 0x0b, 0xd9, 0x8c, 0x53, 0x36, 0xc9, 0xa2, 0x94, 0x8e, 0x65, 0x21, 0xe1, 0x96, 0xda, 0xd2, 0xaa, 0x11, 0x61, 0xe7, + 0x91, 0xa0, 0x33, 0x2f, 0x84, 0x7f, 0xef, 0x9e, 0xb8, 0x90, 0x49, 0x94, 0xc9, 0x69, 0x53, 0x65, 0xdd, 0xc2, 0x9d, + 0x01, 0x39, 0xad, 0x3d, 0x2f, 0x9d, 0x89, 0x46, 0x14, 0x54, 0xac, 0x42, 0x0a, 0x4f, 0x4e, 0xb1, 0x14, 0x6e, 0xbb, + 0x0c, 0x2d, 0x37, 0x56, 0xb0, 0x29, 0xe9, 0x8f, 0x50, 0x91, 0x2b, 0xc5, 0x78, 0xb3, 0xb1, 0x55, 0x97, 0xea, 0x4f, + 0x1b, 0xe8, 0x73, 0x14, 0xbb, 0x42, 0x3b, 0x96, 0x97, 0xba, 0xc7, 0x7d, 0x90, 0x59, 0x53, 0x39, 0xb1, 0xdb, 0x03, + 0x15, 0x2c, 0x9b, 0x2f, 0xe4, 0x40, 0x39, 0xb5, 0x05, 0x5c, 0x90, 0x18, 0x62, 0xa7, 0x04, 0x70, 0x30, 0x5c, 0x6a, + 0x60, 0x46, 0x71, 0x3a, 0x0a, 0x00, 0x22, 0xaf, 0xe9, 0x3d, 0x15, 0x74, 0x86, 0xba, 0x33, 0x96, 0x35, 0x75, 0xdd, + 0x23, 0x47, 0x2d, 0x09, 0x9f, 0xc0, 0x53, 0x11, 0xaa, 0xd1, 0xb0, 0xca, 0x5d, 0xdd, 0x82, 0xcb, 0x8b, 0x61, 0x51, + 0x74, 0x85, 0x0c, 0x06, 0xaf, 0x03, 0x34, 0xc4, 0xbf, 0x38, 0x2f, 0x67, 0xf1, 0xed, 0x51, 0xf1, 0x71, 0x07, 0xed, + 0x68, 0xe2, 0x9e, 0x05, 0xd5, 0xec, 0x17, 0x02, 0x0d, 0xdf, 0x05, 0x3e, 0xcd, 0xe7, 0x4d, 0xcd, 0xbb, 0x9a, 0x8a, + 0x64, 0x7d, 0xe8, 0x8a, 0x8c, 0xa7, 0xf6, 0x7b, 0xb9, 0x54, 0x6c, 0xc9, 0x5c, 0xd2, 0xd0, 0xce, 0x84, 0x61, 0x79, + 0xa9, 0xc7, 0x3c, 0xbb, 0xd7, 0x78, 0x50, 0x8d, 0x9f, 0x5c, 0x9c, 0xd4, 0x79, 0x1c, 0xf0, 0xa5, 0xf2, 0x05, 0x76, + 0x71, 0x9a, 0xc2, 0x84, 0x17, 0x56, 0x7d, 0x71, 0x5f, 0xfa, 0x31, 0x90, 0xc3, 0x00, 0x15, 0xe6, 0x9c, 0x3e, 0x53, + 0x2a, 0xa5, 0xf3, 0xd6, 0xbc, 0x3d, 0x69, 0x83, 0x45, 0x5a, 0xfa, 0x32, 0x08, 0x77, 0xd7, 0xf2, 0xa2, 0xbb, 0x15, + 0xef, 0xd2, 0x0a, 0xa9, 0xa7, 0x16, 0x44, 0x7c, 0x91, 0x25, 0xbe, 0xf7, 0x97, 0x51, 0xca, 0x46, 0x1f, 0x89, 0xbf, + 0xbf, 0x0c, 0xd0, 0xe6, 0xb5, 0x47, 0xc5, 0x15, 0x2c, 0xc3, 0x46, 0x75, 0x47, 0x7a, 0x16, 0x3a, 0xbc, 0x58, 0xbf, + 0x15, 0xc7, 0xef, 0xed, 0x2f, 0x81, 0xf1, 0xe8, 0x79, 0x7a, 0x17, 0xc5, 0x79, 0xf5, 0xae, 0xab, 0x0a, 0x0a, 0x40, + 0xb3, 0x2e, 0xf7, 0x14, 0x51, 0x11, 0xff, 0x93, 0x94, 0xe6, 0x7b, 0x9a, 0xa9, 0x01, 0x9c, 0xd2, 0xf0, 0x37, 0xdf, + 0xfb, 0x4b, 0x59, 0x46, 0x4b, 0x8f, 0x86, 0x4a, 0xc9, 0x20, 0x3e, 0xcc, 0x05, 0x66, 0x6c, 0x98, 0x50, 0x19, 0xb3, + 0x54, 0x77, 0xe9, 0x5a, 0x03, 0x7c, 0x6d, 0x45, 0xab, 0x55, 0x5e, 0x5f, 0x0b, 0xab, 0x63, 0x50, 0xad, 0xec, 0xf8, + 0xb0, 0x82, 0x5b, 0xad, 0x4c, 0x9d, 0x49, 0x37, 0x34, 0x58, 0xad, 0x50, 0xd7, 0x79, 0x7f, 0x19, 0xa9, 0x6b, 0x43, + 0x00, 0x20, 0x37, 0x00, 0x42, 0xd0, 0x5a, 0x5f, 0x8b, 0x09, 0x52, 0xc2, 0x43, 0x19, 0x8b, 0x09, 0x95, 0x6b, 0x88, + 0x4d, 0x75, 0x8e, 0x6a, 0xd7, 0x06, 0xa8, 0x37, 0xa0, 0x8d, 0xeb, 0xd0, 0x5e, 0x00, 0xd2, 0xfb, 0xfb, 0x4b, 0x56, + 0x90, 0xfd, 0x25, 0xcd, 0x46, 0x3c, 0xa1, 0x1f, 0xde, 0x7d, 0x09, 0x97, 0x1c, 0x79, 0x06, 0x86, 0xc5, 0x14, 0x81, + 0xe0, 0x54, 0x9b, 0xa3, 0x45, 0x08, 0x57, 0x22, 0x44, 0x73, 0x02, 0x4f, 0xcd, 0xa5, 0x40, 0x2c, 0x7c, 0xaf, 0xaf, + 0x21, 0xa7, 0x89, 0x86, 0x99, 0x64, 0xaa, 0x17, 0x2f, 0x8e, 0x0f, 0x75, 0x6b, 0x2d, 0x02, 0x74, 0x23, 0x40, 0x82, + 0x3a, 0xa7, 0x15, 0x0e, 0x20, 0xaf, 0xd9, 0xc5, 0x43, 0xc2, 0xae, 0x4a, 0x62, 0x53, 0x17, 0xa8, 0x7a, 0xc7, 0x69, + 0x7c, 0x49, 0xd3, 0xde, 0xfe, 0x32, 0x5b, 0xad, 0x5a, 0xc5, 0xf1, 0xa1, 0x7e, 0xf4, 0x8e, 0x15, 0xdf, 0xd0, 0x2f, + 0xbc, 0x54, 0x5b, 0x0c, 0xb7, 0x12, 0x21, 0xdb, 0xd3, 0xa6, 0x39, 0x45, 0x66, 0x80, 0xc2, 0xf7, 0x54, 0x82, 0x85, + 0x6a, 0x54, 0x2a, 0x44, 0x85, 0xef, 0xb1, 0x64, 0xb3, 0x2c, 0x97, 0x74, 0x0e, 0xa5, 0xd3, 0xd5, 0xaa, 0x5d, 0xf8, + 0xde, 0x8c, 0x65, 0xf0, 0x94, 0xad, 0x56, 0xea, 0xc2, 0xdf, 0x8c, 0x65, 0x41, 0x0b, 0xc8, 0xd6, 0xf7, 0x66, 0xf1, + 0x8d, 0x5a, 0xb0, 0xad, 0x89, 0x6f, 0x82, 0xb6, 0xa9, 0x0a, 0x4b, 0xfc, 0xe4, 0x40, 0x71, 0xd5, 0x8e, 0xa6, 0x66, + 0x47, 0x13, 0xbc, 0xd0, 0x57, 0x99, 0x48, 0x90, 0x90, 0x74, 0xfb, 0x8e, 0x26, 0x76, 0x47, 0x17, 0x3b, 0x76, 0x74, + 0x71, 0xc7, 0x8e, 0xc6, 0x66, 0xf7, 0xbc, 0x12, 0x77, 0x7c, 0xb5, 0x6a, 0xb7, 0x2a, 0xec, 0x1d, 0x1f, 0x26, 0xec, + 0x0a, 0x76, 0x03, 0xd4, 0x3c, 0xc9, 0x66, 0x74, 0x3b, 0x51, 0xd6, 0x51, 0x4c, 0x7f, 0x15, 0x26, 0x2b, 0x2c, 0x64, + 0x75, 0x2c, 0xb8, 0x74, 0x5d, 0xc6, 0xdc, 0xfe, 0x48, 0xca, 0x66, 0x80, 0x87, 0x1c, 0xf0, 0x30, 0x35, 0x78, 0xb8, + 0x28, 0xce, 0x41, 0x24, 0xa8, 0xe5, 0xdc, 0x8b, 0xf4, 0xa0, 0xb5, 0xdf, 0xdb, 0x4d, 0x62, 0x10, 0x0d, 0xbf, 0xe6, + 0x22, 0xf1, 0x23, 0xdd, 0xf4, 0x57, 0x61, 0x66, 0xc6, 0x32, 0x93, 0x5b, 0xb5, 0x93, 0xb4, 0xaa, 0x7a, 0x97, 0xc0, + 0x3a, 0x8f, 0x1e, 0xe9, 0x16, 0xf3, 0x58, 0x4a, 0x2a, 0x32, 0x43, 0xa8, 0xbe, 0xff, 0xff, 0x05, 0xd1, 0x6d, 0x61, + 0x23, 0xb1, 0x65, 0x23, 0x96, 0xde, 0x8c, 0x7e, 0x6e, 0x58, 0xbc, 0x96, 0x46, 0x7b, 0x95, 0xc2, 0x7a, 0x8b, 0x5c, + 0x1b, 0x41, 0x17, 0x81, 0xc9, 0xb2, 0x98, 0xd1, 0xe4, 0x5c, 0xf1, 0xe3, 0xfe, 0xe8, 0xc2, 0xe8, 0xa7, 0x6b, 0xd2, + 0xad, 0xea, 0x80, 0xfd, 0x1f, 0x17, 0x9d, 0x27, 0x0f, 0x4f, 0x7d, 0xac, 0x59, 0x3a, 0x1f, 0x8f, 0x7d, 0x54, 0x78, + 0xf7, 0xeb, 0xd6, 0x7e, 0xf8, 0xe3, 0xe2, 0x8b, 0x17, 0xad, 0x2f, 0xca, 0xce, 0x99, 0x8f, 0x8a, 0x0b, 0x13, 0xce, + 0xb7, 0x92, 0xc9, 0x81, 0xd7, 0xae, 0x68, 0x1c, 0x67, 0xbb, 0x97, 0x33, 0x70, 0x97, 0x93, 0xcf, 0x29, 0x4d, 0xb0, + 0xef, 0xf9, 0x78, 0xa3, 0xf4, 0x3c, 0xa5, 0x57, 0xd4, 0xbe, 0x68, 0x70, 0xcb, 0x64, 0x5b, 0x7a, 0x8c, 0xf8, 0x22, + 0x93, 0x26, 0xaf, 0xc1, 0x70, 0x56, 0x67, 0x49, 0x17, 0x6a, 0x0d, 0xae, 0x49, 0x70, 0xab, 0xc5, 0x5a, 0x5d, 0x58, + 0x15, 0x17, 0xd8, 0x77, 0x00, 0xd8, 0x09, 0x59, 0x7f, 0x47, 0x79, 0xd4, 0xc2, 0xad, 0x5d, 0xb0, 0xe1, 0x36, 0x8a, + 0x7c, 0x7f, 0x68, 0xf1, 0xa4, 0x5c, 0x93, 0xb5, 0xf7, 0x43, 0xec, 0xc4, 0xd7, 0x27, 0x31, 0x70, 0x29, 0x60, 0xb0, + 0x8c, 0xe6, 0xf9, 0x4e, 0x04, 0x94, 0x9b, 0x88, 0xfd, 0xaa, 0xb5, 0xbf, 0x63, 0x14, 0xdc, 0xc2, 0x70, 0xc2, 0x14, + 0xc0, 0x65, 0x80, 0xd4, 0xb4, 0xa2, 0xe3, 0x31, 0x1d, 0x95, 0x9e, 0x5d, 0x08, 0x75, 0x8d, 0x59, 0x2a, 0x21, 0xe2, + 0xa3, 0x42, 0x31, 0xfe, 0x1b, 0x9e, 0x51, 0x1f, 0xd9, 0xe4, 0x4d, 0x03, 0xbf, 0x11, 0xf7, 0xdb, 0xe1, 0xd1, 0x23, + 0xd6, 0x61, 0x31, 0xb3, 0xac, 0x56, 0xd6, 0xab, 0x53, 0x2b, 0xaf, 0x23, 0x92, 0x2b, 0xb7, 0xcd, 0xae, 0x03, 0x74, + 0xbf, 0x63, 0xb2, 0x6c, 0x7f, 0xf1, 0xa8, 0xdd, 0x2a, 0x7c, 0xec, 0xc3, 0x70, 0xf7, 0x3d, 0x25, 0xaa, 0xd7, 0x11, + 0xf4, 0x5a, 0x64, 0xbf, 0xa6, 0x5f, 0xa7, 0xfd, 0x79, 0xdb, 0xc7, 0xfa, 0xbd, 0x01, 0xa8, 0x28, 0x99, 0xc1, 0x08, + 0x7c, 0x9d, 0xbf, 0x7b, 0x29, 0xf5, 0xc1, 0xef, 0x07, 0xcf, 0xe3, 0x76, 0xcb, 0xc7, 0x7e, 0x2e, 0xf9, 0xfc, 0x57, + 0x2c, 0xe1, 0xc8, 0xc7, 0xfe, 0x28, 0xe5, 0x39, 0x75, 0xd7, 0xa0, 0xb5, 0xd7, 0xdf, 0xbf, 0x08, 0x0d, 0xd1, 0x5c, + 0xd0, 0x3c, 0xf7, 0xdc, 0xf1, 0x0d, 0x29, 0x7d, 0x82, 0x61, 0x6e, 0xa5, 0xb8, 0x9c, 0x4a, 0x85, 0x17, 0x7d, 0xa5, + 0xdf, 0xa5, 0x2a, 0x5d, 0xb6, 0x41, 0x6c, 0x4a, 0x04, 0x94, 0x8c, 0x4d, 0x2b, 0x53, 0x9f, 0x9c, 0x79, 0xcb, 0xd1, + 0xd3, 0x13, 0xeb, 0x10, 0xf0, 0xe6, 0x04, 0xb5, 0x92, 0x19, 0xcb, 0xce, 0xb7, 0x94, 0xc6, 0x37, 0x5b, 0x4a, 0x41, + 0x43, 0x2b, 0xa1, 0x33, 0x6f, 0x9b, 0xf9, 0x34, 0xd6, 0x2b, 0x3d, 0xc7, 0x05, 0x31, 0x51, 0x6e, 0xca, 0x4f, 0x40, + 0xea, 0x6c, 0x83, 0x1a, 0xe1, 0xb7, 0x4f, 0x07, 0x25, 0xbf, 0x6a, 0x3a, 0x7a, 0xf3, 0xe9, 0x3d, 0x77, 0x14, 0x9b, + 0xdf, 0x81, 0x7d, 0x73, 0x73, 0x7c, 0x1d, 0xfd, 0x5b, 0x8a, 0x8d, 0xee, 0x51, 0x6e, 0xc1, 0x28, 0x65, 0xb3, 0x6a, + 0x17, 0x36, 0xc1, 0x54, 0x4a, 0x07, 0xa4, 0x0f, 0xb9, 0x83, 0x68, 0xed, 0xe3, 0x1c, 0x2e, 0x45, 0xc2, 0x9b, 0x27, + 0x16, 0x82, 0x9e, 0xa7, 0xfc, 0x7a, 0xfd, 0x4d, 0x5a, 0xbb, 0x1b, 0x4f, 0xd9, 0x64, 0xea, 0xdc, 0x74, 0xa2, 0xa4, + 0x44, 0xfd, 0x9d, 0x13, 0x14, 0xff, 0xfa, 0x2f, 0x61, 0xf8, 0xaf, 0xff, 0xf2, 0xc9, 0xa6, 0x30, 0x7c, 0x71, 0x81, + 0x65, 0x35, 0xec, 0x6e, 0x02, 0xdf, 0x3e, 0x53, 0x1d, 0xe7, 0xdb, 0xdb, 0x6c, 0x6c, 0x02, 0xd4, 0x6f, 0x6c, 0xc1, + 0x46, 0xa1, 0x3e, 0x00, 0xde, 0x6f, 0x01, 0x0c, 0xd6, 0xf5, 0x49, 0xc8, 0xa0, 0xd1, 0xef, 0x02, 0xed, 0x02, 0x45, + 0xf7, 0xda, 0x91, 0xdf, 0x8e, 0xe1, 0x4f, 0xad, 0xe1, 0x77, 0x82, 0x6f, 0x3c, 0x02, 0xa3, 0x8b, 0x8b, 0x32, 0xa5, + 0xcd, 0xed, 0x0a, 0x57, 0xe6, 0xfb, 0x1b, 0x25, 0x46, 0xf6, 0x47, 0x2d, 0xd4, 0x53, 0x17, 0xf2, 0xc8, 0xe8, 0xe2, + 0x35, 0xbc, 0x27, 0xe7, 0xf8, 0x52, 0x58, 0x97, 0xea, 0x1d, 0xfc, 0x19, 0x86, 0xa8, 0xaf, 0x4a, 0x0d, 0xba, 0xc1, + 0x9c, 0xa1, 0x14, 0x34, 0x7e, 0x00, 0x13, 0x8f, 0x2e, 0x8c, 0x7d, 0x77, 0xaa, 0x1d, 0x1f, 0xd1, 0x3a, 0x69, 0x1b, + 0x87, 0x48, 0x0d, 0xe9, 0xd8, 0x7b, 0xaf, 0xf0, 0xa5, 0x1a, 0xd3, 0xca, 0x9e, 0x56, 0xce, 0x25, 0x50, 0xe5, 0x2f, + 0x0a, 0x15, 0x18, 0xff, 0xeb, 0xae, 0xd8, 0xdd, 0xdf, 0x3f, 0x1d, 0xbb, 0xe3, 0xf7, 0x8a, 0xdd, 0xfd, 0xfd, 0x0f, + 0x8f, 0xdd, 0xfd, 0xd5, 0x8d, 0xdd, 0xc1, 0x26, 0x7e, 0x79, 0xaf, 0xf8, 0x9a, 0x8d, 0x7d, 0xf0, 0xeb, 0x9c, 0xb4, + 0x8d, 0x26, 0x9b, 0xf2, 0x09, 0x04, 0xd7, 0xfe, 0xfd, 0x63, 0x65, 0x29, 0x9f, 0xb8, 0x91, 0x32, 0x78, 0x4f, 0x2a, + 0x84, 0xc6, 0xba, 0x36, 0xa6, 0x65, 0xa2, 0x53, 0xad, 0xf2, 0x0e, 0x48, 0xf3, 0xa1, 0x7d, 0x67, 0x81, 0x1f, 0x95, + 0xef, 0x1d, 0x6a, 0xe1, 0x8e, 0x8d, 0x5f, 0x45, 0x2a, 0xf4, 0x55, 0x76, 0xec, 0x34, 0xec, 0x05, 0x07, 0x77, 0x84, + 0xae, 0x7d, 0xaf, 0x8a, 0xbe, 0xef, 0xbe, 0xf4, 0x7f, 0xbc, 0x69, 0x3f, 0x1b, 0xb4, 0xbb, 0x47, 0xed, 0x99, 0x1f, + 0xf9, 0x20, 0xa5, 0x54, 0x41, 0xab, 0x7b, 0x74, 0x04, 0x05, 0xd7, 0x4e, 0x41, 0x07, 0x0a, 0x98, 0x53, 0xf0, 0x08, + 0x0a, 0x46, 0x4e, 0xc1, 0x63, 0x28, 0x48, 0x9c, 0x82, 0x27, 0x50, 0x70, 0xe5, 0x17, 0x03, 0x56, 0x82, 0xfb, 0x04, + 0x0d, 0xb1, 0x36, 0x1e, 0x6c, 0xd9, 0x13, 0xdc, 0x86, 0xa0, 0x59, 0x3c, 0x51, 0xb9, 0x3e, 0xe0, 0x82, 0x8b, 0x38, + 0xbe, 0x9e, 0xd2, 0x2c, 0x82, 0xb0, 0xe5, 0x73, 0x25, 0x63, 0x42, 0xc9, 0xdf, 0xb3, 0x19, 0xb5, 0x5f, 0xa8, 0xb0, + 0x78, 0xf0, 0x7c, 0x34, 0x68, 0x0d, 0x8b, 0x6e, 0xb9, 0x73, 0x3a, 0xda, 0x66, 0xf2, 0x3e, 0xf4, 0x5e, 0x56, 0x75, + 0x7a, 0xba, 0x66, 0xb9, 0xe7, 0x3b, 0xa2, 0x36, 0x8e, 0x3b, 0x60, 0x9c, 0xf2, 0xeb, 0xe6, 0x8d, 0xdf, 0xdb, 0x1e, + 0xc9, 0x01, 0x88, 0xca, 0x48, 0x8e, 0x5a, 0x53, 0xf9, 0xf4, 0x3e, 0x9e, 0x94, 0xbf, 0x5f, 0xd3, 0x3c, 0x8f, 0x27, + 0xa6, 0xe5, 0xee, 0xc8, 0x8d, 0x02, 0xd1, 0x8d, 0xda, 0x58, 0x20, 0x20, 0xfa, 0x02, 0x9b, 0x05, 0xe6, 0xb4, 0x09, + 0xc6, 0x00, 0x76, 0xea, 0x71, 0x1c, 0x35, 0x7d, 0xbd, 0x48, 0xc6, 0x93, 0xaa, 0xe0, 0x78, 0x2e, 0xa8, 0x2a, 0xd5, + 0x18, 0x2e, 0x8e, 0x0f, 0xa1, 0x40, 0x57, 0xef, 0x88, 0xd7, 0x58, 0xdb, 0x7d, 0x77, 0xd4, 0xc6, 0xb3, 0xf1, 0x1a, + 0x37, 0xc3, 0xa5, 0x4c, 0x6f, 0xd9, 0x8c, 0x12, 0x7c, 0xd6, 0x1e, 0xc1, 0x1f, 0x13, 0x83, 0xf8, 0x6c, 0x3c, 0x1e, + 0xdf, 0x19, 0xbf, 0xf9, 0x2c, 0x19, 0xd3, 0x0e, 0x7d, 0xd4, 0x85, 0xec, 0x87, 0xa6, 0xf1, 0xfa, 0xb7, 0x0b, 0x85, + 0xbb, 0xe5, 0xfd, 0x1a, 0x43, 0x80, 0x40, 0x4e, 0x97, 0xf7, 0x8f, 0xe5, 0x14, 0x73, 0x41, 0x97, 0xb3, 0x58, 0x4c, + 0x58, 0x16, 0xb5, 0x8a, 0xf0, 0xca, 0x04, 0x3f, 0x3e, 0x7b, 0xfa, 0xf4, 0x69, 0x11, 0x26, 0xf6, 0xa9, 0x95, 0x24, + 0x45, 0x38, 0x5a, 0x96, 0xcb, 0x68, 0xb5, 0xc6, 0xe3, 0x22, 0x64, 0xb6, 0xe0, 0xa8, 0x33, 0x4a, 0x8e, 0x3a, 0x45, + 0x78, 0xed, 0xb4, 0x28, 0x42, 0x6a, 0x9e, 0x04, 0x4d, 0x6a, 0x29, 0x14, 0x4f, 0x5a, 0xad, 0x22, 0xd4, 0x84, 0xb6, + 0x04, 0x8b, 0x48, 0xff, 0x8c, 0xe2, 0x85, 0xe4, 0xc0, 0x92, 0xbb, 0x5c, 0x06, 0x83, 0x73, 0xf3, 0x7a, 0x0a, 0xfd, + 0x29, 0x87, 0x02, 0x0d, 0xf1, 0x97, 0x6e, 0x98, 0x02, 0x88, 0x59, 0x85, 0x27, 0xb8, 0x8d, 0x62, 0xd4, 0xaa, 0x81, + 0xb2, 0x54, 0xf5, 0x97, 0x84, 0x57, 0xd1, 0x0b, 0xe0, 0x3f, 0xd0, 0x52, 0xbf, 0x47, 0x4d, 0xd2, 0x1d, 0x5c, 0x9f, + 0xd2, 0x4f, 0x72, 0xfd, 0xdb, 0xfb, 0x30, 0x7d, 0x4a, 0xff, 0x68, 0xa6, 0x6f, 0x5e, 0x36, 0xaa, 0x99, 0xbe, 0x66, + 0x6b, 0x33, 0x49, 0xfc, 0xd1, 0x94, 0x8e, 0x3e, 0x5e, 0xf2, 0x9b, 0x26, 0x1c, 0x09, 0xe1, 0x2b, 0x7e, 0xba, 0xff, + 0x5b, 0xd3, 0x2d, 0xec, 0x60, 0xce, 0x97, 0x20, 0x94, 0xd8, 0x7c, 0x9b, 0x11, 0xff, 0xad, 0x35, 0xab, 0x74, 0xc9, + 0x78, 0x4c, 0xfc, 0xb7, 0xe3, 0xb1, 0x6f, 0x2f, 0xd9, 0xc5, 0x92, 0xaa, 0x56, 0x6f, 0x6a, 0x25, 0xaa, 0xd5, 0x17, + 0x5f, 0xb8, 0x65, 0x6e, 0x81, 0x09, 0x72, 0xb8, 0x01, 0x0d, 0x53, 0x93, 0xb0, 0x1c, 0x8e, 0x1a, 0x7c, 0xa0, 0xa2, + 0xfe, 0x96, 0x3f, 0x51, 0x7b, 0x21, 0x73, 0x09, 0xf0, 0x96, 0xb7, 0x48, 0xaf, 0xdf, 0x30, 0x9f, 0x50, 0x9b, 0xf0, + 0xf6, 0xec, 0xf6, 0xcb, 0x24, 0x98, 0x49, 0x54, 0xb0, 0xfc, 0x6d, 0xb6, 0x76, 0x7b, 0x44, 0xc3, 0x48, 0x88, 0xbb, + 0xac, 0x42, 0xf2, 0xc9, 0x24, 0x85, 0x4f, 0x84, 0x2c, 0x6b, 0x6f, 0x1e, 0xd5, 0xdd, 0xfb, 0xb5, 0xf5, 0x46, 0x6e, + 0x47, 0xf3, 0x9e, 0x4e, 0xf5, 0xc5, 0x22, 0x9d, 0x75, 0x7c, 0x65, 0x3e, 0x5d, 0xa3, 0x2c, 0xb2, 0xa5, 0xe1, 0xff, + 0x4b, 0x9d, 0xab, 0x2a, 0x21, 0x4f, 0x43, 0x0f, 0x9c, 0x14, 0x85, 0xc9, 0xf2, 0x4f, 0x58, 0x3e, 0x87, 0x37, 0x62, + 0xea, 0x9e, 0xf4, 0x53, 0x2c, 0x3c, 0xbf, 0x76, 0x22, 0x09, 0xb5, 0xed, 0x2a, 0x6c, 0x28, 0x41, 0xfb, 0x6a, 0x67, + 0xb2, 0xf0, 0x8d, 0xcb, 0xd7, 0x22, 0xd1, 0xf7, 0x34, 0x3e, 0x75, 0x8c, 0xc3, 0x59, 0x21, 0xf8, 0x5d, 0xcb, 0x0d, + 0xb1, 0x55, 0xb6, 0xa0, 0x70, 0x23, 0x65, 0xaa, 0x46, 0x63, 0x4b, 0xf9, 0xe5, 0xf3, 0x79, 0x9c, 0x69, 0x36, 0x4a, + 0x7c, 0xcd, 0x0f, 0xf6, 0x97, 0xd5, 0xce, 0x17, 0xbe, 0x05, 0x5b, 0x13, 0x6f, 0xef, 0xf8, 0x10, 0x3a, 0xf4, 0xbc, + 0x1a, 0xe8, 0xd9, 0x86, 0x3b, 0xff, 0x13, 0x81, 0xf5, 0x8b, 0x30, 0xbf, 0xc6, 0x61, 0x7e, 0xed, 0xfd, 0x79, 0xd9, + 0xbc, 0xa6, 0x97, 0x1f, 0x99, 0x6c, 0xca, 0x78, 0xde, 0x04, 0x85, 0x5f, 0xf9, 0xe5, 0x0c, 0x7b, 0x56, 0xe9, 0x61, + 0xfa, 0x8e, 0x7c, 0x77, 0x91, 0x43, 0xfc, 0x5d, 0xa9, 0xad, 0x51, 0xc6, 0x33, 0xda, 0xad, 0xa7, 0x01, 0xba, 0xe1, + 0x5c, 0x8b, 0xad, 0xe1, 0x92, 0x43, 0xbc, 0x5e, 0xde, 0x46, 0x2d, 0xc3, 0xd6, 0x5b, 0x36, 0x56, 0xdb, 0xda, 0xda, + 0x3e, 0x32, 0xc8, 0x6d, 0x28, 0xe9, 0x25, 0x36, 0x63, 0xd6, 0xbb, 0x62, 0xce, 0x9f, 0x4a, 0x8a, 0x03, 0x6f, 0x9e, + 0xfd, 0xeb, 0x64, 0x13, 0xae, 0x17, 0xab, 0xa4, 0xb8, 0xfb, 0x40, 0x16, 0xc5, 0x63, 0x49, 0x05, 0xbe, 0x4f, 0xcb, + 0x4b, 0x75, 0x7f, 0x65, 0x09, 0x62, 0x26, 0x6a, 0x3f, 0x9d, 0xdf, 0xdc, 0x7f, 0xf8, 0xbb, 0x97, 0x5f, 0x18, 0x1c, + 0xd9, 0xf7, 0xb9, 0xf8, 0x7e, 0x17, 0x0e, 0x42, 0x1a, 0xdf, 0x46, 0x2c, 0x53, 0x32, 0xef, 0x12, 0x5c, 0x72, 0xdd, + 0x39, 0x37, 0xd9, 0x9d, 0x82, 0xa6, 0xea, 0xe3, 0x6d, 0x66, 0x2b, 0x8e, 0x1e, 0xcf, 0x6f, 0xec, 0x6e, 0xb4, 0xd7, + 0xb2, 0x36, 0xff, 0xd0, 0xe4, 0xcc, 0xdd, 0xd9, 0xa0, 0xf5, 0x04, 0xc3, 0x47, 0xf3, 0x9b, 0xae, 0x16, 0xb4, 0x4d, + 0xa1, 0xa1, 0x6a, 0xcd, 0x6f, 0xdc, 0xf4, 0xd4, 0x6a, 0x20, 0x2f, 0x3c, 0xca, 0x3d, 0x1a, 0xe7, 0xb4, 0x0b, 0x6f, + 0xac, 0x66, 0xa3, 0x38, 0x35, 0xc2, 0x7c, 0xc6, 0x92, 0x24, 0xa5, 0x5d, 0x2b, 0xaf, 0xbd, 0xf6, 0x63, 0xc8, 0xee, + 0x74, 0xb7, 0xac, 0xbe, 0x2b, 0x0e, 0xf2, 0x4a, 0x3c, 0xc5, 0x97, 0x39, 0x4f, 0xe1, 0x73, 0x11, 0x5b, 0xd1, 0x69, + 0xd2, 0x1e, 0x5b, 0x15, 0xf2, 0xd4, 0xef, 0xfa, 0x5a, 0x1e, 0xb5, 0xfe, 0xd4, 0x55, 0x1b, 0xde, 0xea, 0x4a, 0x3e, + 0x8f, 0x9a, 0x47, 0xf5, 0x85, 0x40, 0x55, 0xb9, 0x04, 0xbc, 0x65, 0x59, 0x18, 0xa4, 0x95, 0xe6, 0xd3, 0x5e, 0xd8, + 0x36, 0x65, 0x6a, 0x00, 0x78, 0xb5, 0x72, 0x59, 0x54, 0xd4, 0x17, 0xf3, 0xef, 0x73, 0x5a, 0x3e, 0xdf, 0x7e, 0x5a, + 0x3e, 0xb7, 0xa7, 0xe5, 0x6e, 0x8a, 0xfd, 0x6c, 0xdc, 0x86, 0x3f, 0xdd, 0x6a, 0x41, 0x51, 0xcb, 0x3b, 0x9a, 0xdf, + 0x78, 0xa0, 0xa7, 0x35, 0x3b, 0xf3, 0x1b, 0x9d, 0x9c, 0x0b, 0x61, 0x83, 0x16, 0xa4, 0xab, 0xe2, 0x96, 0x07, 0x85, + 0xf0, 0xb7, 0x55, 0xab, 0x6a, 0x3f, 0x84, 0x3a, 0xe8, 0xf5, 0x68, 0xb3, 0xae, 0x73, 0xf7, 0xa1, 0x8d, 0x32, 0x2e, + 0x83, 0xc8, 0x72, 0x63, 0x14, 0xca, 0xf8, 0xf2, 0x92, 0x26, 0xd1, 0x98, 0x8f, 0x16, 0xf9, 0x3f, 0x1b, 0xf8, 0x0d, + 0x12, 0xef, 0x3c, 0xd2, 0x6b, 0xe3, 0xd8, 0xae, 0x3a, 0x55, 0xd8, 0x8e, 0xb0, 0x2c, 0xf7, 0x29, 0xca, 0x47, 0x71, + 0x4a, 0x83, 0x4e, 0xf8, 0x70, 0xcb, 0x21, 0xf8, 0x0f, 0xd9, 0x9b, 0xad, 0x8b, 0xf9, 0xbd, 0xc8, 0xb8, 0x13, 0x09, + 0xbf, 0x0a, 0x07, 0xee, 0x1e, 0xb6, 0x9e, 0x6e, 0x07, 0x77, 0x60, 0x67, 0x1a, 0x5a, 0xa1, 0x60, 0xe4, 0x4e, 0x42, + 0xc7, 0xf1, 0x22, 0x95, 0x77, 0x8f, 0xba, 0x8b, 0x32, 0x36, 0x46, 0xbd, 0x83, 0xa1, 0x57, 0x6d, 0xef, 0xc9, 0xa5, + 0x3f, 0xfb, 0xfc, 0x21, 0xfc, 0xd1, 0x99, 0x46, 0xb7, 0x95, 0xae, 0xae, 0x6d, 0x55, 0xd0, 0xd5, 0xf7, 0x6b, 0xca, + 0xb8, 0x16, 0xe1, 0x4a, 0x1f, 0xbf, 0x6f, 0x6b, 0xd0, 0x2a, 0xef, 0xd5, 0xdc, 0x68, 0x59, 0xbf, 0xaa, 0xf5, 0xaf, + 0x1b, 0xfc, 0x9e, 0x6d, 0x47, 0x5a, 0x73, 0xad, 0xb7, 0x35, 0x5f, 0xaf, 0xdb, 0x68, 0x6c, 0x31, 0xae, 0xda, 0xef, + 0x93, 0xdb, 0xd2, 0x44, 0xd1, 0x81, 0x40, 0xb0, 0x52, 0xf6, 0xb5, 0x95, 0xc2, 0x28, 0x79, 0x00, 0xef, 0x8e, 0xf5, + 0x6e, 0x66, 0x69, 0x96, 0x13, 0x7f, 0x2a, 0xe5, 0x3c, 0xd2, 0x9f, 0x3b, 0xbd, 0x3e, 0x0a, 0xb9, 0x98, 0x1c, 0x76, + 0x5a, 0xad, 0x16, 0xbc, 0xf3, 0xd3, 0xf7, 0xae, 0x18, 0xbd, 0x7e, 0xc6, 0x6f, 0x88, 0xff, 0xc4, 0x7b, 0xea, 0x3d, + 0x39, 0xf2, 0x1e, 0x3d, 0xf6, 0x3d, 0xc5, 0xce, 0x89, 0xff, 0xe4, 0xc8, 0xf7, 0x34, 0x3b, 0x27, 0xfe, 0xa3, 0xc7, + 0x7e, 0xef, 0x78, 0x62, 0x55, 0x32, 0xb8, 0x34, 0xa8, 0xf5, 0x9d, 0x5c, 0x0a, 0xfe, 0x91, 0xd6, 0x0f, 0xae, 0x2e, + 0x33, 0xb9, 0x68, 0x1d, 0xfb, 0x08, 0xa7, 0x77, 0x14, 0xcf, 0x23, 0x45, 0x14, 0x6e, 0x21, 0xb8, 0x65, 0x74, 0xa9, + 0x9a, 0x02, 0xd4, 0xcc, 0x4b, 0xbf, 0x77, 0x0c, 0x79, 0xe3, 0x5e, 0x42, 0xfc, 0xd7, 0x9d, 0x27, 0x5e, 0xfb, 0xf1, + 0x55, 0xf3, 0xe1, 0xa8, 0xd5, 0x6c, 0x7b, 0xed, 0x66, 0x27, 0x7c, 0xe2, 0x75, 0xf4, 0xbf, 0x5e, 0xcb, 0x3b, 0xf2, + 0xda, 0xe1, 0x13, 0xef, 0xc8, 0xeb, 0x84, 0x4f, 0xae, 0x1e, 0xea, 0x7c, 0x82, 0xd8, 0x3f, 0xec, 0x1d, 0xc3, 0xa7, + 0x2b, 0x6f, 0x88, 0xff, 0xb9, 0xaf, 0x3f, 0x10, 0xeb, 0x7f, 0xe6, 0x96, 0xb6, 0x9f, 0x6e, 0x2d, 0xee, 0x3c, 0xd9, + 0x5a, 0x7c, 0xf4, 0x78, 0x6b, 0xf1, 0xc3, 0x47, 0xf5, 0xe2, 0xc3, 0x89, 0xae, 0x2a, 0x4f, 0x39, 0xf1, 0x67, 0xb1, + 0x14, 0xec, 0x26, 0x68, 0x7b, 0x2d, 0xaf, 0xe5, 0x35, 0xe1, 0xbf, 0x27, 0x1d, 0x54, 0xf6, 0xba, 0x84, 0x5e, 0xe5, + 0x2a, 0x9f, 0x3c, 0xf5, 0xda, 0x8f, 0x5f, 0x76, 0x1e, 0x8f, 0xa0, 0x9d, 0x5a, 0x68, 0xdb, 0x6b, 0x5f, 0x1d, 0x3d, + 0x1d, 0xb5, 0x3c, 0xe8, 0xd8, 0x86, 0x3f, 0xd3, 0x47, 0x9d, 0x91, 0x7e, 0x68, 0x41, 0xfd, 0xb7, 0xed, 0x27, 0x79, + 0xab, 0xd9, 0x86, 0x3f, 0xbf, 0x94, 0x1a, 0x31, 0xe8, 0xe3, 0xee, 0xb8, 0x0f, 0x5b, 0xde, 0xd1, 0xd3, 0x69, 0x27, + 0xfc, 0xfc, 0xea, 0x49, 0xf8, 0x74, 0xda, 0x7e, 0xf2, 0xad, 0x7e, 0x4a, 0x9b, 0x9d, 0xf0, 0x73, 0xf8, 0xfb, 0xed, + 0x51, 0x6b, 0xda, 0x6c, 0x87, 0x4f, 0xaf, 0x8e, 0xc2, 0xa3, 0xb4, 0xf9, 0x38, 0x7c, 0x0a, 0x7f, 0xab, 0xe1, 0xa6, + 0x7c, 0x46, 0x7d, 0x0f, 0xf6, 0x7b, 0xcd, 0xdc, 0x72, 0xe7, 0xe8, 0x3c, 0xf4, 0x1e, 0x3d, 0x7c, 0xf9, 0xf4, 0xaa, + 0xf9, 0x70, 0xda, 0xee, 0x5c, 0x35, 0x77, 0xfe, 0xfc, 0x16, 0x10, 0x6f, 0x06, 0x8e, 0x29, 0x5c, 0xe0, 0xb1, 0x88, + 0x53, 0xef, 0x9f, 0x7d, 0x80, 0xf3, 0x5d, 0xe6, 0xb5, 0xf8, 0xb4, 0x79, 0x9d, 0xd1, 0xfb, 0xd8, 0xd7, 0xe2, 0x0f, + 0xb7, 0xaf, 0x73, 0xba, 0xe6, 0x54, 0xbd, 0x95, 0x1b, 0x66, 0xf4, 0xba, 0xed, 0xf5, 0x4e, 0x06, 0x03, 0x06, 0xdf, + 0x39, 0x2a, 0xba, 0xb7, 0xf0, 0x8a, 0x6b, 0xd7, 0xdb, 0xc0, 0xe1, 0x20, 0xdf, 0x4a, 0x7d, 0x92, 0xf9, 0x2e, 0x84, + 0xa4, 0x9f, 0x46, 0xc8, 0xb7, 0xf7, 0xc1, 0x47, 0xfa, 0x87, 0xe3, 0x83, 0xbb, 0xf8, 0xa8, 0xf9, 0x79, 0x95, 0x3d, + 0xab, 0xec, 0xd1, 0x33, 0xf5, 0x1c, 0xc0, 0x1d, 0x8f, 0x86, 0x7f, 0x48, 0xa1, 0x28, 0xf7, 0x75, 0x5c, 0xe1, 0xcd, + 0xaf, 0x71, 0x49, 0xeb, 0x0b, 0x5d, 0xc4, 0x37, 0xc6, 0xff, 0x1c, 0xbe, 0x65, 0x60, 0x1f, 0xae, 0xf4, 0x15, 0x63, + 0xe2, 0x77, 0xc2, 0x56, 0xd8, 0x2a, 0x1d, 0x07, 0x70, 0x89, 0x8f, 0x2c, 0xb9, 0x8c, 0xe1, 0x73, 0x9a, 0x29, 0x9f, + 0xa8, 0x0f, 0x6f, 0xc2, 0xeb, 0xce, 0xd5, 0x27, 0x50, 0xf5, 0x9b, 0xe6, 0x23, 0xdf, 0x37, 0x57, 0xff, 0xe1, 0x92, + 0xd8, 0x37, 0x70, 0x91, 0xce, 0x7a, 0xac, 0x67, 0x60, 0x53, 0xbf, 0xa6, 0x09, 0x8b, 0x03, 0x3f, 0x98, 0x0b, 0x3a, + 0xa6, 0x22, 0x6f, 0xd6, 0x6e, 0x97, 0xa9, 0x8b, 0x65, 0xc8, 0xb7, 0x1f, 0x6e, 0x14, 0xf0, 0xfa, 0x5e, 0x32, 0x30, + 0x5e, 0x2d, 0xdf, 0xa8, 0xf9, 0x7e, 0x81, 0x6d, 0x89, 0x00, 0x8e, 0x5e, 0xa9, 0x06, 0xbe, 0xd6, 0x0d, 0xda, 0x61, + 0xe7, 0x11, 0xd2, 0xbc, 0x04, 0x5e, 0x8b, 0xfa, 0x7d, 0xd0, 0x3c, 0x6a, 0xfd, 0x09, 0x39, 0xdd, 0xca, 0x81, 0x86, + 0xc6, 0xa9, 0x23, 0xaa, 0x0f, 0xde, 0xd6, 0xaf, 0xfe, 0xf9, 0x9a, 0x22, 0x3e, 0xd3, 0x6b, 0x87, 0x17, 0xac, 0x9a, + 0xf8, 0xa1, 0xbe, 0xc0, 0x3e, 0x66, 0x93, 0xc0, 0xfd, 0x9c, 0xa9, 0x7e, 0xed, 0xaa, 0xfa, 0x0a, 0x32, 0x2a, 0xaa, + 0x26, 0x02, 0x2d, 0x95, 0x2f, 0x9e, 0x65, 0x9e, 0x58, 0xad, 0x02, 0x01, 0x8e, 0x58, 0xe2, 0xe0, 0x14, 0x9e, 0x51, + 0x0d, 0xc9, 0x02, 0x97, 0x00, 0x29, 0x04, 0x13, 0xa1, 0xff, 0xaf, 0x8a, 0xed, 0x0f, 0xe3, 0x5e, 0x09, 0xd3, 0x38, + 0x9b, 0x00, 0x15, 0xc6, 0xd9, 0x64, 0xc3, 0x79, 0xa3, 0xc3, 0x09, 0x6b, 0xa5, 0xd5, 0x50, 0x95, 0x93, 0x26, 0x7f, + 0x76, 0xfb, 0xde, 0xbc, 0x9f, 0xc9, 0x07, 0x1f, 0xa8, 0xf2, 0x7d, 0x57, 0xef, 0x92, 0x6d, 0x90, 0x07, 0xfa, 0x03, + 0xe1, 0x2a, 0x21, 0x0d, 0xa4, 0x1f, 0x5c, 0xea, 0xf3, 0x8c, 0xcd, 0x43, 0x7c, 0x2d, 0xfb, 0x12, 0x7a, 0xc5, 0x46, + 0x46, 0x84, 0x61, 0xcf, 0x5c, 0x6c, 0x6e, 0xaa, 0xad, 0x21, 0x6d, 0xac, 0xad, 0xfe, 0x51, 0xac, 0x32, 0x8c, 0x49, + 0xc6, 0xfd, 0xde, 0x83, 0xf2, 0xeb, 0x8c, 0xbb, 0x36, 0x01, 0xbe, 0x5a, 0x3e, 0x10, 0x34, 0xfd, 0x67, 0xf2, 0x00, + 0xbe, 0x5b, 0xfe, 0x60, 0x08, 0x9f, 0xcc, 0x0e, 0x95, 0x28, 0x78, 0x50, 0x7d, 0xbe, 0x1c, 0xf8, 0x60, 0xe3, 0x66, + 0x96, 0xe2, 0xfb, 0x8a, 0x6f, 0x23, 0xaa, 0x3b, 0x8f, 0x2a, 0x51, 0xdd, 0x79, 0xe4, 0x4a, 0xcf, 0xb6, 0xd7, 0xee, + 0x84, 0x8f, 0x1c, 0x01, 0x70, 0xd5, 0x84, 0xff, 0x6b, 0x22, 0xe0, 0x61, 0xf8, 0xa8, 0x94, 0x01, 0xaf, 0xda, 0x9d, + 0xf0, 0x48, 0x8b, 0x9b, 0x4e, 0xf8, 0xe8, 0x07, 0xc5, 0xa0, 0x35, 0x73, 0xae, 0x1f, 0x88, 0x2d, 0xa1, 0x1a, 0x9d, + 0x54, 0xe7, 0xe3, 0xa0, 0xfc, 0x06, 0x9c, 0x39, 0x9f, 0xc6, 0x25, 0xf4, 0x3c, 0x16, 0xf0, 0x21, 0x8e, 0xfa, 0xd9, + 0xad, 0xd5, 0xe1, 0x1a, 0xbf, 0xd8, 0x32, 0x05, 0x9c, 0x70, 0x1f, 0xbb, 0x37, 0x83, 0xe1, 0x5a, 0xad, 0x7a, 0x6d, + 0xb1, 0x7d, 0x7b, 0xdb, 0x6e, 0xd2, 0xd6, 0x0d, 0xed, 0x1b, 0xe2, 0x14, 0xb3, 0x60, 0xea, 0x15, 0xf1, 0x6a, 0x92, + 0x2f, 0x93, 0x62, 0x7d, 0x7e, 0xc8, 0xed, 0x13, 0xdc, 0xb9, 0x1c, 0x4d, 0xab, 0x04, 0xf4, 0x84, 0xc1, 0x75, 0xf6, + 0xa2, 0xb0, 0xa0, 0xd7, 0x9c, 0x81, 0x15, 0x96, 0x14, 0xbf, 0xa0, 0x79, 0xdf, 0x87, 0x22, 0x3f, 0xf2, 0x95, 0x23, + 0xc9, 0x2f, 0x3f, 0x46, 0x52, 0x12, 0x76, 0x55, 0x80, 0xd5, 0x25, 0x12, 0x38, 0xb5, 0x80, 0x1f, 0x1f, 0x1d, 0x1c, + 0xec, 0x3c, 0x2f, 0x4a, 0x1b, 0x83, 0xb5, 0x56, 0x1f, 0x31, 0x70, 0x59, 0x91, 0xef, 0x22, 0xba, 0x1c, 0x57, 0xa1, + 0x10, 0x19, 0x3c, 0x5d, 0xd2, 0x58, 0x86, 0x71, 0xa6, 0x53, 0x14, 0x1c, 0x86, 0x85, 0xdb, 0xf4, 0x08, 0x15, 0x5c, + 0xc6, 0xce, 0x57, 0x4a, 0xcd, 0x39, 0xe7, 0x32, 0xb6, 0x57, 0xfd, 0x32, 0x59, 0xcb, 0x85, 0x9f, 0x76, 0x7a, 0x6f, + 0xdf, 0x9f, 0x78, 0xfa, 0x78, 0x1e, 0x1f, 0x4e, 0x3b, 0xbd, 0x63, 0x65, 0x99, 0xeb, 0x8b, 0x42, 0x44, 0x5f, 0x14, + 0xf2, 0xcc, 0xa5, 0x31, 0x88, 0xd7, 0x14, 0x87, 0x7a, 0xd9, 0xbe, 0x47, 0xb3, 0x91, 0xf6, 0x29, 0xce, 0x16, 0xa9, + 0x64, 0xf0, 0x0a, 0xde, 0x43, 0xe8, 0xda, 0x84, 0x0d, 0x2b, 0x13, 0x4d, 0xad, 0x86, 0x23, 0x33, 0xeb, 0x81, 0x1c, + 0xb3, 0x94, 0xda, 0xd4, 0x52, 0x33, 0x54, 0x99, 0xf9, 0xbc, 0xd9, 0x3a, 0x5f, 0x5c, 0xce, 0x98, 0xf4, 0x6d, 0x7e, + 0xf6, 0x07, 0xd3, 0xe1, 0x58, 0x4d, 0xd5, 0xbb, 0x28, 0x8c, 0x8b, 0xd4, 0x7e, 0x6e, 0x64, 0xed, 0x03, 0xef, 0x7a, + 0xf5, 0x46, 0x42, 0xc0, 0x8d, 0x9f, 0xe9, 0x51, 0xaf, 0x74, 0x4a, 0xba, 0x75, 0xc5, 0xf1, 0xe1, 0xf4, 0xa8, 0x77, + 0x11, 0xcd, 0xcd, 0x78, 0xaf, 0xf8, 0xc6, 0xc7, 0xe2, 0x4b, 0x8e, 0xd9, 0x57, 0xa9, 0xed, 0xfa, 0x0e, 0xa5, 0x01, + 0x78, 0xc4, 0x53, 0xbf, 0x77, 0x6c, 0x94, 0x01, 0x4f, 0x05, 0x5d, 0xfd, 0x47, 0x2d, 0x9b, 0x2d, 0x9f, 0x72, 0xa5, + 0x2d, 0xe9, 0x2e, 0xce, 0x24, 0x35, 0xbf, 0xee, 0xb4, 0xdd, 0x3b, 0x8e, 0x8d, 0x9a, 0x09, 0xcc, 0x23, 0x8f, 0x0e, + 0xa1, 0x33, 0xe8, 0x72, 0x21, 0xe3, 0x87, 0xd7, 0xf4, 0xb2, 0x19, 0xcf, 0x59, 0xe5, 0x44, 0x05, 0xa5, 0xa3, 0x9c, + 0x92, 0x57, 0x33, 0xc1, 0xcf, 0x78, 0x6d, 0x91, 0x8a, 0x85, 0x17, 0xc6, 0x43, 0xab, 0x74, 0x75, 0x1a, 0x4b, 0xdf, + 0xd3, 0x1c, 0xde, 0x7a, 0x72, 0x8d, 0xec, 0x2d, 0xfc, 0xde, 0xbf, 0xfd, 0x8f, 0xff, 0x65, 0x9c, 0xb3, 0xc7, 0x87, + 0xd3, 0xb6, 0x1d, 0x6b, 0x0d, 0xd1, 0xc5, 0x31, 0x5c, 0x30, 0xab, 0xa2, 0x89, 0xf4, 0xa6, 0x39, 0x11, 0x2c, 0x69, + 0x4e, 0xe3, 0x74, 0xec, 0xf7, 0x76, 0x23, 0xc8, 0xbd, 0x59, 0x62, 0xa0, 0xae, 0x17, 0x01, 0x09, 0xfe, 0xa6, 0xbb, + 0x11, 0x36, 0xc5, 0x5e, 0x9d, 0x56, 0xf7, 0xa6, 0x44, 0x75, 0xa0, 0x6a, 0xb7, 0x25, 0x84, 0xf9, 0x2a, 0x91, 0x61, + 0x6a, 0xa2, 0x76, 0x49, 0xa2, 0xf0, 0xbd, 0x32, 0x1a, 0xf2, 0x7f, 0xff, 0xe7, 0x7f, 0xf9, 0x6f, 0xf6, 0x11, 0x82, + 0x1c, 0xff, 0xf6, 0xdf, 0xff, 0xf3, 0xff, 0xf9, 0xdf, 0xff, 0x15, 0x12, 0xeb, 0x4d, 0x20, 0x44, 0xf1, 0x09, 0xaf, + 0x8a, 0x82, 0x68, 0x86, 0xe1, 0x41, 0x32, 0xda, 0x8c, 0xe5, 0x92, 0x8d, 0xea, 0xd7, 0x26, 0xce, 0xd4, 0x84, 0xea, + 0xb0, 0x19, 0xe8, 0xd4, 0xa1, 0x2d, 0x2a, 0x1a, 0xa9, 0xa1, 0x5c, 0xd1, 0x62, 0x71, 0x7c, 0x08, 0xf8, 0xbe, 0xdf, + 0x4d, 0xb3, 0xb0, 0xdc, 0x8e, 0xa5, 0x75, 0xfd, 0x41, 0x49, 0x51, 0x95, 0x7b, 0xe0, 0x94, 0x5f, 0xc2, 0x63, 0xd4, + 0x71, 0x8a, 0xd5, 0xee, 0xd5, 0xfa, 0x74, 0x7f, 0x5a, 0xe4, 0x92, 0x8d, 0x01, 0xe5, 0xda, 0xc1, 0xa8, 0xe2, 0x9f, + 0x4d, 0x50, 0xff, 0xd2, 0xdb, 0x42, 0x8d, 0xa2, 0x6d, 0xc6, 0x87, 0x4f, 0xff, 0x54, 0xfc, 0x65, 0x06, 0x4a, 0x96, + 0x17, 0xcc, 0xe2, 0x1b, 0x63, 0x49, 0x3e, 0x6e, 0xb5, 0xe6, 0x37, 0x68, 0x59, 0xcd, 0x80, 0x77, 0x4d, 0xa6, 0x9c, + 0x92, 0xee, 0x80, 0x2a, 0x70, 0x5a, 0xfa, 0x3f, 0x5b, 0x1e, 0x38, 0x51, 0xbd, 0x56, 0x51, 0xfc, 0x79, 0xa9, 0x5c, + 0x70, 0xec, 0x17, 0x08, 0x70, 0x1a, 0x6f, 0xe5, 0x25, 0x77, 0x17, 0xb7, 0x74, 0x7a, 0x75, 0x74, 0xaf, 0x69, 0x7b, + 0xf3, 0x02, 0x95, 0x1b, 0xa0, 0x75, 0x43, 0xab, 0x0f, 0x21, 0x58, 0x3a, 0x6d, 0xe3, 0x69, 0x67, 0x59, 0x0e, 0x2f, + 0x25, 0x9f, 0xb9, 0x11, 0x59, 0x1a, 0xd3, 0x11, 0x1d, 0x5b, 0x2f, 0xaf, 0xa9, 0xd7, 0xd1, 0xd6, 0x62, 0x7a, 0xb4, + 0x65, 0x2e, 0x03, 0x92, 0x8a, 0xc4, 0x7a, 0xad, 0xe2, 0x33, 0x38, 0x81, 0xcb, 0x71, 0xca, 0x63, 0x19, 0x29, 0x82, + 0xed, 0xba, 0x71, 0xdd, 0x18, 0xd8, 0x0c, 0x5f, 0x3a, 0xf0, 0x74, 0x75, 0x53, 0xf0, 0xb7, 0xd6, 0xaf, 0xb9, 0x15, + 0xa1, 0xea, 0xee, 0x0e, 0xa5, 0xdd, 0x35, 0xdf, 0x9a, 0x70, 0xe9, 0x9b, 0x9a, 0x9f, 0xc3, 0xc8, 0x98, 0x0e, 0xda, + 0x5e, 0xaf, 0x45, 0xb5, 0xae, 0xfd, 0x4a, 0x06, 0xbe, 0x02, 0xd3, 0x5f, 0x6f, 0xa5, 0x0a, 0xa1, 0xd5, 0x1b, 0xf2, + 0x6d, 0x69, 0x05, 0xc5, 0xf3, 0xb9, 0x6a, 0x88, 0xba, 0xc7, 0x87, 0x5a, 0x79, 0x05, 0xee, 0xa1, 0x72, 0x01, 0x74, + 0xe8, 0xdd, 0x34, 0x32, 0x47, 0x41, 0xff, 0x32, 0x41, 0x79, 0xf8, 0x5c, 0x55, 0xef, 0xff, 0x01, 0xb9, 0x37, 0x65, + 0xfc, 0x3f, 0x86, 0x00, 0x00}; + +} // 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 new file mode 100644 index 000000000000..66cd9de47a97 --- /dev/null +++ b/esphome/components/web_server/server_index_v3.h @@ -0,0 +1,4016 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver + +#ifdef USE_WEBSERVER_LOCAL +#if USE_WEBSERVER_VERSION == 3 + +#include "esphome/core/hal.h" + +namespace esphome { +namespace web_server { + +const uint8_t INDEX_GZ[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0x6d, 0x7b, 0xdb, 0xb6, 0xb2, 0x28, 0xfa, + 0xf9, 0x9e, 0x5f, 0x61, 0x73, 0xa7, 0x2e, 0x61, 0x41, 0xb4, 0x24, 0x5b, 0x8e, 0x43, 0x19, 0xd6, 0x71, 0x9c, 0xa4, + 0x49, 0x9b, 0x26, 0x69, 0x9c, 0x26, 0x4d, 0x54, 0x6d, 0x07, 0x22, 0x21, 0x09, 0x0d, 0x45, 0xa8, 0x04, 0x14, 0xdb, + 0x95, 0xf4, 0xdf, 0xef, 0x33, 0x78, 0x21, 0x41, 0x49, 0xc9, 0x5a, 0xeb, 0xdc, 0x73, 0xee, 0x73, 0x76, 0xf7, 0x8a, + 0x45, 0xbc, 0x63, 0x30, 0x18, 0xcc, 0x0c, 0x66, 0x06, 0xe7, 0xfb, 0xa9, 0x48, 0xd4, 0xfd, 0x9c, 0xed, 0x4d, 0xd5, + 0x2c, 0xbb, 0x38, 0xb7, 0xff, 0x32, 0x9a, 0x5e, 0x9c, 0x67, 0x3c, 0xff, 0xb2, 0x57, 0xb0, 0x8c, 0xf0, 0x44, 0xe4, + 0x7b, 0xd3, 0x82, 0x8d, 0x49, 0x4a, 0x15, 0x8d, 0xf9, 0x8c, 0x4e, 0xd8, 0xde, 0xd1, 0xc5, 0xf9, 0x8c, 0x29, 0xba, + 0x97, 0x4c, 0x69, 0x21, 0x99, 0x22, 0xbf, 0xbf, 0x7b, 0xd6, 0x3c, 0xbb, 0x38, 0x97, 0x49, 0xc1, 0xe7, 0x6a, 0x0f, + 0x9a, 0x24, 0x33, 0x91, 0x2e, 0x32, 0x76, 0x71, 0x74, 0x74, 0x7b, 0x7b, 0x1b, 0xfd, 0x25, 0xff, 0xc7, 0x57, 0x5a, + 0xec, 0xfd, 0x52, 0x90, 0xd7, 0xa3, 0xbf, 0x58, 0xa2, 0xa2, 0x94, 0x8d, 0x79, 0xce, 0xde, 0x14, 0x62, 0xce, 0x0a, + 0x75, 0xdf, 0x83, 0xcc, 0x9f, 0x0a, 0x12, 0x72, 0xac, 0x30, 0x43, 0xe4, 0x42, 0xed, 0xf1, 0x7c, 0x8f, 0xf7, 0x7f, + 0x29, 0x74, 0xca, 0x92, 0xe5, 0x8b, 0x19, 0x2b, 0xe8, 0x28, 0x63, 0xf1, 0x7e, 0x0b, 0x27, 0x22, 0x1f, 0xf3, 0xc9, + 0xa2, 0xfc, 0xbe, 0x2d, 0xb8, 0x72, 0xbf, 0xbf, 0xd2, 0x6c, 0xc1, 0x62, 0xb6, 0x46, 0x31, 0x1f, 0xa8, 0x21, 0x61, + 0xba, 0xe5, 0x2f, 0x55, 0xc3, 0xe1, 0x4f, 0xba, 0xc9, 0xfb, 0x39, 0x13, 0xe3, 0x3d, 0xb5, 0x4f, 0x02, 0x79, 0x3f, + 0x1b, 0x89, 0x2c, 0xe8, 0xab, 0x46, 0x10, 0xc4, 0x50, 0x06, 0x33, 0xd4, 0x4b, 0x44, 0x2e, 0xd5, 0x9e, 0xe4, 0xe4, + 0x96, 0xe7, 0xa9, 0xb8, 0xc5, 0x5f, 0x25, 0x91, 0x3c, 0xba, 0x9e, 0xd2, 0x54, 0xdc, 0xbe, 0x15, 0x42, 0x1d, 0x1c, + 0x84, 0xf6, 0xfb, 0xfe, 0xea, 0xfa, 0x9a, 0x10, 0xf2, 0x55, 0xf0, 0x74, 0xaf, 0xb5, 0x5a, 0x79, 0xa9, 0x51, 0x4e, + 0x15, 0xff, 0xca, 0x4c, 0x25, 0x74, 0x70, 0x10, 0xd0, 0x54, 0xcc, 0x15, 0x4b, 0xaf, 0xd5, 0x7d, 0xc6, 0xae, 0xa7, + 0x8c, 0x29, 0x19, 0xf0, 0x7c, 0xef, 0x89, 0x48, 0x16, 0x33, 0x96, 0xab, 0x68, 0x5e, 0x08, 0x25, 0x60, 0x60, 0x07, + 0x07, 0x41, 0xc1, 0xe6, 0x19, 0x4d, 0x18, 0xe4, 0x5f, 0x5d, 0x5f, 0x57, 0x35, 0xaa, 0x42, 0xf8, 0x56, 0x92, 0x6b, + 0x3d, 0xf4, 0x10, 0xe1, 0xe7, 0x92, 0xe4, 0xec, 0x76, 0xef, 0x03, 0xa3, 0x5f, 0x7e, 0xa5, 0xf3, 0x5e, 0x92, 0x51, + 0x29, 0xf7, 0x9e, 0x89, 0xa5, 0x9e, 0x46, 0xb1, 0x48, 0x94, 0x28, 0x42, 0x85, 0x19, 0x96, 0x68, 0xc9, 0xc7, 0xa1, + 0x9a, 0x72, 0x19, 0xdd, 0x3c, 0x48, 0xa4, 0x7c, 0xcb, 0xe4, 0x22, 0x53, 0x0f, 0xc8, 0x7e, 0x0b, 0xcb, 0x7d, 0x42, + 0x6e, 0x25, 0x52, 0xd3, 0x42, 0xdc, 0xee, 0x3d, 0x2d, 0x0a, 0x51, 0x84, 0xc1, 0xd5, 0xf5, 0xb5, 0x29, 0xb1, 0xc7, + 0xe5, 0x5e, 0x2e, 0xd4, 0x5e, 0xd9, 0x1e, 0x40, 0x3b, 0xda, 0xfb, 0x5d, 0xb2, 0xbd, 0xcf, 0x8b, 0x5c, 0xd2, 0x31, + 0xbb, 0xba, 0xbe, 0xfe, 0xbc, 0x27, 0x8a, 0xbd, 0xcf, 0x89, 0x94, 0x9f, 0xf7, 0x78, 0x2e, 0x15, 0xa3, 0x69, 0x14, + 0xa0, 0x9e, 0xee, 0x2c, 0x91, 0xf2, 0x1d, 0xbb, 0x53, 0x44, 0x61, 0xfd, 0xa9, 0x08, 0x5b, 0x4f, 0x98, 0xda, 0x93, + 0xe5, 0xbc, 0x42, 0xb4, 0xcc, 0x98, 0xda, 0x53, 0x44, 0xe7, 0x0b, 0x0b, 0x7f, 0x66, 0x3e, 0x55, 0x8f, 0x8f, 0xc3, + 0xaf, 0xf2, 0xe0, 0x40, 0x95, 0x80, 0x46, 0x4b, 0xbb, 0x42, 0x84, 0xed, 0xbb, 0xb4, 0x83, 0x03, 0x16, 0x65, 0x2c, + 0x9f, 0xa8, 0x29, 0x21, 0xa4, 0xdd, 0x93, 0x07, 0x07, 0xa1, 0x22, 0xcf, 0x65, 0x34, 0x61, 0x2a, 0x64, 0x08, 0xe1, + 0xaa, 0xf6, 0xc1, 0x41, 0x68, 0x80, 0x20, 0x88, 0xd2, 0x80, 0xab, 0xc1, 0x18, 0x45, 0x16, 0xfa, 0xd7, 0xf7, 0x79, + 0x12, 0xfa, 0xe3, 0x47, 0x58, 0x1e, 0x1c, 0x3c, 0x97, 0x91, 0x84, 0x16, 0xb1, 0x42, 0x68, 0x5d, 0x30, 0xb5, 0x28, + 0xf2, 0x3d, 0xb5, 0x56, 0xe2, 0x5a, 0x15, 0x3c, 0x9f, 0x84, 0x68, 0xe9, 0xd2, 0xbc, 0x8a, 0xeb, 0xb5, 0x19, 0xee, + 0x6f, 0x05, 0xe1, 0xe4, 0x02, 0x7a, 0x7c, 0x26, 0x42, 0x8b, 0x83, 0x9c, 0x90, 0x40, 0xea, 0xba, 0x41, 0x9f, 0xc7, + 0xbc, 0x11, 0x04, 0xd8, 0x8c, 0x12, 0xdf, 0x4a, 0x84, 0xb9, 0x02, 0xd4, 0x8d, 0xa2, 0x48, 0x21, 0x72, 0xb1, 0x74, + 0x60, 0xe1, 0xde, 0x44, 0xfb, 0x7c, 0xd0, 0x1a, 0xc6, 0x2a, 0x2a, 0x58, 0xba, 0x48, 0x58, 0x18, 0x4a, 0x9c, 0x63, + 0x81, 0xc8, 0x85, 0x6c, 0x84, 0x05, 0xb9, 0x80, 0xf5, 0x2e, 0xea, 0x8b, 0x4d, 0xc8, 0x7e, 0x0b, 0xd9, 0x41, 0x16, + 0x6e, 0x84, 0x00, 0x62, 0x3b, 0xa0, 0x82, 0x90, 0x20, 0x5f, 0xcc, 0x46, 0xac, 0x08, 0xca, 0x62, 0xbd, 0x1a, 0x5e, + 0x2c, 0x24, 0xdb, 0x4b, 0xa4, 0xdc, 0x1b, 0x2f, 0xf2, 0x44, 0x71, 0x91, 0xef, 0x05, 0x8d, 0xa2, 0x11, 0x18, 0x7c, + 0x28, 0xd1, 0x21, 0x40, 0x6b, 0x14, 0xe6, 0xa8, 0xc1, 0x07, 0xa2, 0xd1, 0x1e, 0x62, 0x18, 0x25, 0xea, 0xd9, 0xf6, + 0x2c, 0x04, 0x18, 0xe6, 0x30, 0xc9, 0x35, 0xfe, 0x64, 0x76, 0x3e, 0x4c, 0xf1, 0xab, 0xec, 0xf3, 0x68, 0x7b, 0xa7, + 0x10, 0x15, 0xcd, 0xe8, 0x3c, 0x64, 0xe4, 0x82, 0x69, 0xec, 0xa2, 0x79, 0x02, 0x63, 0xad, 0x2d, 0x5c, 0x9f, 0xc5, + 0x2c, 0xaa, 0x70, 0x0a, 0xc5, 0x2a, 0x1a, 0x8b, 0xe2, 0x29, 0x4d, 0xa6, 0x50, 0xaf, 0xc4, 0x98, 0xd4, 0x6d, 0xb8, + 0xa4, 0x60, 0x54, 0xb1, 0xa7, 0x19, 0x83, 0xaf, 0x30, 0xd0, 0x35, 0x03, 0x84, 0x73, 0xd8, 0xea, 0x19, 0x57, 0xaf, + 0x44, 0x9e, 0xb0, 0x5e, 0xee, 0xe1, 0x97, 0x5e, 0xf9, 0x4b, 0xa5, 0x0a, 0x3e, 0x5a, 0x28, 0x16, 0x06, 0x39, 0x94, + 0x08, 0x70, 0x8e, 0xb0, 0x8c, 0x14, 0xbb, 0x53, 0x57, 0x22, 0x57, 0x2c, 0x57, 0x84, 0x39, 0xa8, 0x62, 0x1e, 0xd1, + 0xf9, 0x9c, 0xe5, 0xe9, 0xd5, 0x94, 0x67, 0x69, 0x28, 0xd1, 0x1a, 0xad, 0xf1, 0x07, 0x49, 0x60, 0x92, 0xe4, 0x82, + 0xc7, 0xf0, 0xcf, 0xb7, 0xa7, 0x13, 0x2a, 0x72, 0xa1, 0xb7, 0x05, 0x23, 0x41, 0xd0, 0x1b, 0x8b, 0x22, 0xb4, 0x53, + 0xd8, 0x03, 0xd2, 0x05, 0x7d, 0xbc, 0x5d, 0x64, 0x4c, 0x22, 0xd6, 0x20, 0x25, 0xa6, 0x39, 0x08, 0xff, 0x56, 0x84, + 0x0c, 0x16, 0x80, 0xa3, 0x98, 0x6b, 0x12, 0xf8, 0x92, 0xdb, 0x4d, 0x95, 0x96, 0x44, 0xed, 0x77, 0x49, 0x52, 0x1e, + 0xa9, 0x62, 0x21, 0x15, 0x4b, 0xdf, 0xdd, 0xcf, 0x99, 0xc4, 0x3f, 0x17, 0xe4, 0x77, 0xd9, 0xff, 0x5d, 0x46, 0x6c, + 0x36, 0x57, 0xf7, 0xd7, 0x9a, 0x9a, 0xc7, 0x41, 0x80, 0x3f, 0xea, 0xa2, 0x05, 0xa3, 0x09, 0x90, 0x34, 0x0b, 0xb2, + 0x37, 0x22, 0xbb, 0x1f, 0xf3, 0x2c, 0xbb, 0x5e, 0xcc, 0xe7, 0xa2, 0x50, 0x58, 0x49, 0xb2, 0x54, 0xa2, 0x82, 0x0f, + 0xac, 0xe8, 0x52, 0xde, 0x72, 0x95, 0x4c, 0x43, 0x85, 0x96, 0x09, 0x95, 0x6c, 0xef, 0xb1, 0x10, 0x19, 0xa3, 0x79, + 0xcc, 0x09, 0xef, 0xff, 0x5c, 0xc4, 0xf9, 0x22, 0xcb, 0x7a, 0xa3, 0x82, 0xd1, 0x2f, 0x3d, 0x9d, 0x6d, 0x0e, 0x87, + 0x58, 0xff, 0xbe, 0x2c, 0x0a, 0x7a, 0x0f, 0x05, 0x09, 0x81, 0x62, 0x7d, 0x1e, 0xff, 0x7c, 0xfd, 0xfa, 0x55, 0x64, + 0xf6, 0x0a, 0x1f, 0xdf, 0x87, 0xbc, 0xdc, 0x7f, 0x7c, 0x8d, 0xc7, 0x85, 0x98, 0x6d, 0x74, 0x6d, 0x40, 0xc7, 0x7b, + 0xdf, 0x18, 0x02, 0x23, 0x7c, 0xdf, 0x34, 0xed, 0x8f, 0xe0, 0x95, 0xc6, 0x7c, 0xc8, 0x24, 0xb6, 0x5f, 0xf8, 0x27, + 0x36, 0xc9, 0x21, 0x47, 0xdf, 0x1f, 0xad, 0x2a, 0xee, 0x97, 0x8c, 0xe8, 0x71, 0xce, 0xe1, 0x60, 0x84, 0x31, 0x26, + 0x54, 0x25, 0xd3, 0x25, 0xd3, 0x8d, 0xad, 0xdd, 0x88, 0xd9, 0x7a, 0x8d, 0x5f, 0x09, 0x87, 0xf5, 0x6a, 0x9f, 0x10, + 0xae, 0xe9, 0x15, 0x51, 0xab, 0x15, 0x27, 0x84, 0x23, 0xfc, 0x96, 0x93, 0x25, 0x75, 0x13, 0x82, 0x93, 0x0d, 0xb6, + 0x67, 0x6c, 0xa8, 0x0c, 0x9c, 0x80, 0x5f, 0x59, 0xa1, 0x58, 0x11, 0x2b, 0x89, 0x0b, 0x36, 0xce, 0x60, 0x1c, 0xfb, + 0x6d, 0x3c, 0xa5, 0xf2, 0x6a, 0x4a, 0xf3, 0x09, 0x4b, 0xe3, 0x57, 0x62, 0x8d, 0x99, 0x24, 0xc1, 0x98, 0xe7, 0x34, + 0xe3, 0xff, 0xb0, 0x34, 0xb0, 0xe7, 0xc2, 0x63, 0xb5, 0xc7, 0xee, 0x14, 0xcb, 0x53, 0xb9, 0xf7, 0xfc, 0xdd, 0xaf, + 0x2f, 0xed, 0x62, 0xd6, 0xce, 0x0a, 0xb4, 0x94, 0x8b, 0x39, 0x2b, 0x42, 0x84, 0xed, 0x59, 0xf1, 0x94, 0x6b, 0x3a, + 0xf9, 0x2b, 0x9d, 0x9b, 0x14, 0x2e, 0x7f, 0x9f, 0xa7, 0x54, 0xb1, 0x37, 0x2c, 0x4f, 0x79, 0x3e, 0x21, 0xfb, 0x6d, + 0x93, 0x3e, 0xa5, 0x36, 0x23, 0x2d, 0x93, 0x6e, 0x1e, 0x3c, 0xcd, 0xf4, 0xdc, 0xcb, 0xcf, 0x45, 0x88, 0xd6, 0x52, + 0x51, 0xc5, 0x93, 0x3d, 0x9a, 0xa6, 0x2f, 0x72, 0xae, 0xb8, 0x1e, 0x61, 0x01, 0x4b, 0x04, 0xb8, 0xca, 0xcc, 0xa9, + 0xe1, 0x46, 0x1e, 0x22, 0x1c, 0x86, 0xf6, 0x2c, 0x98, 0x22, 0xbb, 0x66, 0x07, 0x07, 0x15, 0xe5, 0xef, 0xb3, 0xd8, + 0x64, 0x92, 0xc1, 0x10, 0x45, 0xf3, 0x85, 0x84, 0xc5, 0x76, 0x5d, 0xc0, 0x41, 0x23, 0x46, 0x92, 0x15, 0x5f, 0x59, + 0x5a, 0x22, 0x88, 0x0c, 0xd1, 0x72, 0xa3, 0x0f, 0xbb, 0x3d, 0x14, 0x19, 0x0c, 0x7b, 0x3e, 0x09, 0x67, 0x16, 0xd9, + 0x0d, 0xa7, 0xc2, 0x99, 0x2c, 0x89, 0x4a, 0x08, 0x07, 0x6a, 0x49, 0x58, 0x72, 0xe2, 0xe6, 0x37, 0x0f, 0x25, 0xf0, + 0x10, 0x3e, 0xe5, 0x70, 0x67, 0xee, 0xd3, 0xaf, 0xfa, 0xf0, 0xc8, 0xb1, 0x44, 0x58, 0x99, 0x91, 0xe6, 0x08, 0xad, + 0x11, 0x56, 0x6e, 0xb8, 0x86, 0x28, 0x39, 0xbe, 0x08, 0x4e, 0x6d, 0xf2, 0x96, 0xeb, 0x63, 0x1b, 0x68, 0x1b, 0x55, + 0xec, 0xe0, 0x20, 0x64, 0x51, 0x89, 0x18, 0x64, 0xbf, 0x6d, 0x17, 0xc9, 0x83, 0xd6, 0x37, 0xc6, 0x0d, 0x3d, 0x6b, + 0x06, 0x67, 0x9f, 0x45, 0xb9, 0xb8, 0x4c, 0x12, 0x26, 0xa5, 0x28, 0x0e, 0x0e, 0xf6, 0x75, 0xf9, 0x92, 0xb3, 0x80, + 0x45, 0x7c, 0x7d, 0x9b, 0x57, 0x43, 0x40, 0xd5, 0x69, 0xeb, 0xf8, 0x26, 0x52, 0xf1, 0x4d, 0x8e, 0x09, 0x89, 0x83, + 0x9b, 0x9b, 0xa0, 0xa1, 0xb0, 0x85, 0xc3, 0x84, 0xb9, 0xae, 0xef, 0x9f, 0x30, 0xc3, 0x16, 0x6a, 0x26, 0x64, 0x0b, + 0x34, 0x3b, 0xf9, 0xc1, 0xb0, 0x3e, 0x24, 0xac, 0x70, 0x8e, 0xd6, 0xde, 0x8a, 0xee, 0x6c, 0x5a, 0xf3, 0x37, 0x66, + 0xe9, 0x96, 0x13, 0xcd, 0x53, 0x78, 0xeb, 0x38, 0x60, 0xc3, 0x35, 0xd6, 0xb0, 0x77, 0xb3, 0x11, 0x7a, 0xa0, 0x03, + 0x35, 0xec, 0xd9, 0x7c, 0x92, 0x1b, 0xc8, 0x15, 0xec, 0xef, 0x05, 0x93, 0xca, 0x20, 0x72, 0xa8, 0xb0, 0xc0, 0x70, + 0x46, 0x6d, 0x32, 0x9d, 0x35, 0x96, 0x74, 0xd7, 0xd8, 0x5e, 0xcf, 0xe1, 0x6c, 0x94, 0x80, 0xd4, 0xdf, 0xc7, 0x27, + 0x18, 0xab, 0x42, 0xab, 0xd5, 0x5b, 0xee, 0x5a, 0xa9, 0xd6, 0xb2, 0xe4, 0xd7, 0x36, 0x16, 0x85, 0x49, 0x64, 0x0f, + 0xe7, 0xfd, 0xb6, 0x1d, 0xbf, 0x1c, 0x92, 0xfd, 0x56, 0x89, 0xc5, 0x16, 0xac, 0x66, 0x3c, 0x06, 0x8a, 0xaf, 0x4d, + 0x53, 0x48, 0x9f, 0xf5, 0x35, 0x7c, 0x89, 0xa6, 0x5b, 0xb8, 0x3a, 0x25, 0x03, 0xe0, 0x3a, 0xa2, 0xe9, 0xf0, 0x5b, + 0xf8, 0xe4, 0x28, 0x42, 0xa8, 0xb6, 0xf3, 0x2a, 0xc2, 0xf1, 0xb5, 0x4e, 0x38, 0x36, 0xa6, 0x11, 0xcc, 0xcb, 0x2a, + 0x41, 0x89, 0x66, 0x76, 0xab, 0x57, 0x59, 0x58, 0xea, 0xc1, 0x54, 0x53, 0xf2, 0x9a, 0x78, 0x45, 0x67, 0x4c, 0x86, + 0x0c, 0xe1, 0x6f, 0x15, 0x30, 0xf8, 0x09, 0x45, 0x86, 0xde, 0x19, 0x9a, 0xc3, 0x19, 0x0a, 0xec, 0x2e, 0x30, 0x69, + 0xf5, 0x2d, 0x97, 0x63, 0x36, 0xc8, 0x87, 0x15, 0x6f, 0xe7, 0x4d, 0x5e, 0x1f, 0xce, 0x92, 0xd4, 0xf6, 0x9b, 0x49, + 0x33, 0x40, 0xd3, 0x2c, 0x84, 0x44, 0x78, 0xbf, 0xb5, 0xb9, 0x92, 0xae, 0x54, 0x35, 0xc7, 0xc1, 0x10, 0xd6, 0x41, + 0x1f, 0x1b, 0x11, 0x97, 0xfa, 0x6f, 0x6d, 0xab, 0x01, 0xd8, 0xae, 0x01, 0x33, 0xa2, 0x71, 0x46, 0x55, 0xd8, 0x3e, + 0x6a, 0x01, 0x63, 0xfa, 0x95, 0xc1, 0xa9, 0x82, 0xd0, 0xf6, 0x54, 0x58, 0xb4, 0xc8, 0xe5, 0x94, 0x8f, 0x55, 0xf8, + 0x41, 0x6a, 0xa2, 0xc2, 0x32, 0xc9, 0x40, 0xc2, 0xf1, 0xd8, 0x63, 0x4d, 0x70, 0x3e, 0xc0, 0x30, 0x4a, 0x56, 0x8c, + 0xb9, 0x91, 0x6a, 0xc2, 0x05, 0xe4, 0xa1, 0x62, 0xad, 0x2b, 0x32, 0xe3, 0x4a, 0x4b, 0xe0, 0x1e, 0xdb, 0x7d, 0xd3, + 0x62, 0x6c, 0xa9, 0x81, 0xf4, 0x38, 0x58, 0x19, 0xfb, 0x24, 0xc2, 0x26, 0xaa, 0x48, 0x89, 0x97, 0xe2, 0x96, 0x15, + 0x57, 0x14, 0x06, 0x1f, 0x9b, 0xea, 0x6b, 0x73, 0x14, 0x68, 0x8a, 0xaf, 0x7a, 0x0e, 0x5f, 0x6e, 0xf4, 0xc4, 0xdf, + 0x14, 0x62, 0xc6, 0x25, 0x03, 0xbe, 0xcd, 0xc0, 0x3f, 0x87, 0x8d, 0xa6, 0x77, 0x24, 0x1c, 0x37, 0xac, 0xc4, 0xaf, + 0xcb, 0x97, 0x75, 0xfc, 0xba, 0x79, 0xf0, 0x74, 0xe2, 0x28, 0x60, 0x7d, 0x1f, 0x23, 0x1c, 0x5a, 0xf1, 0xc2, 0x3b, + 0xe9, 0xa2, 0x29, 0xb2, 0xc7, 0xfc, 0x6a, 0xa5, 0x3c, 0x31, 0xae, 0xc6, 0x39, 0x32, 0xb3, 0x6d, 0xd0, 0x9a, 0xa6, + 0x29, 0xb0, 0x78, 0x85, 0xc8, 0x32, 0xef, 0xb0, 0xc2, 0xb2, 0x57, 0x1e, 0x4f, 0x37, 0x0f, 0x9e, 0x5e, 0x7f, 0xef, + 0x84, 0x82, 0x7c, 0xff, 0x90, 0x72, 0x03, 0xcd, 0x53, 0x56, 0x80, 0x5c, 0xe9, 0xad, 0x96, 0x3d, 0x67, 0xaf, 0x44, + 0x9e, 0xb3, 0x44, 0xb1, 0x14, 0x84, 0x16, 0x60, 0x83, 0xa7, 0x42, 0xaa, 0x32, 0xb1, 0x1a, 0xbd, 0xf4, 0x85, 0xd0, + 0x28, 0xa1, 0x59, 0x16, 0x1a, 0x01, 0x65, 0x26, 0xbe, 0xb2, 0x1d, 0xa3, 0xee, 0xd5, 0x86, 0x5c, 0x36, 0xc3, 0xbc, + 0x66, 0x58, 0x24, 0xe7, 0x19, 0x4f, 0x58, 0x79, 0x78, 0x5d, 0x47, 0x3c, 0x4f, 0xd9, 0x1d, 0xd0, 0x11, 0x74, 0x71, + 0x71, 0xd1, 0xc2, 0x6d, 0xb4, 0x36, 0x00, 0x5f, 0x6e, 0x01, 0xf6, 0x3b, 0xc7, 0xa6, 0x11, 0xc4, 0x97, 0x3b, 0xc9, + 0x1a, 0xf2, 0xce, 0x4a, 0xee, 0x04, 0x2d, 0x43, 0x9e, 0x11, 0x4e, 0x59, 0xc6, 0x14, 0x73, 0xe4, 0x1c, 0x98, 0x69, + 0xb3, 0x75, 0xdf, 0x96, 0xf0, 0x2b, 0xd1, 0xc9, 0xed, 0x32, 0xb7, 0xe6, 0xb2, 0x14, 0xdd, 0xab, 0xe5, 0xa9, 0xa0, + 0xdd, 0x57, 0x66, 0x79, 0xa8, 0x52, 0x34, 0x99, 0x1a, 0x89, 0x3d, 0xdc, 0x9a, 0x52, 0xd5, 0x86, 0x25, 0xed, 0xe5, + 0x26, 0xfa, 0x54, 0xd8, 0x61, 0xee, 0x02, 0xc1, 0xb5, 0x25, 0x0a, 0x0c, 0x84, 0x40, 0xb3, 0x6c, 0x57, 0x34, 0xcb, + 0x46, 0x34, 0xf9, 0x52, 0xc7, 0xfe, 0x0a, 0x0d, 0xc8, 0x26, 0x35, 0xf6, 0xb2, 0x3c, 0x92, 0xe5, 0xcf, 0xdb, 0x51, + 0xe9, 0xda, 0x46, 0x09, 0xf7, 0x5b, 0x15, 0xda, 0xd7, 0x17, 0xfa, 0x9b, 0xd8, 0xae, 0x47, 0x24, 0xed, 0xcc, 0x42, + 0xa0, 0x02, 0xff, 0x12, 0xe3, 0x1c, 0x3d, 0xb0, 0x78, 0x07, 0x82, 0xc7, 0x7a, 0x63, 0x20, 0x0a, 0x2d, 0xd7, 0x29, + 0x97, 0xdf, 0x86, 0xc0, 0xff, 0x96, 0x51, 0x3e, 0xf1, 0x7a, 0xf8, 0x77, 0x07, 0x5a, 0xd2, 0x38, 0xcb, 0x38, 0x97, + 0x23, 0xb3, 0x0c, 0x85, 0x23, 0x34, 0xbf, 0x00, 0xf3, 0xa2, 0xf1, 0xfd, 0xb5, 0xc9, 0xd2, 0x7c, 0x19, 0x0c, 0x23, + 0xef, 0xf9, 0x0c, 0x45, 0x0d, 0x05, 0x2c, 0x51, 0x35, 0x67, 0xae, 0xa8, 0x89, 0x92, 0x96, 0x6b, 0x37, 0xe2, 0xb8, + 0xa5, 0xb9, 0x05, 0x09, 0xc3, 0x30, 0x27, 0xba, 0x0d, 0xc3, 0xdf, 0x57, 0xb3, 0xc8, 0xb7, 0x66, 0x91, 0x47, 0x9e, + 0xb4, 0x85, 0x2a, 0x64, 0xf6, 0xaa, 0xc7, 0x4a, 0x22, 0xbf, 0x14, 0xb0, 0xac, 0x11, 0x50, 0x68, 0x54, 0x12, 0xdc, + 0x8c, 0x28, 0x5c, 0x58, 0x51, 0xc7, 0xe2, 0x1a, 0x90, 0x8c, 0xaa, 0x8a, 0x40, 0x66, 0x73, 0xd4, 0x64, 0x5f, 0x81, + 0x0b, 0xb4, 0xc1, 0xdf, 0xaf, 0xd7, 0x16, 0x4a, 0x0c, 0xd9, 0xd5, 0xa9, 0x31, 0xc6, 0x1e, 0x58, 0xb0, 0x20, 0xb9, + 0x61, 0x86, 0x0d, 0xeb, 0xb3, 0x09, 0x9c, 0xb2, 0xdd, 0x7d, 0x42, 0x44, 0x05, 0x9b, 0x3c, 0xda, 0xc1, 0x5d, 0x09, + 0x84, 0xa9, 0x63, 0x4b, 0x8b, 0x6a, 0xe2, 0x84, 0x04, 0x4e, 0x3b, 0x11, 0xf4, 0x97, 0x35, 0xe1, 0x30, 0xf6, 0x8a, + 0xad, 0x63, 0x20, 0xaa, 0xc5, 0x2e, 0x78, 0xef, 0xc2, 0x9a, 0x5a, 0x3b, 0x1e, 0xc4, 0x8b, 0x1a, 0xc4, 0x3d, 0xd0, + 0x0a, 0x43, 0xbc, 0xc4, 0x90, 0xd0, 0x7a, 0xe5, 0x90, 0xe1, 0xc2, 0x2c, 0xc4, 0x16, 0x14, 0x37, 0xd9, 0x4f, 0x8d, + 0x85, 0x20, 0xcb, 0xe6, 0xc0, 0xdf, 0xf9, 0x47, 0x44, 0x08, 0x83, 0x97, 0xab, 0xd5, 0x16, 0xda, 0xed, 0xe4, 0x42, + 0x51, 0x54, 0x49, 0x87, 0xab, 0xd5, 0x2b, 0x81, 0x42, 0xcb, 0xff, 0x62, 0x86, 0xfa, 0x8e, 0xe8, 0x5e, 0xbe, 0x84, + 0x52, 0x9a, 0x1d, 0xad, 0x52, 0x4a, 0xc1, 0xa1, 0x8e, 0xb5, 0xf5, 0x85, 0x52, 0x1e, 0xe5, 0xbe, 0xda, 0x22, 0x60, + 0x3a, 0xd1, 0x9e, 0xd4, 0xd5, 0x94, 0xaf, 0x6c, 0xd3, 0x12, 0x21, 0x14, 0xe7, 0x5a, 0x96, 0xd9, 0xdf, 0x25, 0x5f, + 0x1e, 0x1c, 0xe4, 0x5e, 0x43, 0x37, 0x25, 0xa5, 0xf8, 0x2b, 0x84, 0x53, 0x59, 0xde, 0xe7, 0x9a, 0x7d, 0xf9, 0xcb, + 0x9d, 0x43, 0x5b, 0xd2, 0x69, 0xab, 0x07, 0x82, 0x39, 0xbd, 0xa5, 0x5c, 0xed, 0x95, 0xad, 0x18, 0xc1, 0x3c, 0x64, + 0x68, 0x69, 0xb9, 0x8d, 0xa8, 0x60, 0xc0, 0x3f, 0x02, 0x59, 0x70, 0x5c, 0xb4, 0x41, 0xfc, 0x64, 0xca, 0x40, 0x95, + 0xed, 0x18, 0x89, 0x52, 0x3c, 0xdc, 0xb7, 0x07, 0x89, 0x6d, 0x78, 0xf7, 0xd8, 0xd7, 0x9b, 0xd5, 0x6b, 0xd2, 0xc0, + 0x9c, 0x15, 0x63, 0x51, 0xcc, 0x5c, 0xde, 0x7a, 0xe3, 0xdb, 0x12, 0x47, 0x3e, 0x0e, 0x77, 0xb6, 0x6d, 0x45, 0x80, + 0xde, 0x86, 0xec, 0x5d, 0x49, 0xed, 0xb5, 0xd3, 0xb4, 0x3c, 0x80, 0x8d, 0x82, 0xd0, 0x61, 0x66, 0xee, 0x4b, 0xf9, + 0x56, 0xbd, 0xda, 0x33, 0xba, 0x93, 0xfd, 0x76, 0xaf, 0x94, 0xfc, 0x1c, 0x36, 0xf4, 0x8c, 0x8e, 0xc3, 0x9e, 0xaa, + 0x62, 0x91, 0xa5, 0x76, 0xb0, 0x70, 0xc4, 0x59, 0x3c, 0xba, 0xe5, 0x59, 0x56, 0xa5, 0xfe, 0x27, 0xa4, 0x3d, 0xb7, + 0xa4, 0x5d, 0x38, 0xd2, 0x0e, 0xa4, 0x02, 0x48, 0xbb, 0x69, 0xae, 0xaa, 0x2e, 0xb6, 0xb6, 0xa7, 0x30, 0x44, 0x3d, + 0xd7, 0xe2, 0x34, 0xf4, 0xb7, 0x70, 0x23, 0x40, 0x25, 0xf3, 0xf5, 0x25, 0xb4, 0xfa, 0x18, 0x10, 0x03, 0x8d, 0x4e, + 0x93, 0xf9, 0x9a, 0x8a, 0x2f, 0x21, 0xc2, 0xf9, 0x9a, 0x95, 0x98, 0x7d, 0xf9, 0x14, 0x94, 0x76, 0xde, 0x74, 0xe0, + 0x1c, 0xd3, 0xc9, 0xff, 0x11, 0x1f, 0xe5, 0x66, 0x27, 0xed, 0xec, 0x72, 0x37, 0x3b, 0xa0, 0xf5, 0xd5, 0xec, 0xd2, + 0xef, 0x53, 0x7b, 0x3d, 0x3d, 0x59, 0x4e, 0xaf, 0x5a, 0xef, 0xd5, 0x2a, 0xdc, 0x48, 0x01, 0x8d, 0xbe, 0x95, 0x52, + 0x8a, 0xb2, 0x75, 0xa0, 0x01, 0x3e, 0x64, 0x20, 0x61, 0x6d, 0x26, 0x5d, 0x9e, 0x72, 0x2f, 0xff, 0x95, 0x9e, 0x47, + 0x2b, 0xee, 0x4d, 0xfd, 0x2b, 0x31, 0x9b, 0x03, 0x43, 0xb6, 0x81, 0xd2, 0x13, 0x66, 0x3b, 0xac, 0xf2, 0xd7, 0x3b, + 0xd2, 0x6a, 0x75, 0xf4, 0x7e, 0xac, 0x61, 0x53, 0x29, 0x35, 0xef, 0xb7, 0xd6, 0x8b, 0x32, 0xa9, 0x24, 0x1c, 0xbb, + 0x74, 0x2b, 0x57, 0x9b, 0x9a, 0x19, 0x97, 0xf1, 0x3a, 0x94, 0x86, 0x0e, 0x4b, 0xa0, 0x75, 0x1e, 0xf9, 0x71, 0xe8, + 0xee, 0xaf, 0xff, 0xba, 0x02, 0xce, 0x72, 0xbd, 0x01, 0xbe, 0xe5, 0x7a, 0xfd, 0x58, 0x59, 0x49, 0x1b, 0x3f, 0xde, + 0x21, 0xf7, 0x96, 0xd0, 0xab, 0x32, 0xad, 0xcc, 0x38, 0x18, 0x42, 0xda, 0x16, 0x0b, 0x49, 0x96, 0x33, 0x91, 0xb2, + 0x38, 0x10, 0x73, 0x96, 0x07, 0x6b, 0xd0, 0xb3, 0x5a, 0x04, 0xf8, 0x28, 0xc3, 0xe5, 0xdb, 0xba, 0xbe, 0x35, 0x7e, + 0xac, 0xd6, 0xa0, 0x0a, 0x7b, 0xc9, 0x77, 0x28, 0x63, 0xdf, 0xb3, 0x42, 0x6a, 0x9e, 0xb4, 0x64, 0x6f, 0x5f, 0xf2, + 0xea, 0x80, 0x7a, 0xc9, 0xe3, 0x6f, 0x57, 0xa9, 0x04, 0x92, 0xa0, 0x1d, 0x9d, 0x46, 0xc7, 0x01, 0xd2, 0x1a, 0xe3, + 0x67, 0x4e, 0x63, 0xbc, 0x28, 0x35, 0xc6, 0xcf, 0x15, 0x59, 0x6c, 0x68, 0x8c, 0xff, 0x96, 0xe4, 0xb9, 0xea, 0x3f, + 0x77, 0xda, 0xf4, 0x37, 0x22, 0xe3, 0xc9, 0x7d, 0x18, 0x64, 0x5c, 0x35, 0xe1, 0x36, 0x31, 0xc0, 0x4b, 0x93, 0x01, + 0xaa, 0x46, 0xad, 0xef, 0x5e, 0x3b, 0xf9, 0x0f, 0x73, 0x49, 0x82, 0x07, 0x19, 0x57, 0x0f, 0x02, 0x3c, 0x55, 0xe4, + 0x33, 0xfc, 0x7a, 0xb0, 0x0c, 0x7f, 0xa5, 0x6a, 0x1a, 0x15, 0x34, 0x4f, 0xc5, 0x2c, 0x44, 0x8d, 0x20, 0x40, 0x91, + 0xd4, 0x42, 0xc8, 0x23, 0xb4, 0x7e, 0xf0, 0x19, 0xff, 0x23, 0x48, 0xd0, 0x0f, 0x1a, 0x53, 0x85, 0x15, 0x25, 0x9f, + 0xcf, 0x1f, 0x2c, 0xff, 0x11, 0xeb, 0x8b, 0xcf, 0xf8, 0xa9, 0x2a, 0xd5, 0xfa, 0xf8, 0x8e, 0x91, 0x10, 0x91, 0x8b, + 0xa7, 0x6e, 0x48, 0x57, 0x62, 0x66, 0x14, 0xfc, 0x01, 0xc2, 0x5f, 0x41, 0xaf, 0x7b, 0xc1, 0x2b, 0x22, 0x64, 0xef, + 0x60, 0xf6, 0x49, 0x20, 0xb4, 0xf2, 0x20, 0x38, 0x38, 0xf0, 0xd2, 0x4a, 0x16, 0x02, 0xff, 0x25, 0x48, 0x4d, 0x54, + 0xc7, 0x8c, 0x42, 0x4b, 0x7f, 0x89, 0x90, 0x23, 0xd7, 0x4c, 0xe8, 0x34, 0xd5, 0x76, 0xc7, 0xf2, 0x81, 0xd1, 0x3d, + 0x44, 0x5c, 0xb1, 0x82, 0x2a, 0x51, 0x0c, 0x91, 0xcf, 0x96, 0xe0, 0x57, 0x9c, 0x7c, 0x1e, 0xec, 0xfd, 0x3f, 0xff, + 0xe3, 0xcf, 0xf1, 0x9f, 0xc5, 0xf0, 0x33, 0x96, 0x8c, 0x1c, 0x9d, 0x87, 0xfd, 0x38, 0xdc, 0x6f, 0x36, 0x57, 0x7f, + 0x1e, 0x0d, 0xfe, 0x9b, 0x36, 0xff, 0xb9, 0x6c, 0x7e, 0x1a, 0xa2, 0x55, 0xf8, 0xe7, 0x51, 0x7f, 0x60, 0xbf, 0x06, + 0xff, 0x7d, 0xf1, 0xa7, 0x1c, 0x1e, 0x9a, 0xc4, 0x07, 0x08, 0x1d, 0x4d, 0xf0, 0x1f, 0x92, 0x1c, 0x35, 0x9b, 0x17, + 0x47, 0x13, 0xfc, 0x8b, 0x24, 0x47, 0xf0, 0xf7, 0x5e, 0x91, 0xb7, 0x6c, 0xf2, 0xf4, 0x6e, 0x1e, 0x7e, 0xbe, 0x58, + 0x3d, 0x58, 0xbe, 0xe2, 0x6b, 0x68, 0x77, 0xf0, 0xdf, 0x7f, 0xfe, 0x29, 0x83, 0x1f, 0x2f, 0xc8, 0xd1, 0xb0, 0x81, + 0x42, 0x9d, 0x7c, 0x48, 0xcc, 0x9f, 0xb0, 0x1f, 0x0f, 0xfe, 0xdb, 0x0e, 0x25, 0xf8, 0xf1, 0xcf, 0xcf, 0xe7, 0x17, + 0x64, 0xb8, 0x0a, 0x83, 0xd5, 0x8f, 0x68, 0x85, 0xd0, 0xea, 0x01, 0xfa, 0x8c, 0x83, 0x49, 0x80, 0xf0, 0x4f, 0x92, + 0x1c, 0xfd, 0x78, 0x34, 0xc1, 0xbf, 0x49, 0x72, 0x14, 0x1c, 0x4d, 0xf0, 0x7b, 0x41, 0x8e, 0xfe, 0x3b, 0xec, 0xc7, + 0x46, 0x09, 0xb7, 0xd2, 0xea, 0x8f, 0x15, 0xdc, 0x84, 0xd0, 0x82, 0xd1, 0x95, 0xe2, 0x2a, 0x63, 0xe8, 0xc1, 0x11, + 0xc7, 0x8f, 0x05, 0x00, 0x2b, 0x54, 0xa0, 0xa4, 0xd1, 0x97, 0xb0, 0xcb, 0x1b, 0x58, 0x78, 0xc0, 0xa0, 0x07, 0x31, + 0xc7, 0x46, 0x4f, 0x20, 0x63, 0x65, 0x6e, 0x6f, 0x25, 0x5c, 0xdf, 0xe2, 0x2b, 0xf2, 0x58, 0x84, 0x6d, 0x84, 0x39, + 0x85, 0x1f, 0x1d, 0x84, 0x3f, 0x28, 0x7b, 0xe1, 0x09, 0xdb, 0xdc, 0x60, 0x58, 0x2e, 0x0c, 0x3f, 0x13, 0x20, 0xfc, + 0x72, 0x47, 0xa6, 0x9a, 0x82, 0xfa, 0x01, 0xe1, 0x4f, 0xb5, 0xeb, 0x51, 0x7c, 0xa5, 0x48, 0x89, 0x1c, 0xef, 0x0a, + 0xc6, 0x3e, 0xd0, 0xec, 0x0b, 0x2b, 0xc2, 0xa7, 0x0a, 0xb7, 0x3b, 0x8f, 0xb0, 0x56, 0x55, 0xef, 0xb7, 0x51, 0xaf, + 0xbc, 0xdd, 0x7a, 0x2e, 0xcc, 0x7d, 0x02, 0x9c, 0xc2, 0x75, 0x7d, 0x0d, 0xac, 0xfd, 0x3e, 0xdf, 0x52, 0x6a, 0x15, + 0xf4, 0x36, 0x40, 0xf5, 0xab, 0x54, 0x9e, 0x7f, 0xa5, 0x19, 0x4f, 0xf7, 0x14, 0x9b, 0xcd, 0x33, 0xaa, 0xd8, 0x9e, + 0x9d, 0xf3, 0x1e, 0x85, 0x86, 0x82, 0x92, 0xa7, 0xf8, 0x5b, 0x56, 0x9b, 0xf6, 0x6f, 0x27, 0xe7, 0xc1, 0xde, 0x09, + 0xe1, 0x3e, 0xcb, 0xf2, 0x25, 0x92, 0x96, 0xd7, 0x65, 0x9b, 0x37, 0x82, 0xcd, 0x36, 0x28, 0xcb, 0x86, 0xfa, 0xfc, + 0xce, 0x31, 0xdc, 0x6f, 0x12, 0xd2, 0xe9, 0x07, 0xe7, 0xf2, 0xeb, 0xe4, 0x22, 0x80, 0x9b, 0x9c, 0x82, 0x48, 0xa6, + 0x95, 0x47, 0x50, 0x82, 0x92, 0x56, 0x8f, 0x9e, 0xb3, 0x1e, 0x6d, 0x34, 0x1c, 0x9b, 0x9d, 0x10, 0x3e, 0xa0, 0xa6, + 0x7e, 0x86, 0xa7, 0x38, 0x25, 0xcd, 0x36, 0x5e, 0x90, 0x96, 0xae, 0xd2, 0x5b, 0x9c, 0x27, 0xb6, 0x9f, 0x83, 0x83, + 0xb0, 0x88, 0x32, 0x2a, 0xd5, 0x0b, 0xd0, 0x08, 0x90, 0x05, 0x9e, 0x92, 0x22, 0x62, 0x77, 0x2c, 0x09, 0x13, 0x84, + 0xa7, 0x96, 0x06, 0xa1, 0x1e, 0x5a, 0x10, 0xaf, 0x18, 0xc8, 0x19, 0x44, 0xb2, 0xfe, 0x74, 0xd0, 0x1e, 0x12, 0x42, + 0x82, 0xfd, 0x66, 0x33, 0xe8, 0x17, 0xe4, 0x0f, 0x19, 0x43, 0x8a, 0xc7, 0x4e, 0x93, 0x5f, 0x20, 0xa9, 0xe3, 0x25, + 0x85, 0xef, 0x45, 0xa4, 0x98, 0x54, 0x21, 0x24, 0x83, 0x92, 0x20, 0x77, 0x18, 0x1e, 0x9c, 0x1f, 0x05, 0x0d, 0x48, + 0xd5, 0x28, 0x8a, 0x70, 0x41, 0xee, 0x15, 0x8a, 0xa7, 0x83, 0xe3, 0xa1, 0x7f, 0x46, 0x98, 0x54, 0xe8, 0xff, 0x5e, + 0xf5, 0xa7, 0x83, 0x96, 0xee, 0xff, 0x22, 0xe8, 0x87, 0x05, 0xc9, 0xf7, 0xed, 0x3d, 0x4f, 0x2c, 0x99, 0x9e, 0x2f, + 0x8a, 0xed, 0x00, 0x6d, 0xdf, 0x29, 0x69, 0x76, 0xe2, 0x30, 0xf5, 0x67, 0xd2, 0x84, 0x0e, 0x2d, 0x28, 0x70, 0x46, + 0xa0, 0x3c, 0x2e, 0x08, 0x74, 0x5a, 0x55, 0xbb, 0x57, 0xb1, 0x4d, 0xf8, 0x31, 0xf8, 0xb1, 0xff, 0x9b, 0x8c, 0x7f, + 0x92, 0x66, 0x04, 0xbf, 0xc9, 0xd5, 0x0a, 0xfe, 0xfe, 0x24, 0xfb, 0x30, 0x2c, 0x9d, 0xf6, 0x87, 0x4d, 0xfb, 0x05, + 0xd2, 0x24, 0x8b, 0xf5, 0x80, 0x71, 0x5e, 0xf2, 0x63, 0x66, 0x71, 0xc6, 0xc4, 0xcc, 0xe0, 0xe0, 0x80, 0x0f, 0x68, + 0xa3, 0x3d, 0x84, 0x1b, 0x81, 0x42, 0xc9, 0x0f, 0x5c, 0x4d, 0xc3, 0xe0, 0xe8, 0x22, 0x40, 0xfd, 0x60, 0x0f, 0x56, + 0xb9, 0x27, 0x1a, 0xc4, 0xc2, 0x3a, 0x69, 0x28, 0x1a, 0xa7, 0x17, 0xa4, 0xd5, 0x0f, 0xa5, 0x21, 0xf2, 0x19, 0xc2, + 0x89, 0xa5, 0xa9, 0x2d, 0x9c, 0xa2, 0x06, 0x97, 0x0d, 0xf7, 0x9d, 0xa2, 0xc6, 0x54, 0x35, 0xc6, 0x28, 0x4e, 0xe0, + 0x6f, 0x98, 0x12, 0x42, 0x9a, 0x9d, 0xb2, 0xa2, 0x3b, 0x2c, 0x29, 0x8a, 0xc7, 0x4e, 0x3d, 0x3a, 0xd0, 0x9b, 0x43, + 0x34, 0x42, 0x3e, 0x60, 0xc3, 0xd5, 0x2a, 0x38, 0xef, 0x5f, 0x04, 0xa8, 0x11, 0x3a, 0xb4, 0x3b, 0x72, 0x78, 0x87, + 0x10, 0x96, 0xc3, 0xb5, 0xbd, 0x81, 0xba, 0x65, 0xb5, 0xdb, 0xa6, 0x65, 0xb5, 0xff, 0x3d, 0xb2, 0xc0, 0xd6, 0xa5, + 0xdc, 0x63, 0xf8, 0xdb, 0x39, 0x4c, 0xd5, 0xe1, 0xb6, 0x20, 0x2d, 0x5c, 0x10, 0xa7, 0xee, 0xa6, 0x44, 0x55, 0xf8, + 0x9f, 0x90, 0xaa, 0x38, 0x1e, 0x64, 0x78, 0x3a, 0x24, 0x92, 0x6a, 0xf9, 0xa5, 0xe7, 0x94, 0xe9, 0x2c, 0x23, 0xb7, + 0x6c, 0xe3, 0xfe, 0x37, 0x83, 0x3b, 0x99, 0x2b, 0x15, 0x25, 0x8b, 0xa2, 0x60, 0xb9, 0x7a, 0x25, 0x52, 0xcb, 0xd8, + 0xb1, 0x0c, 0x64, 0x2b, 0xb8, 0xd8, 0xc5, 0xc0, 0xd5, 0x75, 0xdc, 0x4e, 0x49, 0xb7, 0xb2, 0x17, 0x24, 0x35, 0x0c, + 0x97, 0xbe, 0xee, 0xed, 0x2d, 0xac, 0x28, 0x1d, 0x22, 0x9c, 0xda, 0x7b, 0xe0, 0x30, 0x8a, 0xa2, 0x45, 0x94, 0x40, + 0x36, 0x74, 0x20, 0xd1, 0x5a, 0xef, 0xab, 0x30, 0x27, 0x57, 0x2a, 0xca, 0xd9, 0x9d, 0xee, 0x36, 0x44, 0xd5, 0x21, + 0xee, 0xf6, 0xdb, 0x39, 0xed, 0x69, 0x02, 0x94, 0x47, 0xb9, 0x48, 0x19, 0x40, 0x08, 0xee, 0xfe, 0x6d, 0xd2, 0x94, + 0x4a, 0xff, 0x66, 0xab, 0x1a, 0xe0, 0xc0, 0x57, 0x79, 0x2f, 0x40, 0x4f, 0xac, 0x65, 0xe8, 0xb2, 0xb0, 0x51, 0x9e, + 0x23, 0xc4, 0xc7, 0xe1, 0x22, 0x82, 0x1b, 0x41, 0x8d, 0x49, 0x5c, 0xa2, 0xd5, 0x6a, 0xe1, 0xe3, 0xd6, 0xb4, 0x52, + 0x4c, 0x8f, 0xc9, 0x74, 0x50, 0x34, 0x1a, 0x5a, 0x79, 0x9d, 0x1a, 0xbc, 0x58, 0x20, 0x3c, 0x2e, 0xf7, 0x9a, 0x2b, + 0x37, 0x27, 0xf5, 0xae, 0xc2, 0x71, 0x5d, 0x09, 0xdc, 0xe0, 0x12, 0x69, 0xfd, 0xa2, 0x82, 0xd6, 0xf1, 0x84, 0x1c, + 0x85, 0x83, 0xa8, 0xff, 0x3f, 0x87, 0xa8, 0x1f, 0x46, 0x87, 0xe8, 0xc8, 0xd0, 0x92, 0x31, 0xea, 0x25, 0xa6, 0x8f, + 0xa5, 0xbe, 0xfd, 0x6c, 0x63, 0xad, 0x80, 0x8c, 0x05, 0xce, 0xe9, 0x8c, 0xc5, 0x13, 0xd8, 0xf5, 0x0e, 0x79, 0xe6, + 0x18, 0x90, 0x29, 0x9e, 0x58, 0xda, 0x12, 0x05, 0x7d, 0x41, 0xcb, 0xaf, 0x7e, 0xd0, 0xa7, 0xd5, 0xd7, 0xff, 0x0c, + 0xfa, 0x09, 0x8d, 0xaf, 0xf8, 0xda, 0x2a, 0xc9, 0x6b, 0x7d, 0x9c, 0xba, 0x3e, 0xd6, 0x66, 0x71, 0x3c, 0xe0, 0xa5, + 0x28, 0xdf, 0xd2, 0x8e, 0x2c, 0xd0, 0x9a, 0x8f, 0x4b, 0xea, 0x94, 0x47, 0x8a, 0x4e, 0x00, 0xaa, 0xde, 0x22, 0xe4, + 0xbe, 0x6d, 0x80, 0x37, 0x65, 0xc0, 0x16, 0x87, 0xb4, 0x00, 0xcd, 0xc5, 0x45, 0x0b, 0x2d, 0x6b, 0x85, 0x2d, 0x67, + 0x55, 0xbf, 0x8b, 0x2f, 0x89, 0xf7, 0x18, 0xa8, 0xf2, 0xf9, 0xa2, 0x37, 0x6e, 0x34, 0x50, 0xee, 0xf0, 0x2b, 0x1d, + 0x8c, 0x87, 0xf8, 0x0e, 0x50, 0x08, 0xd7, 0x30, 0x0a, 0xd7, 0xe6, 0xd8, 0xb1, 0x73, 0x6c, 0x34, 0xc4, 0x1a, 0xf5, + 0xbc, 0xca, 0x0b, 0x5b, 0x79, 0xbd, 0x36, 0x90, 0xd9, 0xc4, 0xb8, 0x33, 0xa4, 0x53, 0xc0, 0x10, 0x8c, 0x10, 0xf2, + 0x8f, 0x40, 0x3b, 0x9b, 0x85, 0x46, 0xa1, 0xba, 0xde, 0xbd, 0x40, 0x51, 0xcd, 0xe9, 0x11, 0x02, 0x2c, 0xa0, 0x6a, + 0xa9, 0x46, 0x9e, 0x2a, 0x9c, 0x36, 0xda, 0x1a, 0xdd, 0x9b, 0xed, 0x5e, 0xbd, 0xb1, 0x87, 0x55, 0x63, 0x38, 0x6d, + 0x90, 0x69, 0xb5, 0xc3, 0xd7, 0xa2, 0xd1, 0x58, 0xd7, 0xef, 0x4b, 0xdd, 0x26, 0xae, 0xdd, 0x5f, 0x3c, 0xdd, 0x32, + 0xf1, 0x70, 0xa7, 0x6f, 0x75, 0xde, 0xca, 0x88, 0xe7, 0x39, 0x2b, 0xe0, 0x84, 0x25, 0x0a, 0xcb, 0xf5, 0xba, 0x3c, + 0xf5, 0x7f, 0x57, 0xc6, 0x66, 0x8c, 0x70, 0xa0, 0x43, 0x5a, 0x6a, 0xc3, 0x02, 0x17, 0x98, 0x6a, 0x2a, 0x42, 0x08, + 0xf9, 0xa0, 0x9c, 0x79, 0x8c, 0xd2, 0x24, 0x29, 0x21, 0xde, 0xd9, 0x1d, 0xe6, 0x84, 0x45, 0x37, 0x0f, 0xae, 0xc4, + 0x77, 0x45, 0xba, 0x81, 0x1c, 0xc6, 0xba, 0x58, 0x66, 0x09, 0x59, 0x46, 0xbe, 0x82, 0x9c, 0x53, 0x5e, 0xb0, 0x44, + 0x9a, 0x20, 0x3e, 0xe1, 0x05, 0xd3, 0x8c, 0xfb, 0x03, 0x27, 0x37, 0x26, 0x75, 0x4e, 0x33, 0xf1, 0xb5, 0x3f, 0x00, + 0xcd, 0x0c, 0x94, 0x43, 0x82, 0x6c, 0x15, 0xbb, 0x79, 0x70, 0xf9, 0x7a, 0x97, 0x0c, 0xbd, 0x5a, 0x59, 0xe9, 0x39, + 0x01, 0xd6, 0x07, 0x67, 0xd5, 0x50, 0x13, 0xfb, 0x23, 0x0e, 0x13, 0xcd, 0x44, 0x65, 0x21, 0x07, 0x64, 0xba, 0x79, + 0x70, 0xf9, 0x2e, 0xe4, 0x5a, 0x37, 0x85, 0xb0, 0x3f, 0xef, 0xb0, 0x20, 0x21, 0x25, 0x0c, 0x99, 0xc9, 0x97, 0x74, + 0xac, 0xf0, 0x4e, 0xf7, 0x98, 0xea, 0x4c, 0x10, 0x3b, 0x06, 0x72, 0x48, 0x12, 0x0b, 0x02, 0x92, 0x20, 0x9c, 0xd4, + 0xe4, 0x3a, 0xa2, 0xd7, 0x40, 0x77, 0x76, 0x0d, 0x8b, 0x11, 0x19, 0xf6, 0x10, 0xe1, 0x44, 0x77, 0xab, 0xd6, 0xe6, + 0x38, 0xc9, 0xe9, 0xa6, 0xa1, 0x5b, 0x25, 0xcf, 0xbe, 0x07, 0xc1, 0xcb, 0x7d, 0xbc, 0xb2, 0x6d, 0x97, 0x09, 0x4f, + 0x9c, 0x45, 0xda, 0xcd, 0x83, 0xcb, 0x5f, 0xad, 0x51, 0xda, 0x9c, 0x3a, 0xf2, 0xbf, 0x25, 0xa3, 0x5e, 0xfe, 0x1a, + 0x55, 0xb9, 0xba, 0xf0, 0xcd, 0x83, 0xcb, 0xdf, 0x77, 0x15, 0x83, 0xf4, 0xf5, 0xa2, 0x52, 0x12, 0xe8, 0xf1, 0x2d, + 0x59, 0x16, 0x2f, 0xed, 0x59, 0x11, 0xcb, 0x35, 0xd6, 0x27, 0x54, 0x9c, 0xaf, 0x4b, 0xdd, 0xca, 0x13, 0x2c, 0x88, + 0xbe, 0x4a, 0xaa, 0x2f, 0x9b, 0x45, 0x63, 0x2e, 0xf2, 0xeb, 0x44, 0xcc, 0xd9, 0x37, 0xee, 0x97, 0x9e, 0x2a, 0x14, + 0xf1, 0x19, 0x18, 0xe2, 0xe8, 0xb1, 0x4b, 0xbc, 0xdf, 0x42, 0xbd, 0x8d, 0xf3, 0x4c, 0x68, 0x44, 0x2d, 0xea, 0x87, + 0x0d, 0xa6, 0xa4, 0x85, 0x13, 0xd2, 0xc2, 0x19, 0xc9, 0x07, 0x2d, 0x73, 0x62, 0xf4, 0xb2, 0xb2, 0x69, 0x73, 0xee, + 0xc0, 0x76, 0xcf, 0xcc, 0xbe, 0x35, 0x87, 0xf2, 0xb4, 0x97, 0x69, 0xfd, 0xa5, 0x3e, 0xe8, 0xa7, 0x1a, 0x35, 0x9e, + 0xb0, 0xb0, 0xc0, 0x85, 0x6e, 0xf9, 0x9a, 0x8f, 0x32, 0xb0, 0x53, 0x81, 0x99, 0x61, 0x85, 0xe2, 0xb2, 0x6c, 0xdb, + 0x96, 0xcd, 0x22, 0xbd, 0x56, 0x05, 0xce, 0x22, 0x20, 0xe5, 0x38, 0xb3, 0x76, 0x3d, 0x72, 0xbb, 0xca, 0xe9, 0xc1, + 0x41, 0x68, 0x2b, 0xd1, 0xb0, 0x70, 0xf9, 0xd5, 0x0d, 0xe0, 0x7b, 0x43, 0x35, 0xa6, 0x48, 0x4f, 0xa0, 0xd1, 0x48, + 0x86, 0x6b, 0xba, 0x4f, 0x48, 0x98, 0xd5, 0xa1, 0xe8, 0x46, 0xaf, 0x99, 0xc1, 0x0d, 0x00, 0x34, 0x1a, 0xe5, 0x75, + 0xef, 0x06, 0xc4, 0x9e, 0x2a, 0x2c, 0xd6, 0x5f, 0xc3, 0xd2, 0x9a, 0xa8, 0xb5, 0x65, 0x87, 0xe5, 0x46, 0x81, 0xa4, + 0x8f, 0xbb, 0xd2, 0xcd, 0xc7, 0xdb, 0x1a, 0xba, 0xdc, 0x0b, 0x6b, 0x03, 0x81, 0xb5, 0xd5, 0x96, 0x2d, 0xe4, 0x48, + 0x5b, 0x07, 0xc5, 0xae, 0x10, 0x5c, 0x70, 0x41, 0xa1, 0xc6, 0xda, 0x62, 0xf9, 0x13, 0xb6, 0x6d, 0xce, 0x89, 0x73, + 0x64, 0xb5, 0x65, 0x7a, 0x18, 0x1a, 0x60, 0x9d, 0x12, 0x30, 0xcf, 0xc9, 0xcb, 0x6f, 0xa3, 0xfe, 0xa5, 0x87, 0xfa, + 0x8f, 0x09, 0xf3, 0xb6, 0x81, 0x59, 0x82, 0x48, 0x58, 0x05, 0x45, 0xee, 0xb2, 0xae, 0xe6, 0x04, 0xb4, 0x71, 0x75, + 0xa8, 0xe6, 0xfe, 0x15, 0xe5, 0x37, 0x28, 0x8b, 0xbf, 0x53, 0xb4, 0x3e, 0x13, 0xbb, 0xfb, 0xe4, 0xb0, 0xba, 0xa0, + 0x83, 0xae, 0x77, 0x29, 0x07, 0x7d, 0x52, 0x78, 0xf9, 0xfb, 0xf7, 0xef, 0x56, 0xaf, 0xe6, 0xdb, 0x3b, 0xd8, 0x33, + 0x2b, 0x85, 0x59, 0x7b, 0x1b, 0xb8, 0x6e, 0x64, 0x0a, 0xfd, 0x97, 0x77, 0xe2, 0x75, 0x2a, 0xb4, 0xb1, 0x19, 0xdd, + 0x71, 0x08, 0xa3, 0x6d, 0xb7, 0x75, 0x09, 0xe6, 0x35, 0x0b, 0x74, 0xc9, 0x18, 0xb7, 0xd2, 0xe2, 0x1b, 0x64, 0xe4, + 0x52, 0x17, 0x60, 0x79, 0xba, 0x3b, 0xfb, 0xf1, 0xda, 0xe2, 0x89, 0x19, 0x1a, 0x5a, 0x6a, 0x42, 0x68, 0xf0, 0x1e, + 0x30, 0xc7, 0x1c, 0x11, 0x00, 0xa2, 0x97, 0x1a, 0x52, 0x15, 0xc8, 0x82, 0xa0, 0x52, 0xe4, 0x3f, 0xdf, 0x27, 0xe4, + 0x65, 0xa5, 0xc8, 0x7c, 0x5b, 0x19, 0x73, 0x01, 0x62, 0xa0, 0x18, 0x2e, 0x12, 0xca, 0x04, 0x73, 0x19, 0xfa, 0x41, + 0xb9, 0xf2, 0x5a, 0xda, 0x8c, 0x2a, 0x6e, 0xdc, 0xbb, 0x29, 0xd5, 0x2a, 0x3e, 0x93, 0xef, 0x20, 0xb1, 0x91, 0xfb, + 0x00, 0x72, 0x19, 0xd5, 0x83, 0x84, 0xef, 0x77, 0xba, 0xb4, 0x6b, 0x77, 0xfd, 0x65, 0xd3, 0x22, 0x66, 0x63, 0x5d, + 0x22, 0x9e, 0x4b, 0x56, 0xa8, 0xc7, 0x6c, 0x2c, 0x0a, 0xb8, 0xff, 0x28, 0xc1, 0x82, 0xd6, 0x0f, 0x3c, 0x1d, 0xa0, + 0x9e, 0xa0, 0x77, 0xe9, 0xb0, 0x31, 0x43, 0xfd, 0xeb, 0x8b, 0xbe, 0x03, 0xbf, 0xd9, 0xac, 0xf5, 0xf2, 0xe0, 0xe0, + 0x2b, 0xab, 0x00, 0x65, 0x87, 0xa9, 0x87, 0xe1, 0x11, 0x2f, 0xc3, 0xe5, 0xd8, 0x9b, 0xe1, 0x07, 0x61, 0xa5, 0x32, + 0x70, 0x84, 0xc3, 0x27, 0x42, 0xcf, 0x89, 0x5a, 0x4f, 0x36, 0xe9, 0xbd, 0xd5, 0x66, 0x48, 0x5f, 0xac, 0x01, 0x72, + 0x0f, 0x72, 0xb9, 0x51, 0x32, 0xe5, 0x95, 0xad, 0x6d, 0x39, 0x88, 0x2b, 0x00, 0x57, 0x98, 0x83, 0x90, 0xe2, 0xa1, + 0x61, 0xbe, 0x53, 0x68, 0x79, 0x2e, 0x80, 0xfd, 0xc7, 0x79, 0x04, 0x22, 0x2d, 0xaa, 0x6d, 0x5c, 0x84, 0x70, 0xae, + 0x25, 0x1e, 0xcf, 0x38, 0xe1, 0xf2, 0xf9, 0x2e, 0x0d, 0xb5, 0x43, 0x6d, 0xa6, 0xcf, 0x20, 0x28, 0x21, 0x50, 0x59, + 0x21, 0xfa, 0x1a, 0x4a, 0xcb, 0xcd, 0x95, 0xf7, 0x70, 0xec, 0x76, 0x2f, 0xa7, 0xa1, 0xb9, 0xdb, 0x82, 0xe3, 0xa3, + 0x88, 0x16, 0x61, 0xad, 0xeb, 0x5e, 0xa1, 0xab, 0x61, 0x0b, 0x3a, 0xea, 0xc3, 0xa9, 0xd0, 0xf7, 0x84, 0x57, 0x15, + 0x49, 0xfd, 0x64, 0x2d, 0xa0, 0x1c, 0x31, 0xac, 0x4c, 0x53, 0xbc, 0xf9, 0x7f, 0xb2, 0xe6, 0x6b, 0xe5, 0x31, 0xc1, + 0xf4, 0x30, 0x6e, 0xcd, 0x2a, 0xb0, 0x35, 0xe0, 0xd8, 0xf2, 0x2f, 0xe1, 0x2d, 0xaa, 0x53, 0x8a, 0xeb, 0x4e, 0x3d, + 0x26, 0xe0, 0x2d, 0x58, 0xcf, 0x6c, 0x6e, 0xfd, 0xe7, 0xfa, 0x60, 0x94, 0x38, 0xaf, 0x11, 0x78, 0xa1, 0x09, 0x3c, + 0x02, 0xc6, 0xcd, 0x99, 0x96, 0xf7, 0xad, 0x11, 0x8d, 0x74, 0x27, 0x9e, 0xc5, 0x33, 0xc3, 0x72, 0x6f, 0x7d, 0x6c, + 0xac, 0x48, 0x2c, 0x09, 0xd8, 0x16, 0x61, 0x4b, 0xe4, 0x05, 0xc2, 0x79, 0xa3, 0xd1, 0xcb, 0xcf, 0x59, 0xa5, 0x55, + 0xa9, 0x86, 0x29, 0xe1, 0x96, 0x18, 0xf0, 0xbe, 0x76, 0xa2, 0xe6, 0x08, 0x97, 0x66, 0xee, 0x39, 0xa8, 0xef, 0x2f, + 0xdf, 0x86, 0x3e, 0x7d, 0xf3, 0xcb, 0x96, 0x17, 0xb1, 0x30, 0xa5, 0xb0, 0xba, 0xc3, 0x79, 0xf3, 0x7d, 0xb3, 0x11, + 0x18, 0xf7, 0x7e, 0x1b, 0x83, 0x8d, 0x1b, 0xea, 0x29, 0x43, 0x1a, 0xca, 0x4d, 0xd8, 0x43, 0x95, 0xbd, 0xa3, 0xdf, + 0x59, 0x4f, 0x55, 0xd2, 0xae, 0x22, 0xf9, 0x7a, 0x2d, 0x59, 0x65, 0x34, 0xb0, 0x61, 0xd8, 0xa9, 0x8f, 0x99, 0x6d, + 0x05, 0xfe, 0xd5, 0x9c, 0x28, 0xec, 0x21, 0xeb, 0x9b, 0x6f, 0x5d, 0xa7, 0x54, 0xc3, 0x84, 0xed, 0x6d, 0xcf, 0xc7, + 0x2b, 0xbe, 0xeb, 0x7c, 0xc4, 0xc2, 0x6e, 0x7d, 0x7d, 0x36, 0xb6, 0xff, 0x8d, 0xb3, 0xd1, 0xaa, 0xed, 0xdd, 0xf1, + 0x08, 0xdc, 0x49, 0xed, 0x78, 0xcc, 0xeb, 0xc7, 0xa3, 0xc0, 0xee, 0xf4, 0xbe, 0xe8, 0xac, 0x56, 0x72, 0xd0, 0x02, + 0xb5, 0x53, 0x10, 0xc0, 0xcf, 0xb6, 0xf9, 0xe9, 0x91, 0x64, 0xa3, 0x43, 0x0e, 0xcb, 0xf3, 0xbe, 0x8d, 0x22, 0x30, + 0xa0, 0x0e, 0xb5, 0xad, 0x97, 0x46, 0x6c, 0x8b, 0x43, 0x16, 0xcb, 0x89, 0x2c, 0xaf, 0xae, 0x60, 0xc4, 0xfa, 0xd8, + 0xb0, 0x02, 0x66, 0xb8, 0xd3, 0xaa, 0xd0, 0x89, 0x9f, 0xff, 0x9a, 0x39, 0xad, 0x1d, 0x31, 0x86, 0x93, 0xa8, 0x59, + 0x31, 0xd8, 0x11, 0x58, 0x86, 0x71, 0x5f, 0x4b, 0xa8, 0xd5, 0xa9, 0x8e, 0x6a, 0x47, 0x12, 0x6e, 0x81, 0xda, 0x6d, + 0x5f, 0x9f, 0x4b, 0xab, 0xd5, 0xce, 0x83, 0x05, 0x17, 0x1e, 0x6e, 0x3f, 0x27, 0xaa, 0x46, 0x52, 0x28, 0xb1, 0x12, + 0x14, 0xce, 0x34, 0xaa, 0x2a, 0x22, 0x06, 0xad, 0x21, 0xf0, 0xa4, 0xbd, 0xe4, 0x5c, 0x54, 0x42, 0x72, 0xd2, 0x68, + 0xa0, 0xac, 0xec, 0x98, 0x0e, 0x64, 0x23, 0x19, 0x62, 0x86, 0x13, 0x23, 0xb0, 0xc0, 0xe9, 0x15, 0x66, 0x55, 0xd7, + 0x83, 0x64, 0x88, 0x70, 0xb1, 0x5a, 0x85, 0x66, 0x68, 0x19, 0x5a, 0xad, 0x32, 0x7f, 0x68, 0x3a, 0x1f, 0x2a, 0xbe, + 0xec, 0x2b, 0xf2, 0x52, 0x9f, 0x87, 0x2f, 0x61, 0x90, 0x0d, 0x12, 0x66, 0x56, 0x25, 0x98, 0x81, 0xe6, 0xaa, 0x21, + 0x06, 0x49, 0xa3, 0x3d, 0xf4, 0x68, 0xd8, 0x20, 0x19, 0x92, 0x6c, 0x0d, 0x96, 0xb3, 0xb9, 0x3d, 0x30, 0xff, 0x82, + 0x83, 0xed, 0x2f, 0x7d, 0xce, 0x98, 0x06, 0xfd, 0x35, 0xd9, 0x54, 0x19, 0x94, 0x78, 0x65, 0x17, 0xd7, 0x95, 0xab, + 0x19, 0x58, 0x94, 0x85, 0xb0, 0xbd, 0x66, 0xee, 0x83, 0xf0, 0x5f, 0x62, 0xbb, 0xa0, 0xa5, 0x11, 0xf7, 0x06, 0xe2, + 0x3b, 0xdb, 0xed, 0x28, 0x8a, 0x68, 0x31, 0xd1, 0x57, 0x22, 0x8e, 0x12, 0xeb, 0x3d, 0x70, 0x6c, 0xc7, 0xe9, 0xf5, + 0x3c, 0x28, 0x3b, 0x1b, 0x12, 0x33, 0x7e, 0xc7, 0xec, 0x38, 0xc7, 0x95, 0x82, 0xee, 0xd6, 0x45, 0x98, 0xc1, 0xd0, + 0xff, 0xe5, 0xc1, 0x9c, 0xd8, 0xc1, 0x18, 0x34, 0xd9, 0x80, 0xdb, 0x37, 0xe0, 0x51, 0xd0, 0x0d, 0xb8, 0x7d, 0x1b, + 0xbe, 0x0e, 0x5a, 0xc9, 0x37, 0x07, 0xe8, 0x91, 0x09, 0x33, 0xd2, 0x2a, 0xc1, 0x1b, 0x66, 0x77, 0x93, 0x23, 0x33, + 0x64, 0x15, 0x0e, 0x57, 0x45, 0x42, 0xb9, 0xb1, 0x17, 0x2a, 0x26, 0xd5, 0xe3, 0xfe, 0x65, 0xfc, 0x12, 0xf9, 0x4a, + 0x83, 0xba, 0x71, 0x0c, 0x60, 0x95, 0xd5, 0xd6, 0xbf, 0x3c, 0x38, 0x00, 0xf3, 0x68, 0x60, 0xed, 0xa2, 0x84, 0xce, + 0xd5, 0xa2, 0x00, 0xfe, 0x2a, 0x77, 0xbf, 0x21, 0x19, 0xdc, 0x4e, 0x74, 0x1a, 0xfc, 0x80, 0x84, 0x39, 0x95, 0x92, + 0x7f, 0x35, 0x69, 0xf6, 0x37, 0x2e, 0x88, 0xc3, 0xe8, 0xdc, 0x70, 0x8a, 0x02, 0xf5, 0x84, 0x45, 0xd7, 0x3a, 0xe4, + 0x9e, 0x7e, 0x65, 0xb9, 0x7a, 0xc9, 0xa5, 0x62, 0x39, 0x00, 0xa0, 0x42, 0x3c, 0x98, 0x52, 0x8e, 0x60, 0xeb, 0xd6, + 0x6a, 0xd1, 0x34, 0xfd, 0x6e, 0x15, 0x55, 0x67, 0x8b, 0xa6, 0x34, 0x4f, 0x33, 0xd3, 0x89, 0x6f, 0x33, 0xe9, 0xec, + 0x44, 0xcb, 0x92, 0xbe, 0xc5, 0x4e, 0xc5, 0x7e, 0x68, 0x5a, 0x1f, 0x4a, 0xe2, 0xce, 0x05, 0x77, 0x96, 0x7e, 0x97, + 0x8f, 0x81, 0x2b, 0xf5, 0x6f, 0xac, 0x82, 0x3f, 0x13, 0xac, 0x3c, 0xf2, 0x1a, 0xd5, 0xc7, 0xe9, 0x50, 0x27, 0xdb, + 0x52, 0x2e, 0x94, 0x46, 0x61, 0x1b, 0x27, 0x85, 0xc6, 0x94, 0xd3, 0x6f, 0x4b, 0x5c, 0xbf, 0xba, 0x63, 0xc4, 0x1d, + 0x1d, 0xf2, 0xdf, 0xa5, 0xd2, 0x68, 0x59, 0x22, 0x18, 0x72, 0x3b, 0xf2, 0x67, 0x09, 0x57, 0xb1, 0x19, 0x57, 0xcf, + 0xd5, 0x2c, 0xdb, 0xf0, 0xc4, 0xe9, 0xfd, 0x5c, 0x5e, 0x23, 0xff, 0x2c, 0xc3, 0x5b, 0x86, 0x9f, 0x30, 0xb8, 0x37, + 0x7e, 0xc6, 0xbd, 0x2a, 0xdb, 0xf7, 0xc5, 0xcf, 0xbc, 0xfb, 0xe2, 0x67, 0x3c, 0xde, 0x2e, 0xea, 0xdd, 0x13, 0x77, + 0xa2, 0xb3, 0xa8, 0x15, 0x38, 0x3e, 0x6a, 0x4a, 0x2b, 0xff, 0x4a, 0xb3, 0x35, 0x70, 0x65, 0x13, 0x07, 0xc6, 0x79, + 0x75, 0x11, 0x8a, 0x59, 0x73, 0x46, 0xc3, 0xe1, 0x7f, 0x6b, 0x9d, 0xec, 0xc9, 0x23, 0x8c, 0x14, 0xf2, 0x86, 0x16, + 0xea, 0x01, 0x6c, 0xb8, 0x62, 0xcb, 0x07, 0x90, 0x12, 0x50, 0xb6, 0xfd, 0x7b, 0x5d, 0x54, 0x8e, 0x07, 0xfd, 0xdc, + 0x38, 0x1f, 0xf9, 0xed, 0x93, 0xa2, 0xe4, 0xea, 0xea, 0x42, 0xc8, 0x9d, 0xd6, 0x12, 0x20, 0x4c, 0x9d, 0x6b, 0x1e, + 0xb3, 0x34, 0x99, 0xc5, 0xcb, 0x75, 0xa9, 0x3a, 0x28, 0x0c, 0x57, 0xc7, 0x11, 0x2e, 0xd6, 0xfa, 0x06, 0xfd, 0x1f, + 0x8e, 0xff, 0xe2, 0x96, 0x46, 0x7e, 0x2a, 0x29, 0xd0, 0xe3, 0xdd, 0xbe, 0x36, 0x3b, 0x48, 0xa4, 0x99, 0x43, 0x69, + 0x29, 0x00, 0x58, 0xad, 0xf1, 0x75, 0xed, 0x70, 0xea, 0x89, 0xb0, 0xb3, 0xf9, 0xa6, 0x21, 0x2c, 0x66, 0xa5, 0x05, + 0x8f, 0xee, 0x66, 0x87, 0xe5, 0xa8, 0x93, 0xc5, 0x55, 0xb9, 0xc7, 0x6a, 0xfd, 0xa2, 0x6f, 0x80, 0xb2, 0x32, 0x44, + 0x5b, 0xad, 0xc2, 0x3a, 0xbc, 0x89, 0xf4, 0xae, 0x41, 0x10, 0x96, 0x9e, 0x01, 0x47, 0x8d, 0xf1, 0x36, 0x75, 0x42, + 0xb4, 0x69, 0xbf, 0xe4, 0x58, 0xf7, 0xda, 0x38, 0x7c, 0x45, 0x83, 0xa9, 0xee, 0x6b, 0x1e, 0xb0, 0x99, 0x5d, 0xd9, + 0x91, 0x07, 0xa1, 0x29, 0x75, 0xc6, 0xb9, 0x95, 0x15, 0xed, 0x0e, 0xf8, 0xa2, 0xef, 0x98, 0xe7, 0x5a, 0xd0, 0x6d, + 0xe7, 0x7b, 0xb6, 0x4d, 0x4f, 0xc4, 0xb7, 0x6c, 0x9b, 0x6a, 0x9c, 0xf0, 0x7e, 0x0b, 0x7d, 0xdf, 0x10, 0xd6, 0xf4, + 0xb5, 0xbb, 0xc8, 0xff, 0x42, 0x77, 0x6d, 0x40, 0x4f, 0x03, 0x66, 0x47, 0x63, 0x3e, 0xa8, 0xf5, 0xfa, 0x53, 0xe9, + 0xbf, 0xa0, 0x6d, 0x85, 0x3e, 0x99, 0x5d, 0x60, 0xc5, 0x4a, 0xed, 0x10, 0x1c, 0xfe, 0xc3, 0xc9, 0x24, 0x13, 0x23, + 0x9a, 0xbd, 0x83, 0x1e, 0xab, 0xdc, 0xe7, 0xf7, 0x69, 0x41, 0x15, 0xb3, 0xb4, 0xa6, 0x1a, 0xc5, 0x3f, 0xdc, 0x1b, + 0xc6, 0x3f, 0xdc, 0x50, 0xee, 0xaa, 0x05, 0xbc, 0x7c, 0x59, 0x36, 0x11, 0x7f, 0x5a, 0x97, 0xfe, 0x56, 0xf9, 0xee, + 0x5e, 0x36, 0x49, 0x9a, 0xca, 0xcb, 0xd9, 0xe6, 0xe1, 0x66, 0x53, 0x61, 0xf8, 0xd7, 0x37, 0x06, 0xbb, 0x4d, 0xe6, + 0xfe, 0xf2, 0xc8, 0xdc, 0x5f, 0x3c, 0xfe, 0x6e, 0x2d, 0x8f, 0xe2, 0x1d, 0x47, 0xc7, 0xda, 0x32, 0xc6, 0x8c, 0xfa, + 0xad, 0x02, 0x83, 0x06, 0x45, 0x2e, 0x3c, 0x6f, 0x87, 0xea, 0xf4, 0x72, 0xf6, 0x47, 0x61, 0xb2, 0x90, 0x4a, 0xcc, + 0x6c, 0xa3, 0xd2, 0xfa, 0x38, 0xe9, 0x4c, 0x50, 0x60, 0xeb, 0x3b, 0xfc, 0xb8, 0xee, 0x46, 0xb6, 0xfc, 0xc2, 0xf3, + 0x34, 0xce, 0xb1, 0x3d, 0x5b, 0x64, 0x2c, 0xd6, 0xc4, 0x99, 0x39, 0x6f, 0xe7, 0xe1, 0x31, 0xcf, 0xb9, 0x9c, 0xb2, + 0x22, 0x2c, 0xd0, 0xf2, 0x5b, 0x9d, 0x15, 0x70, 0x9b, 0x63, 0x3a, 0xc3, 0x69, 0x69, 0x39, 0xa0, 0x22, 0x68, 0x0d, + 0x74, 0x46, 0x33, 0xa6, 0xa6, 0x22, 0x05, 0xc3, 0x97, 0x28, 0x2d, 0xdd, 0xa9, 0x0e, 0x0e, 0xf6, 0xc3, 0x40, 0xeb, + 0x2f, 0xc0, 0x07, 0xdd, 0xcf, 0x41, 0xfd, 0x25, 0x38, 0x06, 0x55, 0x5d, 0x33, 0xb4, 0x64, 0x9b, 0x3e, 0x34, 0x2a, + 0xfa, 0xc2, 0xee, 0x31, 0x47, 0xeb, 0x75, 0x6c, 0x46, 0x1d, 0x8c, 0x39, 0xcb, 0xd2, 0x00, 0x7f, 0x61, 0xf7, 0x71, + 0xe9, 0xb6, 0xae, 0xbd, 0xac, 0xf5, 0x22, 0x06, 0xe2, 0x36, 0x0f, 0x70, 0xd5, 0x49, 0xbc, 0x5c, 0x63, 0x51, 0xf0, + 0x09, 0xe0, 0xe8, 0x2f, 0xec, 0x3e, 0xb6, 0xed, 0x79, 0xae, 0x82, 0x68, 0xe9, 0x40, 0x1f, 0x79, 0xc9, 0xfe, 0x32, + 0x58, 0x81, 0x63, 0xa0, 0xeb, 0x0e, 0x49, 0xad, 0x5c, 0x25, 0x42, 0x22, 0xb4, 0xfe, 0x77, 0xa7, 0x82, 0x17, 0xfe, + 0x39, 0xa7, 0x6a, 0x16, 0xb7, 0x1b, 0x95, 0x18, 0x54, 0xa8, 0x2c, 0x48, 0x3e, 0x86, 0xdc, 0xed, 0x3e, 0xeb, 0xfd, + 0xe0, 0xe9, 0xcc, 0x16, 0xd4, 0x36, 0x1a, 0xa7, 0xfa, 0x17, 0xaa, 0xee, 0xa0, 0x66, 0xaa, 0xaa, 0xb8, 0xf7, 0x31, + 0x04, 0xc0, 0x83, 0xb5, 0x0c, 0xd5, 0x0e, 0xa1, 0x6b, 0x67, 0xa6, 0x3a, 0xa6, 0x24, 0x5c, 0xba, 0x39, 0xc4, 0xdc, + 0x07, 0xa3, 0x5a, 0x03, 0x1a, 0x5a, 0x04, 0x33, 0x96, 0x87, 0x7c, 0x1c, 0xca, 0xad, 0x33, 0x54, 0xe8, 0x33, 0x34, + 0xf2, 0x40, 0xfe, 0x8d, 0x33, 0x93, 0x69, 0x68, 0x68, 0xde, 0x52, 0x1f, 0x80, 0x76, 0x75, 0x2d, 0x0e, 0xf9, 0x2b, + 0x5a, 0x3a, 0xef, 0x99, 0x45, 0x17, 0xb5, 0x61, 0x85, 0xba, 0x1d, 0xb4, 0x8e, 0x61, 0x4a, 0xcc, 0x14, 0x58, 0x3b, + 0xbd, 0x0f, 0x77, 0x76, 0xb5, 0x61, 0x11, 0xb9, 0x69, 0x11, 0x07, 0x93, 0x90, 0xa2, 0x25, 0x0f, 0x29, 0x16, 0x60, + 0x07, 0x59, 0xac, 0xcb, 0xf1, 0x33, 0x7f, 0x39, 0x6a, 0x56, 0xd2, 0xbb, 0x1d, 0x0c, 0x81, 0xcb, 0x57, 0x60, 0x1b, + 0x8a, 0xb9, 0x23, 0x2c, 0x3c, 0xd4, 0x9e, 0x7e, 0xde, 0xba, 0xcd, 0xcd, 0x82, 0xb8, 0x15, 0x18, 0xd3, 0x70, 0xe9, + 0xcd, 0xc2, 0x77, 0x2a, 0xb7, 0x0e, 0x87, 0xf6, 0x9a, 0xb0, 0x32, 0x5e, 0x0d, 0x73, 0xb2, 0x71, 0xf4, 0x7c, 0xdf, + 0xc6, 0xf3, 0xef, 0x05, 0x2b, 0xee, 0xaf, 0x19, 0xd8, 0x58, 0x0b, 0x70, 0x37, 0xae, 0x96, 0xa1, 0x32, 0x90, 0xef, + 0x0b, 0xcd, 0xba, 0xac, 0xf1, 0x77, 0xa3, 0x62, 0xac, 0xf5, 0x3d, 0xa5, 0xa7, 0xad, 0x31, 0xdb, 0x85, 0x7d, 0xd3, + 0x75, 0x93, 0xf5, 0xb4, 0x22, 0xae, 0x82, 0xb4, 0xbd, 0x5b, 0xc0, 0x85, 0xef, 0x0f, 0x3b, 0xc8, 0x87, 0x9b, 0xaa, + 0x1b, 0x48, 0x82, 0x6b, 0x3f, 0xf1, 0xed, 0xa9, 0xee, 0xb2, 0xd6, 0xfd, 0xf6, 0x54, 0x6b, 0x97, 0x85, 0xda, 0x90, + 0x08, 0xdb, 0x7e, 0x4a, 0xff, 0x69, 0xb9, 0x5e, 0xa3, 0x35, 0x0c, 0xef, 0x3d, 0xef, 0x85, 0xe1, 0x7b, 0x67, 0xa1, + 0x18, 0xc1, 0x45, 0xee, 0x75, 0x26, 0x1c, 0x21, 0xaf, 0x46, 0xf0, 0x9e, 0x6f, 0x0d, 0xe1, 0x3d, 0xf7, 0x9c, 0x5e, + 0x41, 0x6a, 0x9a, 0xe4, 0x2c, 0x75, 0xf4, 0x13, 0x19, 0x24, 0xd4, 0x7c, 0xdc, 0x6b, 0x4e, 0xb8, 0xfa, 0x1c, 0x03, + 0xff, 0x85, 0x47, 0x0b, 0xa5, 0x44, 0x8e, 0x79, 0x3e, 0x5f, 0x28, 0x2c, 0xf5, 0xe8, 0x97, 0x63, 0x91, 0xab, 0xe6, + 0x98, 0xce, 0x78, 0x76, 0x1f, 0x2f, 0x78, 0x73, 0x26, 0x72, 0x21, 0xe7, 0x34, 0x61, 0x58, 0xde, 0x4b, 0xc5, 0x66, + 0xcd, 0x05, 0xc7, 0xcf, 0x59, 0xf6, 0x95, 0x29, 0x9e, 0x50, 0xfc, 0x56, 0x8c, 0x84, 0x12, 0xf8, 0xf5, 0xdd, 0xfd, + 0x84, 0xe5, 0xf8, 0xf7, 0xd1, 0x22, 0x57, 0x0b, 0x2c, 0x69, 0x2e, 0x9b, 0x92, 0x15, 0x7c, 0xdc, 0x6b, 0x36, 0xe7, + 0x05, 0x9f, 0xd1, 0xe2, 0xbe, 0x99, 0x88, 0x4c, 0x14, 0xf1, 0x7f, 0xb5, 0x8e, 0xe9, 0xa3, 0xf1, 0x49, 0x4f, 0x15, + 0x34, 0x97, 0x1c, 0x16, 0x26, 0xa6, 0x59, 0xb6, 0x77, 0xdc, 0x6d, 0xcd, 0xe4, 0xbe, 0xb9, 0xf0, 0xa3, 0xb9, 0x5a, + 0x7f, 0xc6, 0x1f, 0x04, 0x8c, 0x32, 0x1a, 0xa9, 0xdc, 0x0e, 0x72, 0x99, 0x2c, 0x0a, 0x29, 0x8a, 0x78, 0x2e, 0x78, + 0xae, 0x58, 0xd1, 0x1b, 0x89, 0x22, 0x65, 0x45, 0xb3, 0xa0, 0x29, 0x5f, 0xc8, 0xf8, 0x64, 0x7e, 0xd7, 0xab, 0xf7, + 0x60, 0xf2, 0xe3, 0x5c, 0xe4, 0xac, 0x07, 0xfc, 0xc6, 0xa4, 0x10, 0x8b, 0x3c, 0xb5, 0xc3, 0x58, 0xe4, 0x92, 0xa9, + 0xde, 0x9c, 0xa6, 0x60, 0x07, 0x1c, 0x9f, 0xcd, 0xef, 0x7a, 0x7a, 0xd6, 0xb7, 0x8c, 0x4f, 0xa6, 0x2a, 0xee, 0xb6, + 0x5a, 0xe6, 0x5b, 0xf2, 0x7f, 0x58, 0xdc, 0xee, 0x44, 0x9d, 0xee, 0xfc, 0x0e, 0x38, 0x78, 0xc5, 0x8a, 0x26, 0xc0, + 0x02, 0x2a, 0xb5, 0xa3, 0xd6, 0xa3, 0xe3, 0x87, 0x90, 0x01, 0x36, 0x0e, 0x4d, 0x3d, 0x21, 0x30, 0x76, 0x8f, 0x17, + 0xf3, 0x39, 0x2b, 0xc0, 0x8b, 0xbe, 0x37, 0xa3, 0xc5, 0x84, 0xe7, 0xcd, 0x42, 0x37, 0xda, 0x3c, 0x9b, 0xdf, 0xad, + 0x61, 0x3e, 0xb1, 0x31, 0x5b, 0xb5, 0xd3, 0xb2, 0x5f, 0x4b, 0x6f, 0x88, 0x3a, 0x26, 0x4d, 0x5c, 0x4c, 0x46, 0x34, + 0x6c, 0x77, 0x1e, 0x62, 0xf7, 0xbf, 0xa8, 0x83, 0x3c, 0xb0, 0x35, 0xd3, 0x45, 0xa1, 0x6f, 0x51, 0xe3, 0xb6, 0x34, + 0xcd, 0x4e, 0xc5, 0x57, 0x56, 0xb8, 0x56, 0xf5, 0xc7, 0x72, 0x6b, 0xde, 0xff, 0x49, 0xa3, 0x9f, 0xf1, 0x84, 0xc2, + 0x1a, 0x68, 0xe4, 0x18, 0x68, 0x79, 0x10, 0x66, 0x3a, 0x5c, 0xde, 0xf2, 0x54, 0x4d, 0xe3, 0x76, 0xab, 0xf5, 0x43, + 0xb5, 0x62, 0xbd, 0xa9, 0x01, 0x5d, 0xbb, 0x60, 0xb3, 0xda, 0x3a, 0xce, 0x68, 0x89, 0x6d, 0xcb, 0xb9, 0xb0, 0x4b, + 0x5e, 0xb0, 0x4c, 0x47, 0x93, 0x59, 0x5b, 0x94, 0xdb, 0x1a, 0x27, 0xcf, 0xa7, 0xac, 0xe0, 0xaa, 0x57, 0xff, 0xaa, + 0x3a, 0xde, 0x5e, 0xfd, 0xb5, 0x91, 0x43, 0x97, 0xa6, 0x86, 0xbd, 0xf4, 0xbc, 0x82, 0x8f, 0xed, 0xd5, 0xff, 0x4a, + 0x8b, 0x70, 0x03, 0x31, 0xb1, 0x5f, 0x03, 0xad, 0xbd, 0x39, 0x17, 0x60, 0x92, 0x39, 0xc4, 0xdf, 0x80, 0x42, 0x42, + 0xb3, 0x24, 0x84, 0x11, 0xed, 0x35, 0xf7, 0x8e, 0x0b, 0x36, 0x43, 0x0e, 0x10, 0xd1, 0xc3, 0x6e, 0xc1, 0x66, 0xeb, + 0x48, 0x57, 0x5f, 0x6a, 0x14, 0xa1, 0x19, 0x9f, 0xe4, 0x71, 0xc2, 0x00, 0x7d, 0xd7, 0x11, 0xcb, 0x15, 0x57, 0xf7, + 0xcd, 0x42, 0xdc, 0x2e, 0x53, 0x2e, 0xe7, 0x19, 0xbd, 0x8f, 0xc7, 0x19, 0xbb, 0xeb, 0xe9, 0x52, 0x4d, 0xae, 0xd8, + 0x4c, 0xda, 0xb2, 0x3d, 0x48, 0x6f, 0xa6, 0xc6, 0x6c, 0x02, 0xa0, 0x27, 0x6e, 0x37, 0xf7, 0x4f, 0x74, 0x2c, 0xf7, + 0x18, 0x95, 0xac, 0x29, 0x16, 0x6a, 0xaf, 0x25, 0x7b, 0x33, 0x9e, 0x37, 0xed, 0x40, 0x4e, 0x5a, 0xf3, 0xbb, 0xde, + 0x36, 0xe4, 0xbd, 0xfe, 0x23, 0x76, 0x37, 0xa7, 0x79, 0xca, 0xd2, 0xa5, 0x57, 0xad, 0x03, 0xf5, 0xfc, 0x52, 0x71, + 0xae, 0xa6, 0x4d, 0x6d, 0xeb, 0x15, 0x76, 0x72, 0xf4, 0x0d, 0xd4, 0x7a, 0xd4, 0xc2, 0xe6, 0xff, 0xa3, 0x36, 0xf2, + 0x2b, 0xef, 0x41, 0xd8, 0x25, 0x3e, 0xbe, 0x6f, 0xc2, 0xdf, 0x25, 0xf8, 0x16, 0xf1, 0x84, 0x66, 0x16, 0x22, 0x33, + 0x9e, 0xa6, 0x59, 0x6d, 0x44, 0x17, 0x5e, 0x67, 0x6d, 0xb4, 0x84, 0xf9, 0xc7, 0xad, 0xbd, 0xd6, 0x9e, 0x9e, 0x8b, + 0xdd, 0xe6, 0x27, 0x27, 0x0f, 0x8f, 0x1f, 0xb1, 0x5e, 0xc6, 0x73, 0x56, 0x9b, 0xea, 0x77, 0x41, 0xed, 0x37, 0xdc, + 0xb1, 0x0d, 0xb7, 0xf7, 0xda, 0x7b, 0x27, 0xad, 0x1f, 0xdc, 0x6e, 0xcd, 0xd8, 0x58, 0xc5, 0xed, 0xd3, 0xf9, 0x5d, + 0x7d, 0xfb, 0x9e, 0xb9, 0xa6, 0x6f, 0x0b, 0x3a, 0x8f, 0x73, 0x01, 0x7f, 0x7a, 0xb0, 0xc9, 0xc6, 0x99, 0xb8, 0x8d, + 0xa7, 0x3c, 0x4d, 0x59, 0x6e, 0x0a, 0x94, 0x89, 0x2c, 0xcb, 0xf8, 0x5c, 0x72, 0xb3, 0x1a, 0x16, 0x77, 0xbb, 0x1b, + 0x50, 0xf5, 0x07, 0x74, 0xec, 0x0d, 0xa8, 0x5b, 0x0d, 0xa8, 0xea, 0xdf, 0x1f, 0x61, 0x67, 0x63, 0xae, 0xba, 0x54, + 0xaf, 0x86, 0x49, 0x7f, 0x2d, 0xa4, 0x02, 0x98, 0x97, 0x46, 0x1a, 0x40, 0xc5, 0x9b, 0x23, 0xa6, 0x6e, 0x19, 0xcb, + 0xbf, 0x3d, 0x88, 0x8b, 0x58, 0xe4, 0xd9, 0xbd, 0xf9, 0x5c, 0xfa, 0x5d, 0xd2, 0x85, 0x12, 0xeb, 0x68, 0xc4, 0x73, + 0x5a, 0xdc, 0xdf, 0x48, 0x96, 0x4b, 0x51, 0xdc, 0x88, 0xf1, 0x78, 0xf9, 0x2d, 0xd2, 0xf2, 0x10, 0xad, 0x23, 0xc9, + 0xf3, 0x49, 0xc6, 0x0c, 0x51, 0xd2, 0x88, 0x60, 0x89, 0xb9, 0x69, 0x57, 0x37, 0x59, 0x1b, 0xb4, 0xbf, 0xf3, 0x74, + 0xbb, 0xc3, 0x38, 0x6e, 0xde, 0xb2, 0xd1, 0x17, 0xae, 0x0c, 0x9e, 0x35, 0xe5, 0x2d, 0x78, 0xbc, 0xe8, 0x65, 0x98, + 0xb3, 0x62, 0xe9, 0x68, 0x78, 0xcb, 0xa3, 0x3a, 0x51, 0x92, 0xf1, 0x19, 0x55, 0xcc, 0xa3, 0x54, 0x65, 0x27, 0x93, + 0x82, 0xa7, 0xdb, 0x38, 0xd2, 0x83, 0xe4, 0xa6, 0x33, 0xa8, 0x82, 0x9e, 0x16, 0xb3, 0x5c, 0xc6, 0x05, 0x9b, 0x33, + 0xaa, 0xc2, 0x63, 0xdc, 0x1e, 0x17, 0xa8, 0x37, 0xa1, 0xf3, 0x18, 0xf0, 0xc2, 0x75, 0xd9, 0x86, 0x25, 0xd8, 0xde, + 0xae, 0xeb, 0xcf, 0xf8, 0x8b, 0xd4, 0x87, 0x97, 0xa2, 0xa3, 0x26, 0x84, 0x1f, 0x63, 0x45, 0xb9, 0xc5, 0x79, 0xae, + 0x11, 0x56, 0xaf, 0xcf, 0xe6, 0x26, 0xf5, 0x8f, 0xa0, 0x93, 0x56, 0xcb, 0xf5, 0xd3, 0x34, 0x75, 0xe2, 0x76, 0xd4, + 0x65, 0xb3, 0x5d, 0xe4, 0xa1, 0x4e, 0x0b, 0xdb, 0x9d, 0xf9, 0xdd, 0x9e, 0xfe, 0xa7, 0xb5, 0xd7, 0xda, 0xa6, 0x7d, + 0xdb, 0xcb, 0x74, 0x8c, 0x1c, 0x62, 0x29, 0x31, 0x8f, 0xdb, 0x6c, 0xd6, 0x5b, 0x48, 0x38, 0xe7, 0x34, 0x69, 0xd6, + 0xe7, 0xe7, 0x5a, 0xcf, 0x04, 0xd0, 0x88, 0xf2, 0x1c, 0x8e, 0x15, 0x73, 0xb4, 0x42, 0x1f, 0x52, 0x80, 0x1d, 0xf8, + 0xce, 0x46, 0xeb, 0xc3, 0x6a, 0xed, 0x55, 0x03, 0x83, 0x7f, 0xd6, 0x9f, 0x2b, 0xc6, 0xf4, 0x05, 0xf3, 0x04, 0x03, + 0xde, 0x88, 0xba, 0xab, 0x96, 0x15, 0x06, 0x52, 0x55, 0xc9, 0x28, 0xda, 0x95, 0x62, 0x46, 0xef, 0x8c, 0x4f, 0xc5, + 0x8c, 0xe7, 0x60, 0xb1, 0x85, 0xb0, 0xf2, 0x6c, 0xdb, 0xa7, 0x7e, 0x43, 0xa9, 0x0a, 0xa1, 0xe1, 0xc3, 0x4e, 0xd4, + 0xed, 0x22, 0xdc, 0xc2, 0x9d, 0x6e, 0xd7, 0x13, 0x46, 0xc6, 0x6a, 0x57, 0xd1, 0x5d, 0x25, 0xf3, 0x1d, 0x25, 0x8f, + 0x74, 0xa3, 0x47, 0xed, 0x56, 0x0b, 0x6b, 0xbf, 0xf1, 0xb2, 0x30, 0xcb, 0x77, 0x34, 0xdb, 0x6e, 0xb5, 0xa0, 0x59, + 0xf8, 0x63, 0xe7, 0xf5, 0x0b, 0x59, 0xb6, 0xe2, 0x16, 0x6e, 0xc7, 0x6d, 0xdc, 0x89, 0x3b, 0xf8, 0x38, 0x3e, 0xc6, + 0x27, 0xf1, 0x09, 0xee, 0xc6, 0x5d, 0x7c, 0x1a, 0x9f, 0xe2, 0x87, 0xf1, 0x43, 0x7c, 0x16, 0x9f, 0xe1, 0x47, 0xf1, + 0x23, 0x7c, 0x19, 0xb7, 0x5b, 0xf8, 0x71, 0xdc, 0x6e, 0xe3, 0xab, 0xb8, 0xdd, 0xc1, 0x4f, 0xe2, 0xf6, 0x31, 0x7e, + 0x1a, 0xb7, 0x4f, 0xf0, 0xb3, 0xb8, 0xdd, 0xc5, 0x14, 0x72, 0x47, 0x90, 0x9b, 0x40, 0x6e, 0x0a, 0xb9, 0x0c, 0x72, + 0xc7, 0x71, 0xbb, 0xbb, 0xc6, 0xd2, 0xc4, 0x9a, 0x08, 0x5a, 0xed, 0xce, 0xf1, 0x49, 0xf7, 0xf4, 0xe1, 0xd9, 0xa3, + 0xcb, 0xc7, 0x57, 0x4f, 0x9e, 0x3e, 0x0b, 0x86, 0x78, 0xa4, 0x5d, 0x3e, 0xa4, 0x1c, 0xf0, 0x83, 0x76, 0x77, 0x88, + 0x6f, 0xdc, 0x67, 0xc8, 0x0f, 0x3a, 0x27, 0x2d, 0x74, 0x71, 0x71, 0x32, 0x6c, 0x94, 0xb9, 0xef, 0xb5, 0xa7, 0x49, + 0x95, 0x45, 0x08, 0x09, 0x21, 0x07, 0xe1, 0x7b, 0x5d, 0xef, 0x3d, 0x0b, 0x79, 0x54, 0xa0, 0x83, 0x03, 0xfd, 0x63, + 0xe2, 0x7e, 0x8c, 0xdc, 0x0f, 0xea, 0x2d, 0xd2, 0x1d, 0x0d, 0xad, 0xab, 0xb1, 0x2a, 0x0d, 0xfd, 0x4b, 0x1b, 0x9a, + 0x3d, 0x6e, 0xad, 0xd9, 0xff, 0x2b, 0x30, 0xd6, 0x2a, 0xe4, 0xc4, 0x68, 0x84, 0xba, 0x7d, 0x46, 0x96, 0x45, 0xdc, + 0xe9, 0x76, 0x0f, 0x7e, 0x19, 0xf0, 0x41, 0x7b, 0x38, 0x3c, 0x6c, 0x3f, 0xc4, 0x93, 0x32, 0xa1, 0x63, 0x12, 0x46, + 0x65, 0xc2, 0xb1, 0x49, 0xa0, 0xb1, 0xa9, 0x0d, 0x49, 0x27, 0x3a, 0x09, 0x4a, 0xac, 0x63, 0xdd, 0xf6, 0x43, 0xd3, + 0xf6, 0x23, 0x30, 0xa3, 0xd2, 0xcd, 0xdb, 0xa6, 0xcf, 0xcf, 0x4f, 0x56, 0xb6, 0x51, 0x3c, 0x89, 0x6d, 0x6b, 0x2e, + 0xf1, 0x64, 0x38, 0xc4, 0x23, 0x9d, 0xd8, 0xad, 0x12, 0x4f, 0x87, 0x43, 0xdb, 0xd5, 0x23, 0xdd, 0xd5, 0xc3, 0x2a, + 0xeb, 0x6c, 0x38, 0xd4, 0x5d, 0x22, 0xeb, 0x34, 0x2f, 0xd5, 0xde, 0xd7, 0x52, 0x71, 0xc1, 0xcf, 0x3b, 0xdd, 0x6e, + 0x1f, 0x30, 0x4c, 0x1b, 0xc3, 0x3a, 0x18, 0xdd, 0x7a, 0x30, 0xba, 0x87, 0xdf, 0xfd, 0x11, 0x8d, 0x6f, 0x68, 0x09, + 0xa4, 0x7e, 0xf0, 0x5f, 0x41, 0x43, 0x69, 0x98, 0xeb, 0x3f, 0x13, 0xf3, 0x67, 0x84, 0x1a, 0x5f, 0x29, 0x80, 0x1b, + 0x54, 0x31, 0x4e, 0x97, 0xaa, 0x7b, 0xfc, 0x42, 0xc1, 0xb7, 0x65, 0x2a, 0x33, 0xda, 0x5f, 0x4d, 0x6f, 0x47, 0xab, + 0xa9, 0xfc, 0x8a, 0xfe, 0x0c, 0xff, 0x94, 0x87, 0xe1, 0xa0, 0xd9, 0x88, 0xd8, 0x9f, 0x29, 0x38, 0xd1, 0xf4, 0xe3, + 0x94, 0x4d, 0x50, 0x7f, 0xf0, 0xa7, 0xc4, 0xc3, 0x86, 0x97, 0xf1, 0xc3, 0x76, 0x0a, 0xb8, 0xd8, 0x6c, 0x26, 0x86, + 0x3f, 0xa0, 0x3e, 0xea, 0xff, 0x29, 0x0f, 0xff, 0x44, 0x0f, 0x8e, 0xaa, 0xb9, 0xfc, 0x2e, 0xec, 0x16, 0xae, 0xe2, + 0xee, 0x1c, 0x96, 0x5b, 0x98, 0xe1, 0x76, 0x93, 0x41, 0x94, 0x32, 0xf0, 0xc1, 0x26, 0xa1, 0x68, 0xf0, 0xa3, 0xe3, + 0x16, 0xfa, 0xa1, 0xdd, 0x01, 0xad, 0x42, 0x53, 0x1e, 0x6e, 0x6f, 0xfa, 0xa2, 0x79, 0x8c, 0x1f, 0x35, 0x0b, 0xdc, + 0x46, 0xb8, 0xd9, 0x76, 0xea, 0xde, 0x41, 0x1e, 0xb6, 0x10, 0xce, 0xc3, 0x33, 0xf8, 0xe7, 0x04, 0x0d, 0xab, 0x0d, + 0x79, 0x4d, 0x37, 0x7b, 0x07, 0x87, 0x51, 0x12, 0xe6, 0x0d, 0x7e, 0x74, 0xda, 0x42, 0x3f, 0x9c, 0xea, 0x8e, 0xd8, + 0xa1, 0xda, 0xd1, 0x95, 0xc0, 0x27, 0x4d, 0x01, 0x1d, 0xb5, 0xca, 0x7e, 0x64, 0xd8, 0x45, 0x58, 0x86, 0xc7, 0xf0, + 0x4f, 0xdb, 0xef, 0xe7, 0xd7, 0xad, 0x7e, 0xf4, 0xbc, 0xdb, 0x38, 0xea, 0x1a, 0xff, 0xd3, 0xdc, 0x5c, 0x06, 0x37, + 0xd8, 0x45, 0x5b, 0xdf, 0x62, 0xb5, 0x8f, 0xe0, 0x03, 0x61, 0x75, 0x48, 0x72, 0xcc, 0x0e, 0xc1, 0x71, 0x15, 0xec, + 0x35, 0xf2, 0xf3, 0xe3, 0x5e, 0xde, 0x68, 0x20, 0x90, 0x0f, 0x0f, 0x49, 0xbb, 0xa9, 0x9a, 0x0c, 0xc3, 0xef, 0x06, + 0x29, 0xa3, 0xa1, 0xc9, 0xaa, 0xd7, 0x2b, 0xdb, 0xab, 0xb9, 0xf2, 0x76, 0xd8, 0x01, 0x62, 0x62, 0x3f, 0x54, 0x4d, + 0x86, 0x8e, 0x64, 0x23, 0x54, 0xe7, 0xac, 0x7f, 0x1a, 0xb7, 0x90, 0xc6, 0xce, 0xbc, 0x1f, 0xb2, 0x26, 0x87, 0xf4, + 0x4e, 0x1c, 0xf2, 0xa6, 0x82, 0x5f, 0x27, 0x1e, 0xb4, 0x24, 0xe0, 0x5d, 0xe5, 0x86, 0x53, 0x1c, 0x75, 0xba, 0x5d, + 0x2c, 0x09, 0x8f, 0x26, 0xfa, 0x57, 0x4e, 0x78, 0x34, 0xd2, 0xbf, 0x04, 0x29, 0xe1, 0x65, 0x7a, 0xc7, 0x05, 0xf1, + 0x56, 0x55, 0xa7, 0x50, 0x58, 0xd0, 0x02, 0x1d, 0x75, 0xdc, 0x7d, 0x38, 0x9e, 0xba, 0x39, 0x80, 0xfc, 0x5f, 0x68, + 0x53, 0x48, 0xd1, 0x2c, 0x70, 0x46, 0xe8, 0x45, 0xd4, 0xed, 0x4f, 0x8f, 0xc2, 0x4e, 0x53, 0x34, 0x0b, 0x14, 0x4f, + 0x8f, 0x74, 0x4d, 0x9c, 0x90, 0x2b, 0x6a, 0x5a, 0xc3, 0x53, 0xb8, 0xc4, 0x4c, 0x48, 0x72, 0x78, 0xda, 0x6a, 0x44, + 0x5d, 0x84, 0x07, 0xc9, 0xaa, 0x85, 0xb3, 0xd5, 0xaa, 0x85, 0xa9, 0xbf, 0x0c, 0xd2, 0x01, 0xa4, 0x54, 0x51, 0x6d, + 0x06, 0xa5, 0xe9, 0xf3, 0x50, 0xc1, 0x85, 0xbc, 0x02, 0x37, 0x17, 0x05, 0x0e, 0x38, 0x31, 0xb7, 0x37, 0x61, 0x48, + 0x87, 0xe5, 0x1b, 0xfb, 0x4a, 0xab, 0x2b, 0xe9, 0xd6, 0xd5, 0x8e, 0xfc, 0x57, 0x19, 0xfe, 0x2e, 0xb0, 0x49, 0xab, + 0x8a, 0xbd, 0xa6, 0xdb, 0xc5, 0x7e, 0xa5, 0x5b, 0xc5, 0xde, 0xec, 0x28, 0x76, 0xbd, 0x5d, 0xec, 0xa3, 0xf0, 0x54, + 0x6c, 0xfc, 0x87, 0xe3, 0xd3, 0x56, 0xe3, 0x18, 0x90, 0xf5, 0xf8, 0xb4, 0x55, 0x15, 0x7a, 0x40, 0xab, 0xb5, 0x52, + 0xe4, 0x0b, 0x35, 0x4e, 0x06, 0xdc, 0x79, 0x3b, 0xeb, 0x85, 0x33, 0xbe, 0xd6, 0xa5, 0x63, 0xf5, 0xa0, 0x0b, 0x46, + 0x1c, 0x52, 0x53, 0x3b, 0x35, 0x38, 0x1d, 0xf6, 0xa7, 0x2c, 0x6c, 0x40, 0x2a, 0x8a, 0xc7, 0xca, 0xfe, 0x42, 0xe5, + 0x5d, 0xee, 0x47, 0x01, 0x49, 0x9d, 0x21, 0xc2, 0x82, 0x34, 0xd4, 0xe0, 0x78, 0xa8, 0xcf, 0xbb, 0x02, 0x7e, 0x9f, + 0xe8, 0xdf, 0xa5, 0x26, 0xc5, 0x7a, 0x22, 0x4c, 0x6f, 0x47, 0x41, 0x5f, 0x92, 0xd7, 0x34, 0xd4, 0xc6, 0xe5, 0x28, + 0x2e, 0x33, 0xe4, 0x57, 0xc8, 0x78, 0x53, 0x66, 0x48, 0x72, 0x25, 0xed, 0x6f, 0xbc, 0x2c, 0x62, 0x30, 0x34, 0xc1, + 0x93, 0x18, 0x8c, 0x4c, 0xf0, 0x28, 0x96, 0xe0, 0x08, 0x41, 0x63, 0xe6, 0x99, 0xaf, 0x5f, 0x5a, 0xd5, 0x95, 0xbe, + 0x6e, 0x25, 0x1a, 0x4b, 0x7b, 0x0c, 0x4e, 0x8a, 0x8f, 0x22, 0x84, 0xbf, 0x0d, 0x85, 0x30, 0x83, 0x36, 0x19, 0xc2, + 0x3c, 0x2a, 0x08, 0xa4, 0x61, 0x1e, 0x4d, 0x08, 0x83, 0x26, 0x79, 0x34, 0x22, 0x6c, 0xd0, 0xf1, 0xd0, 0xe4, 0xa9, + 0x86, 0x1d, 0x00, 0x87, 0xd7, 0x6f, 0xb0, 0x95, 0x69, 0x1c, 0xae, 0xc6, 0xa1, 0x09, 0x49, 0x58, 0x1e, 0xc2, 0x2c, + 0x60, 0x73, 0xea, 0x9f, 0x9d, 0x2a, 0xee, 0x23, 0x8f, 0xa8, 0xa6, 0xde, 0x9f, 0x81, 0xac, 0x86, 0x0f, 0x96, 0x6c, + 0x8d, 0xf7, 0x1e, 0x2c, 0xe5, 0xfa, 0x07, 0xf8, 0x93, 0xdb, 0x3f, 0x4a, 0x9f, 0x7e, 0x6b, 0xf4, 0x39, 0x86, 0x62, + 0x3b, 0x4a, 0xa1, 0xcf, 0xce, 0x0f, 0x2e, 0x27, 0xcb, 0xbb, 0x38, 0x48, 0x69, 0xf1, 0x25, 0xc0, 0x9f, 0xe2, 0x20, + 0x03, 0x46, 0x30, 0xc0, 0x1f, 0xe3, 0xa0, 0x60, 0x01, 0xfe, 0x23, 0x0e, 0x46, 0xd9, 0x22, 0xc0, 0x1f, 0xe2, 0x60, + 0x52, 0x04, 0xf8, 0x3d, 0x68, 0x29, 0x53, 0xbe, 0x98, 0x05, 0xf8, 0xf7, 0x38, 0x90, 0xda, 0x0d, 0x00, 0x5f, 0xc6, + 0x01, 0x63, 0x01, 0x7e, 0x17, 0x07, 0x22, 0x0b, 0xf0, 0x75, 0x1c, 0x88, 0x22, 0xc0, 0x8f, 0xe3, 0xa0, 0xa0, 0x01, + 0xbe, 0x8a, 0x03, 0x28, 0x34, 0x09, 0xf0, 0x93, 0x38, 0x80, 0x96, 0x65, 0x80, 0xdf, 0xc6, 0x01, 0xcf, 0x03, 0xfc, + 0x5b, 0x1c, 0xa8, 0x45, 0xf1, 0xf7, 0x42, 0x70, 0x19, 0xe0, 0xa7, 0x71, 0x30, 0xe5, 0x01, 0x7e, 0x13, 0x07, 0x85, + 0x08, 0xf0, 0xeb, 0x38, 0xa0, 0x59, 0x80, 0x5f, 0xc5, 0x41, 0xc6, 0x02, 0xfc, 0x6b, 0x1c, 0xa4, 0x2c, 0xc0, 0x2f, + 0xe3, 0xe0, 0x9e, 0x65, 0x99, 0x08, 0xf0, 0xb3, 0x38, 0x60, 0x79, 0x80, 0x7f, 0x89, 0x83, 0x64, 0x1a, 0xe0, 0x9f, + 0xe2, 0x80, 0x16, 0x5f, 0x64, 0x80, 0x9f, 0xc7, 0x01, 0xa3, 0x01, 0x7e, 0x61, 0x3a, 0x9a, 0x04, 0xf8, 0xe7, 0x38, + 0xb8, 0x9d, 0x06, 0x6b, 0x9c, 0xe7, 0x64, 0xf9, 0x9a, 0x27, 0xec, 0x0f, 0x16, 0x07, 0xe3, 0xd6, 0xf8, 0x6c, 0x3c, + 0x0e, 0x30, 0xcd, 0x15, 0xff, 0x7b, 0xc1, 0x6e, 0x9f, 0x2a, 0x48, 0xa4, 0x6c, 0x94, 0x3e, 0x0c, 0x30, 0xfd, 0x7b, + 0x41, 0xe3, 0x60, 0x3c, 0xd6, 0x05, 0xfe, 0x5e, 0xd0, 0x19, 0x2d, 0xde, 0xb2, 0x38, 0x78, 0x38, 0x1e, 0x8f, 0xd3, + 0x93, 0x00, 0xd3, 0x7f, 0x16, 0x1f, 0x75, 0x0b, 0xba, 0xc0, 0x88, 0xf1, 0x09, 0xd4, 0xed, 0x8e, 0xbb, 0x69, 0x12, + 0xe0, 0x11, 0x97, 0x7f, 0x2f, 0xe0, 0x7b, 0xcc, 0x4e, 0x92, 0x93, 0x00, 0x8f, 0x32, 0x9a, 0x7c, 0x89, 0x83, 0x96, + 0xfe, 0x95, 0xff, 0xc2, 0xd2, 0xd7, 0x33, 0xa1, 0x75, 0xf8, 0x63, 0x36, 0x4a, 0xd2, 0x00, 0xeb, 0xc1, 0x8c, 0xe1, + 0xef, 0x57, 0xfe, 0x8e, 0xa9, 0x38, 0x38, 0xa3, 0x9d, 0x11, 0xeb, 0x04, 0x78, 0xf4, 0xe6, 0x36, 0x8f, 0x03, 0xda, + 0xed, 0xd0, 0x0e, 0x0d, 0xf0, 0x68, 0x51, 0x64, 0xf7, 0xb7, 0x42, 0xa4, 0x00, 0x84, 0xd1, 0xd9, 0xd9, 0xc3, 0x00, + 0x27, 0xf4, 0x57, 0x05, 0xb5, 0xbb, 0xe3, 0x47, 0x8c, 0xb6, 0x02, 0xfc, 0x0b, 0x2d, 0xd4, 0xc7, 0x85, 0xb4, 0x03, + 0x6d, 0x41, 0x8a, 0x48, 0xde, 0x81, 0x7e, 0x3b, 0x48, 0x3b, 0xa7, 0x8f, 0xda, 0x2c, 0xc0, 0xc9, 0xf5, 0x6b, 0xe8, + 0xed, 0xe1, 0xb8, 0xdb, 0x82, 0x8f, 0x1c, 0x04, 0x45, 0x56, 0x40, 0x23, 0xa7, 0x27, 0x8f, 0xba, 0x2c, 0xd5, 0x89, + 0x92, 0x67, 0x5f, 0xf4, 0xec, 0xcf, 0x60, 0x3e, 0x49, 0xc1, 0x67, 0x52, 0xe4, 0x71, 0x90, 0x26, 0xed, 0x93, 0x63, + 0x48, 0xb8, 0xa7, 0xb9, 0x03, 0xce, 0x1d, 0x54, 0x3d, 0x1b, 0x05, 0xf8, 0xce, 0xa4, 0x9e, 0x8d, 0xf4, 0xc7, 0xe4, + 0xdd, 0xaf, 0xf9, 0x9b, 0x34, 0x0e, 0x46, 0x67, 0x67, 0xa7, 0x2d, 0x48, 0xf8, 0x40, 0xef, 0xe3, 0x80, 0x3e, 0x82, + 0xff, 0x20, 0xfb, 0xe3, 0x33, 0xe8, 0x10, 0x46, 0x78, 0x37, 0xf9, 0xe8, 0xe7, 0x7c, 0x99, 0xd2, 0x2f, 0x3c, 0x0e, + 0x46, 0xe9, 0xe8, 0xe1, 0x29, 0xd4, 0x9b, 0xd1, 0xc9, 0x33, 0x45, 0xa1, 0xdd, 0x56, 0x4b, 0xb7, 0xfc, 0x8e, 0x7f, + 0x65, 0xba, 0x7a, 0xb7, 0x7b, 0x3a, 0xea, 0xc0, 0x08, 0xae, 0x41, 0xc3, 0x01, 0xe3, 0x39, 0x4b, 0x74, 0x83, 0xd7, + 0xc9, 0xd3, 0x34, 0x0e, 0x1e, 0x3d, 0x3a, 0xee, 0x24, 0x49, 0x80, 0xef, 0x3e, 0xa6, 0xa6, 0xb6, 0xce, 0x93, 0x00, + 0xfb, 0x38, 0x60, 0x8f, 0x1e, 0x9d, 0x3e, 0xa4, 0xf0, 0xfd, 0x5c, 0xb7, 0x75, 0x36, 0x1e, 0x25, 0x67, 0xd0, 0xd6, + 0xef, 0x30, 0x9d, 0x93, 0xb3, 0xe3, 0x54, 0xf7, 0xf5, 0xbb, 0x1e, 0x75, 0x67, 0x7c, 0x32, 0x3e, 0xd1, 0x99, 0x7a, + 0xa8, 0xe5, 0xe7, 0x6f, 0x2c, 0x0e, 0x12, 0x96, 0xb6, 0x03, 0x7c, 0x67, 0x17, 0xee, 0xd1, 0x49, 0xab, 0x95, 0x1e, + 0x07, 0x38, 0xbd, 0x9c, 0xcf, 0xdf, 0x6a, 0x08, 0xb6, 0x4f, 0x1e, 0x99, 0x6f, 0xf9, 0xe5, 0x1e, 0x9a, 0x1e, 0x69, + 0xa0, 0xa5, 0x7c, 0xa6, 0x5b, 0x3e, 0x7d, 0x04, 0xff, 0xe9, 0x6f, 0xdd, 0x74, 0xf9, 0x2d, 0xd2, 0x89, 0x59, 0x94, + 0x36, 0x7b, 0xd4, 0x82, 0x1a, 0x63, 0xfe, 0x71, 0x54, 0x70, 0x40, 0xa3, 0x51, 0x07, 0xfe, 0x2f, 0xc0, 0xe3, 0xec, + 0xfa, 0xb5, 0xc5, 0xd9, 0xf1, 0x98, 0x8e, 0x5b, 0x01, 0x1e, 0x8b, 0x8f, 0x52, 0x7d, 0xb8, 0xcc, 0xe3, 0xa0, 0xd3, + 0x39, 0x1b, 0xe9, 0x32, 0x8b, 0x5f, 0x24, 0xd7, 0x78, 0xdc, 0xd2, 0xad, 0x4c, 0xe8, 0x5b, 0x39, 0xba, 0x16, 0xb0, + 0x92, 0xf0, 0x5f, 0x80, 0x27, 0xa0, 0x16, 0xb3, 0xad, 0x9c, 0x99, 0xed, 0x30, 0x79, 0xa7, 0x51, 0x33, 0x7d, 0x08, + 0xf0, 0x72, 0xcb, 0x98, 0x52, 0xda, 0xed, 0xb4, 0x02, 0xac, 0x47, 0x7d, 0xd6, 0x82, 0xff, 0x02, 0x6c, 0x20, 0xa7, + 0xe1, 0x3a, 0xf9, 0xf8, 0xec, 0xe5, 0x6d, 0x1c, 0xd0, 0x74, 0x3c, 0x86, 0x25, 0xd1, 0x93, 0x71, 0xc5, 0xa6, 0x22, + 0x67, 0xf7, 0xbf, 0xde, 0xda, 0xed, 0xa2, 0x13, 0x94, 0x85, 0xce, 0xe9, 0xa3, 0xd1, 0x49, 0x80, 0xdf, 0xa6, 0x9c, + 0xe6, 0xb0, 0x4a, 0x49, 0xda, 0x4d, 0xba, 0x89, 0x4e, 0x98, 0x88, 0x38, 0x38, 0x81, 0x25, 0xef, 0x04, 0x98, 0x7f, + 0xbd, 0xbe, 0x37, 0xe8, 0x06, 0xb5, 0x2d, 0x82, 0x8c, 0x5b, 0xec, 0xf4, 0x2c, 0x09, 0x70, 0x46, 0xbf, 0x3e, 0xfb, + 0xb5, 0x88, 0x03, 0x76, 0xca, 0x4e, 0xc7, 0xd4, 0x7d, 0xff, 0x21, 0xa7, 0xba, 0x46, 0x6b, 0xdc, 0x85, 0xa4, 0xdb, + 0x5c, 0x8f, 0xf5, 0x61, 0x32, 0xd6, 0x18, 0xf2, 0x6a, 0x26, 0xf2, 0xe4, 0xe9, 0x78, 0x2c, 0x0c, 0x16, 0x53, 0xd8, + 0x84, 0x9f, 0x00, 0xda, 0x34, 0x4d, 0xcf, 0xd8, 0x69, 0x80, 0x3f, 0x99, 0x5d, 0x62, 0x27, 0xf0, 0xc9, 0x60, 0x36, + 0xb3, 0xbb, 0xfd, 0x93, 0x01, 0x0a, 0xcc, 0x77, 0x4c, 0xc7, 0x34, 0xed, 0x04, 0xf8, 0x93, 0x86, 0x4b, 0x7a, 0x0c, + 0xff, 0x41, 0x01, 0xe8, 0xec, 0x51, 0x8b, 0xb1, 0x47, 0x2d, 0xfd, 0xe5, 0xe7, 0xd9, 0x99, 0x8f, 0x4e, 0x93, 0x76, + 0x80, 0x3f, 0x59, 0x74, 0x1c, 0x8f, 0x69, 0x0b, 0xd0, 0xf1, 0x93, 0x45, 0xc7, 0x4e, 0x6b, 0xd4, 0xa1, 0xfa, 0xdb, + 0x60, 0xcd, 0xd9, 0xc3, 0x84, 0xc1, 0xe4, 0x3e, 0x19, 0x84, 0x7c, 0xf8, 0xf0, 0xec, 0xec, 0xd1, 0x23, 0xf8, 0xd4, + 0x6d, 0x97, 0x9f, 0x52, 0x5d, 0x66, 0x1a, 0xc9, 0x5a, 0xc9, 0x09, 0xd0, 0xc9, 0x4f, 0x7a, 0x8c, 0xe3, 0xf1, 0x98, + 0xb5, 0x02, 0x9c, 0xf1, 0x19, 0x33, 0x98, 0x60, 0x7e, 0xeb, 0x8e, 0x8e, 0x3b, 0x49, 0x7a, 0xdc, 0x09, 0x70, 0xf6, + 0xf6, 0x99, 0x9e, 0x4d, 0x0b, 0x66, 0xef, 0xb6, 0x9c, 0xc3, 0x9a, 0x19, 0x7d, 0x03, 0x83, 0x84, 0x95, 0x86, 0xca, + 0xef, 0x3d, 0x7a, 0x78, 0x7a, 0x9a, 0xa4, 0x30, 0xd0, 0xf7, 0xd0, 0x2d, 0x80, 0xf1, 0xbd, 0xd9, 0x7c, 0x23, 0xda, + 0xed, 0xc2, 0x74, 0xdf, 0xcf, 0x17, 0xc5, 0xfc, 0x55, 0x1c, 0x3c, 0x3a, 0x7e, 0xd8, 0x4a, 0x47, 0x01, 0x7e, 0x6f, + 0x27, 0x78, 0x9c, 0x8c, 0x8e, 0x1f, 0xb6, 0x03, 0xfc, 0x5e, 0xef, 0xb7, 0x87, 0xa3, 0xd3, 0x33, 0x38, 0x37, 0xde, + 0xcb, 0x79, 0xf1, 0x76, 0xa2, 0x0b, 0x8c, 0xe9, 0x23, 0x68, 0xf6, 0x37, 0xbd, 0x1b, 0xd3, 0x36, 0x6c, 0xe4, 0xf7, + 0x7a, 0x93, 0x69, 0x3c, 0x79, 0xd8, 0xee, 0x9e, 0x75, 0x03, 0x3c, 0xe3, 0x69, 0x0e, 0x04, 0x5e, 0x6f, 0x94, 0x47, + 0xed, 0x47, 0x0f, 0x5b, 0x01, 0x9e, 0xbd, 0x55, 0xc9, 0x47, 0x3a, 0xd3, 0xd4, 0x78, 0x0c, 0x30, 0x9b, 0x71, 0xa9, + 0xee, 0xdf, 0x48, 0x4b, 0x8f, 0x59, 0x3b, 0xc0, 0x33, 0x91, 0x24, 0x54, 0xbe, 0x35, 0x09, 0xa3, 0x6e, 0x80, 0x73, + 0xfa, 0x95, 0xfe, 0x25, 0xdc, 0x66, 0x4a, 0x19, 0x4d, 0x75, 0x9a, 0xc6, 0xe1, 0x00, 0xbf, 0x4b, 0xe1, 0x16, 0x2e, + 0x0e, 0xc6, 0xe9, 0xb8, 0x0b, 0xe0, 0x01, 0x02, 0x64, 0xb0, 0x1b, 0xa0, 0x01, 0x5f, 0xe9, 0xe3, 0x51, 0x1c, 0x9c, + 0x8e, 0xce, 0x58, 0xe7, 0x38, 0xc0, 0x25, 0x35, 0xa2, 0x5d, 0xc8, 0xd7, 0x9f, 0x1f, 0xf5, 0x96, 0x3a, 0x31, 0x09, + 0x1a, 0x40, 0x29, 0x7d, 0xd8, 0x4a, 0x4f, 0x03, 0x3c, 0x7f, 0xcd, 0xdc, 0x1e, 0x63, 0x8c, 0x9d, 0x01, 0x2c, 0x21, + 0x49, 0x23, 0xd0, 0xd9, 0x78, 0xf4, 0xe8, 0x4c, 0x7f, 0x03, 0x18, 0xe8, 0x98, 0x31, 0x00, 0xd2, 0xfc, 0x35, 0x2b, + 0x01, 0x91, 0x8e, 0x1e, 0xb6, 0x80, 0xbe, 0xcc, 0xe9, 0x9c, 0xde, 0xd3, 0xdb, 0xa7, 0x73, 0x3d, 0xa7, 0x71, 0xda, + 0x0d, 0xf0, 0xfc, 0xf9, 0x2f, 0xf3, 0xc5, 0x78, 0xac, 0x27, 0x44, 0x47, 0x8f, 0x02, 0x3c, 0x67, 0xc5, 0x02, 0xd6, + 0xe8, 0xac, 0x7b, 0x3c, 0x0e, 0xb0, 0x45, 0xc3, 0xa4, 0x95, 0x8c, 0xe0, 0x9a, 0x71, 0x31, 0x8b, 0x83, 0x34, 0xa5, + 0xad, 0x14, 0x2e, 0x1d, 0xc5, 0xed, 0xaf, 0x85, 0x41, 0x23, 0xa6, 0xf1, 0xc1, 0xae, 0x21, 0xcc, 0x17, 0xe0, 0xf1, + 0x71, 0xc4, 0x92, 0x84, 0xda, 0xc4, 0xd3, 0xd3, 0xe3, 0x63, 0xc0, 0x3d, 0x33, 0x43, 0x83, 0x20, 0x6f, 0xe4, 0xfd, + 0xa8, 0x10, 0x70, 0x74, 0x01, 0x51, 0x05, 0xb2, 0xfa, 0xe6, 0xfe, 0xb5, 0xa6, 0xab, 0xed, 0xd3, 0x47, 0xb0, 0x00, + 0x92, 0xa6, 0xe9, 0x2b, 0x73, 0xb8, 0x9d, 0x8d, 0x4e, 0xba, 0xed, 0xe3, 0x00, 0xbb, 0x8d, 0x40, 0xcf, 0x5a, 0x0f, + 0x3b, 0x50, 0x22, 0x4f, 0xef, 0x4d, 0x89, 0xf1, 0x09, 0x3d, 0x39, 0x6d, 0x05, 0xd8, 0x6d, 0x0d, 0x76, 0x36, 0xea, + 0x3e, 0x84, 0x4f, 0x39, 0x65, 0x59, 0xa6, 0xf1, 0xbb, 0x0b, 0x70, 0x91, 0xfc, 0x59, 0x4e, 0xe3, 0x80, 0xb6, 0xba, + 0x9d, 0x4e, 0x0a, 0x9f, 0xd9, 0x57, 0x56, 0xc4, 0x41, 0xd2, 0x82, 0xff, 0x02, 0xec, 0xed, 0x24, 0x36, 0x0a, 0xb0, + 0xc6, 0xbb, 0x53, 0xda, 0xd5, 0x7b, 0xdf, 0xee, 0xaa, 0xd6, 0x59, 0x0b, 0x36, 0xac, 0xdd, 0x54, 0xf6, 0x4b, 0xe6, + 0xe2, 0xd6, 0x92, 0x58, 0x1a, 0x60, 0x0f, 0x41, 0xc7, 0x0f, 0xc7, 0x01, 0x76, 0x3b, 0xee, 0xe4, 0xf4, 0xac, 0x03, + 0xa4, 0x4c, 0x01, 0xa1, 0x48, 0x3b, 0xa3, 0x13, 0x20, 0x4d, 0x8a, 0xbd, 0x36, 0x78, 0x12, 0x60, 0xf5, 0x54, 0xaa, + 0x57, 0x71, 0x90, 0x9e, 0x8d, 0xc6, 0xe9, 0x59, 0x80, 0x95, 0x98, 0x51, 0x25, 0x34, 0x05, 0x3c, 0x3e, 0x79, 0x18, + 0x60, 0x8d, 0xe6, 0x2d, 0xd6, 0x4a, 0x5b, 0x01, 0xb6, 0x47, 0x09, 0x63, 0x67, 0x1d, 0x98, 0xd6, 0xcf, 0xcf, 0x15, + 0xe0, 0x72, 0xca, 0x46, 0xc7, 0x01, 0x2e, 0xe9, 0xbd, 0x26, 0x44, 0xf0, 0x25, 0x67, 0xe2, 0x8b, 0x65, 0x3d, 0x80, + 0xd4, 0xb9, 0x0d, 0x0f, 0xcb, 0xf0, 0xf2, 0xd6, 0xa0, 0x11, 0xd5, 0x5b, 0xdc, 0xbb, 0x86, 0x7d, 0x42, 0x43, 0xc7, + 0xb6, 0x73, 0xb2, 0x5c, 0xe3, 0x32, 0xba, 0xe9, 0x17, 0x76, 0x2f, 0xc3, 0x1c, 0x4c, 0xe2, 0x6b, 0x29, 0x32, 0x47, + 0xce, 0x9e, 0xdf, 0xba, 0x6c, 0x82, 0x20, 0x29, 0x49, 0xab, 0x27, 0xcf, 0x9d, 0x1b, 0x69, 0x4f, 0x42, 0xcc, 0x03, + 0x48, 0x2f, 0x08, 0x25, 0x0a, 0x42, 0xc3, 0x18, 0x61, 0xd2, 0x59, 0xd7, 0x6b, 0x99, 0x52, 0x18, 0x7b, 0x7d, 0x4a, + 0xa8, 0x0b, 0x0a, 0x0f, 0x77, 0xc4, 0xf9, 0x40, 0x0c, 0x51, 0x4f, 0x10, 0x1d, 0xe2, 0xf9, 0x45, 0xae, 0xc2, 0x3c, + 0x1f, 0x14, 0x43, 0xdc, 0x3e, 0x45, 0x18, 0x82, 0x27, 0x90, 0x81, 0xb8, 0xb8, 0x68, 0x9f, 0x1e, 0x68, 0xa1, 0xef, + 0xe2, 0xe2, 0xcc, 0xfc, 0x80, 0x7f, 0x87, 0x55, 0xc0, 0x6a, 0x18, 0xdf, 0x63, 0xe6, 0x69, 0xf4, 0x34, 0x7f, 0xfd, + 0x98, 0xad, 0x56, 0xe1, 0x63, 0x46, 0x60, 0xc6, 0xf8, 0x31, 0x8b, 0xf4, 0xa5, 0x85, 0x71, 0x8d, 0x21, 0x03, 0xd0, + 0x9c, 0xb5, 0x30, 0x84, 0x51, 0x77, 0x9c, 0xf7, 0x63, 0x36, 0xe0, 0x75, 0xb7, 0xea, 0x2a, 0x76, 0xf1, 0xc1, 0xc1, + 0xb2, 0x88, 0x95, 0x11, 0x13, 0x94, 0x11, 0x13, 0x94, 0x11, 0x13, 0x54, 0x15, 0x3d, 0xfe, 0xa4, 0x0f, 0x52, 0x8a, + 0x56, 0xb6, 0x58, 0x9e, 0xfa, 0x1d, 0xa8, 0x3d, 0x40, 0x3b, 0xd9, 0xaf, 0x94, 0x1d, 0xa5, 0xae, 0x62, 0xa7, 0x02, + 0x63, 0x67, 0xa2, 0xd5, 0x76, 0x1c, 0xfd, 0x3b, 0xea, 0x8e, 0x97, 0x35, 0xb1, 0xec, 0xdd, 0x4e, 0xb1, 0x0c, 0x56, + 0x52, 0x8b, 0x66, 0xfb, 0x26, 0x10, 0x87, 0x1a, 0x3c, 0xd4, 0x82, 0x59, 0x15, 0x1d, 0xae, 0x01, 0x49, 0x3d, 0x90, + 0x42, 0xce, 0xb4, 0x94, 0x56, 0xa0, 0x38, 0x55, 0x61, 0x01, 0x1a, 0x4a, 0xa7, 0xa0, 0x2c, 0x83, 0x98, 0x36, 0x34, + 0x40, 0x72, 0x23, 0xa3, 0x19, 0x59, 0xad, 0x0b, 0xa2, 0x0b, 0x68, 0xc2, 0xb4, 0xc4, 0x02, 0x0d, 0x48, 0xdd, 0x80, + 0xb4, 0x95, 0x41, 0x9c, 0xb1, 0xd9, 0x27, 0x3a, 0x3b, 0xd7, 0xd9, 0x79, 0x99, 0x2d, 0x5c, 0xb6, 0x11, 0x12, 0x85, + 0xce, 0x16, 0x65, 0x36, 0xc8, 0x6c, 0x78, 0x12, 0xe7, 0x78, 0x14, 0x0b, 0x23, 0xaa, 0x55, 0xb2, 0xd5, 0x5b, 0xea, + 0x6b, 0x73, 0x0f, 0x0e, 0xc2, 0x52, 0x4e, 0xd2, 0x6a, 0xe2, 0x07, 0x4b, 0x1e, 0x15, 0x5a, 0x06, 0xe2, 0xd1, 0xc4, + 0xfe, 0x1d, 0xad, 0x37, 0x65, 0xa5, 0x62, 0x32, 0xfa, 0x46, 0x49, 0xf4, 0xd9, 0x29, 0x51, 0x1f, 0x73, 0x1d, 0xfe, + 0xe6, 0x9c, 0x44, 0xad, 0xd6, 0x71, 0xfb, 0xb8, 0x75, 0xd6, 0xe7, 0x87, 0xed, 0x4e, 0xf4, 0xa8, 0x13, 0x6b, 0x45, + 0xc4, 0x5c, 0xdc, 0x82, 0x02, 0xe6, 0xa8, 0x13, 0x9d, 0xa0, 0xc3, 0x76, 0xd4, 0xea, 0x76, 0x9b, 0xf0, 0x0f, 0x7e, + 0xaf, 0xca, 0x6a, 0x27, 0xad, 0x93, 0x6e, 0x9f, 0x1f, 0x6d, 0x54, 0x0a, 0x79, 0x03, 0x0a, 0xa2, 0x23, 0x5d, 0x09, + 0x43, 0xfd, 0x6a, 0x79, 0x9f, 0x6d, 0xe9, 0x79, 0xde, 0xab, 0x30, 0x37, 0xaa, 0x38, 0x80, 0xaa, 0xfb, 0x9a, 0x68, + 0x20, 0xba, 0xaf, 0x51, 0x19, 0xa2, 0x76, 0x59, 0x80, 0xa8, 0xfd, 0x98, 0x87, 0xb2, 0xc1, 0x0e, 0x43, 0x93, 0xaf, + 0xa0, 0x6e, 0x13, 0xc2, 0xc6, 0xe1, 0x89, 0xcd, 0xcd, 0xfd, 0xdc, 0x09, 0x42, 0xcd, 0x1c, 0x72, 0x47, 0x36, 0x57, + 0xf8, 0xb9, 0x23, 0x84, 0x9a, 0x02, 0x72, 0x69, 0xcc, 0x23, 0x0a, 0x39, 0x2a, 0xa2, 0x4d, 0x0d, 0xc9, 0x6a, 0x51, + 0x9e, 0x33, 0x37, 0x6c, 0x3e, 0x86, 0xe5, 0xd1, 0x04, 0xc5, 0x0a, 0xd2, 0x10, 0x35, 0xaf, 0xd2, 0xe6, 0xb4, 0x70, + 0xa9, 0xc6, 0x81, 0x8c, 0x06, 0xfc, 0x73, 0xc8, 0xf4, 0x7b, 0x13, 0xad, 0xfe, 0xf1, 0x69, 0x2b, 0x6e, 0x83, 0x8f, + 0x34, 0xc8, 0xda, 0xd2, 0xc8, 0xda, 0xd2, 0xc9, 0xda, 0xd2, 0xc9, 0xda, 0x20, 0xc0, 0x7b, 0x7d, 0xff, 0x2d, 0x6a, + 0x76, 0x27, 0xbc, 0x34, 0x62, 0x31, 0x56, 0x0a, 0xa1, 0x5a, 0xad, 0x96, 0x6b, 0x30, 0x31, 0x2a, 0x6b, 0x88, 0xbc, + 0x52, 0x7f, 0x2e, 0x8b, 0xb8, 0x85, 0x27, 0x31, 0x68, 0xb9, 0x5b, 0x98, 0xea, 0xcd, 0xed, 0xa8, 0xc2, 0x66, 0xf8, + 0x9a, 0xbe, 0x53, 0x27, 0x5f, 0x90, 0x63, 0xad, 0x3d, 0x5e, 0x16, 0x31, 0x37, 0x34, 0x83, 0x1b, 0x9a, 0xc1, 0x0d, + 0xcd, 0x80, 0x46, 0x70, 0x59, 0x58, 0x97, 0x8d, 0x28, 0x81, 0x2b, 0x81, 0xc1, 0xf1, 0x10, 0xa2, 0xf7, 0x85, 0x8a, + 0xe8, 0x51, 0x6f, 0x74, 0xde, 0x86, 0x68, 0x65, 0xa6, 0xa4, 0x8a, 0xa8, 0x76, 0xda, 0x2e, 0xc7, 0xfc, 0xaa, 0x86, + 0xf6, 0x11, 0x3c, 0x25, 0x73, 0xa9, 0xc2, 0x16, 0xd8, 0x6c, 0x04, 0x45, 0xd0, 0xd7, 0x64, 0x21, 0xd6, 0x3a, 0x1b, + 0x6b, 0x8b, 0xfd, 0x65, 0xc3, 0x05, 0xd6, 0x50, 0x02, 0xff, 0x01, 0x85, 0x2f, 0xac, 0xf2, 0xc9, 0x2f, 0x4d, 0x4d, + 0xad, 0x9d, 0x98, 0x39, 0x12, 0x7a, 0x60, 0x2f, 0xee, 0x82, 0x3d, 0xf5, 0x25, 0x11, 0xb9, 0xf6, 0x56, 0x24, 0x55, + 0xb8, 0x62, 0xf0, 0xde, 0x25, 0x77, 0x54, 0xfb, 0xb2, 0xbc, 0x30, 0x7f, 0x5e, 0x51, 0xcf, 0xd9, 0xaf, 0x98, 0x8c, + 0x9c, 0x8f, 0xec, 0x8d, 0x0e, 0xea, 0x43, 0xf6, 0xf7, 0x8d, 0x29, 0xb7, 0xfe, 0xda, 0xb4, 0xe5, 0xd6, 0x89, 0x3a, + 0x1b, 0x76, 0xa8, 0x5b, 0xa3, 0xbf, 0x9d, 0xab, 0x5a, 0x31, 0x19, 0x21, 0x8f, 0x66, 0x6b, 0xb0, 0xe6, 0x15, 0xb0, + 0xa4, 0xad, 0x57, 0x7a, 0x30, 0x42, 0xef, 0x7a, 0xcc, 0xeb, 0x62, 0x32, 0xda, 0xf9, 0xe6, 0x88, 0xe9, 0xb1, 0xff, + 0x96, 0x7a, 0x3d, 0x38, 0xd5, 0xf6, 0x94, 0xdd, 0x7d, 0xaf, 0xce, 0xed, 0xce, 0x3a, 0x32, 0xfb, 0x5e, 0x9d, 0xa7, + 0xbb, 0xea, 0xcc, 0xf8, 0x5d, 0xe8, 0xf6, 0x8e, 0xf2, 0xd4, 0xd8, 0xda, 0x3e, 0x68, 0x32, 0x82, 0x20, 0xf1, 0xf0, + 0xd7, 0x84, 0x72, 0xe9, 0x39, 0x12, 0x0e, 0xab, 0x20, 0xfa, 0x51, 0x37, 0x66, 0x98, 0x92, 0xce, 0x61, 0xa1, 0x83, + 0xb9, 0xc8, 0x88, 0x36, 0xf3, 0x88, 0xe2, 0x8c, 0x84, 0x21, 0x3d, 0x4c, 0x20, 0x24, 0x4d, 0xbb, 0x4f, 0xe3, 0x90, + 0x36, 0x12, 0x74, 0x14, 0xb6, 0x1b, 0xf4, 0x30, 0x41, 0xa8, 0xd1, 0x06, 0x9d, 0xa9, 0x20, 0xed, 0x66, 0x06, 0x41, + 0x2a, 0x35, 0x29, 0xce, 0x0e, 0x65, 0x54, 0x34, 0xc4, 0x61, 0x1e, 0x15, 0x8d, 0xa8, 0x8b, 0x65, 0x34, 0x29, 0x93, + 0x27, 0x3a, 0x79, 0x62, 0x92, 0x47, 0x65, 0xf2, 0x48, 0x27, 0x8f, 0x4c, 0x32, 0x25, 0xc5, 0xa1, 0x8c, 0x68, 0x23, + 0x6c, 0x37, 0x0b, 0x74, 0x08, 0x23, 0x70, 0xa3, 0x27, 0xd2, 0x8f, 0x0d, 0xbe, 0xd6, 0xc6, 0x35, 0x73, 0x91, 0xd9, + 0x68, 0x9d, 0x15, 0x90, 0x4a, 0x8f, 0x27, 0xa8, 0xf3, 0xcc, 0x03, 0x13, 0x56, 0xe6, 0x8f, 0x8b, 0x45, 0xb7, 0x4e, + 0x32, 0x91, 0x7b, 0x1e, 0x5d, 0x60, 0x84, 0xfe, 0xc5, 0xfa, 0xb1, 0x00, 0x54, 0xd7, 0x34, 0x9b, 0x4f, 0xe9, 0x96, + 0xdb, 0x6c, 0x31, 0x19, 0xd9, 0x9d, 0x55, 0x36, 0xc3, 0x68, 0x61, 0x62, 0x3c, 0xd7, 0x1d, 0x1c, 0x01, 0xd4, 0xce, + 0xa9, 0x32, 0xa2, 0x5a, 0x49, 0x6e, 0x6a, 0x4c, 0x0a, 0x76, 0x2f, 0x13, 0x9a, 0xb1, 0xb0, 0x3a, 0x80, 0xab, 0x61, + 0x32, 0xf2, 0x02, 0x4c, 0xe1, 0x8b, 0xc3, 0xe8, 0xb8, 0xa1, 0xa2, 0xc9, 0x61, 0xd4, 0x7d, 0xd4, 0x50, 0xd1, 0xe8, + 0x30, 0x6a, 0xb7, 0x2b, 0x9c, 0x8d, 0x0a, 0xa2, 0xa2, 0x09, 0x51, 0xa0, 0x31, 0x34, 0x8d, 0x8a, 0x39, 0x05, 0xdb, + 0xae, 0x7f, 0x63, 0x18, 0x0d, 0x3b, 0x8c, 0x9c, 0x4d, 0x4c, 0xb8, 0xcb, 0xad, 0x31, 0xf8, 0xdd, 0x74, 0xba, 0xdd, + 0xa6, 0x8a, 0x0a, 0xac, 0xcc, 0x4a, 0x36, 0x55, 0x34, 0xc1, 0xca, 0x2c, 0x5f, 0x53, 0x45, 0x23, 0xd3, 0x94, 0xd6, + 0x01, 0x82, 0xde, 0xb1, 0x04, 0xd6, 0x73, 0xe6, 0x41, 0xbe, 0xe3, 0xbc, 0x53, 0xd6, 0xa0, 0x35, 0xfc, 0x5e, 0xb9, + 0xa6, 0x2b, 0x28, 0xa9, 0x02, 0x1b, 0x1f, 0xf6, 0xad, 0xa2, 0xed, 0xaa, 0x49, 0xf6, 0xaf, 0xcb, 0x96, 0xcd, 0x16, + 0x42, 0xd5, 0x0b, 0x5e, 0xd6, 0x30, 0xc4, 0x96, 0xb2, 0x07, 0xf7, 0x3f, 0x94, 0x84, 0x10, 0xd4, 0x4e, 0x9f, 0x42, + 0x9c, 0x38, 0x3d, 0x32, 0x24, 0xf1, 0x46, 0x63, 0x8d, 0x42, 0xef, 0xbc, 0x7d, 0xea, 0x53, 0xd5, 0xad, 0x48, 0x77, + 0x84, 0x04, 0x8b, 0xdc, 0xd8, 0x42, 0xa6, 0x81, 0xc7, 0x82, 0x58, 0xed, 0x6e, 0xed, 0x80, 0x38, 0x38, 0xd8, 0x3c, + 0x2f, 0xdc, 0x9b, 0x03, 0x5b, 0xef, 0x0c, 0x54, 0x86, 0x74, 0xee, 0x25, 0x24, 0x63, 0x62, 0xcb, 0x3d, 0x44, 0x71, + 0x31, 0xa7, 0x1e, 0x6a, 0x0a, 0x3f, 0xa8, 0x02, 0xee, 0xd9, 0x9c, 0xe6, 0xa9, 0xce, 0xd0, 0x7d, 0x0d, 0xbd, 0xb1, + 0xbd, 0xf1, 0x27, 0x54, 0x1a, 0x09, 0xfe, 0xcb, 0x8e, 0xbd, 0x4e, 0xec, 0x4b, 0x2d, 0x7e, 0xa3, 0x7f, 0xf9, 0x26, + 0xb9, 0x15, 0x6c, 0xac, 0x33, 0xf6, 0x6a, 0x55, 0x7b, 0x97, 0xc7, 0xbc, 0xfe, 0x82, 0x0e, 0x0e, 0xb8, 0x7c, 0x06, + 0x56, 0xc4, 0x2c, 0x6c, 0xf8, 0x87, 0xef, 0xdf, 0xb5, 0xd3, 0xfa, 0x2f, 0x7d, 0xae, 0xc6, 0xde, 0x41, 0x77, 0x59, + 0xcb, 0xdf, 0xb9, 0x12, 0x7d, 0x15, 0x73, 0xbb, 0xd6, 0x7f, 0x55, 0x36, 0xda, 0x5b, 0x2f, 0x44, 0x1d, 0x1c, 0xf0, + 0x2a, 0x4e, 0x53, 0xf0, 0x43, 0x80, 0xfa, 0x5a, 0x06, 0x79, 0x96, 0x09, 0x0a, 0x37, 0xa2, 0x70, 0xc5, 0x10, 0x37, + 0xf8, 0x91, 0xc2, 0x7f, 0x88, 0xff, 0x4f, 0x8d, 0x1c, 0xaa, 0xb8, 0xc1, 0x3d, 0x01, 0xcc, 0x67, 0x85, 0xaa, 0x08, + 0x89, 0x1a, 0xd2, 0xbe, 0xc9, 0x35, 0x2a, 0x0f, 0x73, 0x3a, 0x9f, 0x67, 0xf7, 0xfa, 0x91, 0x2c, 0x8f, 0xa3, 0xaa, + 0x2e, 0x9a, 0x6c, 0x78, 0x3a, 0x5c, 0x00, 0x4f, 0x0f, 0xb8, 0x87, 0xb4, 0x7b, 0x69, 0x79, 0xb9, 0x2d, 0x11, 0x48, + 0x66, 0x39, 0x11, 0xcd, 0x76, 0x2f, 0xbf, 0x00, 0xb9, 0xac, 0xd9, 0x44, 0xca, 0x46, 0xed, 0xc6, 0x1c, 0x64, 0xb2, + 0xdc, 0xb8, 0x90, 0xee, 0x99, 0x82, 0x20, 0xb9, 0x09, 0x2d, 0xb2, 0xed, 0x2e, 0xc5, 0xc7, 0x21, 0xa0, 0x11, 0x32, + 0x02, 0x9f, 0x2f, 0x2c, 0x72, 0xe0, 0x3a, 0x0b, 0xd7, 0xf1, 0x37, 0x5a, 0x2a, 0x06, 0xf9, 0x70, 0x88, 0x0b, 0xfd, + 0x2e, 0x44, 0x39, 0x1f, 0xb8, 0x69, 0x2a, 0xdf, 0x19, 0xf2, 0x44, 0x14, 0xbe, 0x5a, 0xed, 0xc3, 0x33, 0x3e, 0xb6, + 0x4d, 0xf0, 0x39, 0xb5, 0x3f, 0xab, 0x27, 0x3b, 0x60, 0x1c, 0x8c, 0xb4, 0xf4, 0x45, 0xa1, 0x95, 0x37, 0xd9, 0xb9, + 0xec, 0x35, 0x1a, 0x4c, 0x47, 0x58, 0x22, 0x10, 0x4e, 0x0d, 0x1c, 0x02, 0xe1, 0x8f, 0x09, 0x9a, 0x24, 0x99, 0x09, + 0x3d, 0x07, 0x31, 0xb1, 0x6b, 0x09, 0xab, 0x55, 0x6e, 0x42, 0x9b, 0xe8, 0x1c, 0x13, 0xe4, 0xa4, 0xec, 0xa7, 0x8c, + 0xa1, 0x5a, 0x99, 0x71, 0x70, 0xbb, 0xd5, 0xdf, 0x56, 0xfb, 0x79, 0x8f, 0x9b, 0x6b, 0x3c, 0xae, 0x03, 0x06, 0x68, + 0x40, 0x2d, 0x37, 0x36, 0xb8, 0x31, 0x70, 0x0f, 0x8d, 0x35, 0x2e, 0xdb, 0x84, 0xa0, 0x2c, 0x1d, 0xe4, 0xcd, 0xcd, + 0xad, 0x0b, 0x18, 0x98, 0xeb, 0x39, 0xe5, 0x48, 0x0d, 0x40, 0x8e, 0x1e, 0x12, 0xe8, 0xdc, 0xfc, 0xac, 0xe8, 0x42, + 0x25, 0x13, 0x97, 0x63, 0xfc, 0xc5, 0xbb, 0xcd, 0x1b, 0x04, 0x37, 0x37, 0x7a, 0x93, 0xdf, 0xdc, 0x04, 0xd8, 0xb7, + 0x2a, 0x0f, 0x3c, 0x5e, 0x30, 0x18, 0x96, 0x31, 0xa5, 0xf4, 0xc6, 0x6f, 0xb6, 0xab, 0xc6, 0xde, 0xd3, 0x0a, 0xef, + 0x60, 0x79, 0x74, 0xe3, 0x5b, 0x5e, 0x98, 0x03, 0x0e, 0xf0, 0x66, 0x03, 0x3e, 0xec, 0xbd, 0x09, 0x73, 0x74, 0x70, + 0xf0, 0x26, 0x14, 0xa8, 0x7f, 0xcd, 0xf4, 0x9d, 0x1b, 0xb8, 0x61, 0x0f, 0xb8, 0x1e, 0xbe, 0xf0, 0x10, 0xe0, 0x9a, + 0x6d, 0x4a, 0x36, 0x6f, 0x75, 0xd0, 0x8b, 0x18, 0x82, 0x6a, 0x43, 0x68, 0x5f, 0x0b, 0x12, 0xe8, 0xf5, 0x8d, 0x0f, + 0xed, 0x1e, 0x23, 0x0c, 0x58, 0xf8, 0xd2, 0x49, 0x8e, 0x45, 0x33, 0x56, 0x4c, 0x58, 0xb1, 0x5a, 0xbd, 0xa7, 0xc6, + 0xf1, 0x6d, 0x23, 0x46, 0x63, 0xde, 0x6b, 0x34, 0xa8, 0x1e, 0x3f, 0x88, 0x0f, 0x74, 0x88, 0xf7, 0xdf, 0x84, 0x05, + 0x42, 0x60, 0x61, 0xc4, 0xf3, 0x85, 0x73, 0xf2, 0x4a, 0x6a, 0xeb, 0x52, 0xa0, 0xb2, 0x91, 0x8c, 0xb4, 0xf0, 0x94, + 0x24, 0xe5, 0x1a, 0x9d, 0x4f, 0x7b, 0x8d, 0x46, 0x86, 0x44, 0x98, 0x0c, 0xb2, 0x21, 0xe6, 0xb8, 0x80, 0xcb, 0xd4, + 0xed, 0x75, 0x98, 0xb3, 0x1a, 0xe5, 0xb2, 0xf3, 0x5d, 0x9a, 0xb1, 0xc6, 0x8f, 0xe9, 0xda, 0x03, 0xc6, 0x63, 0xea, + 0x11, 0x89, 0x5d, 0x40, 0x96, 0x06, 0xc8, 0xb9, 0x03, 0xb2, 0xd4, 0x40, 0xce, 0x51, 0x7f, 0x0e, 0xd1, 0x8a, 0x72, + 0x14, 0x6f, 0x51, 0xf4, 0x7a, 0x5c, 0x4d, 0xeb, 0xb3, 0x81, 0xb9, 0x0e, 0xed, 0x60, 0x97, 0x03, 0x1e, 0x3a, 0xb1, + 0x6e, 0x80, 0x39, 0x59, 0x06, 0x81, 0x0e, 0x31, 0x8b, 0xef, 0xf4, 0x9f, 0xe8, 0x0e, 0xdf, 0x9b, 0x1f, 0xf7, 0x9e, + 0x32, 0xe9, 0x79, 0x4d, 0xdb, 0xc0, 0x6d, 0x40, 0xb6, 0x20, 0x0a, 0x00, 0xad, 0x6d, 0x74, 0x41, 0x59, 0x7f, 0x70, + 0x2d, 0x37, 0x71, 0x20, 0x64, 0x83, 0xe4, 0x58, 0x7a, 0xa4, 0xf3, 0xcf, 0x3f, 0x03, 0xd4, 0x97, 0x10, 0xc6, 0xc7, + 0x9e, 0x6c, 0xcd, 0x36, 0x6a, 0x04, 0x51, 0x10, 0x87, 0x2e, 0x4a, 0x04, 0xec, 0x8c, 0x20, 0xf0, 0x1e, 0x5b, 0x29, + 0x87, 0xf1, 0xa1, 0x36, 0x0c, 0x3d, 0xa8, 0x2a, 0xee, 0xc5, 0xc5, 0x72, 0x33, 0xca, 0x90, 0x86, 0xaa, 0xd4, 0x21, + 0x5e, 0x90, 0x79, 0x81, 0x8c, 0x11, 0x76, 0x70, 0xc0, 0x06, 0x72, 0xe8, 0x3d, 0x29, 0x56, 0x5d, 0x87, 0x2b, 0x7f, + 0xe1, 0x42, 0x9a, 0x0f, 0xd4, 0x70, 0xb5, 0x32, 0x7f, 0xc9, 0x07, 0x2d, 0xcd, 0xc0, 0xdb, 0x70, 0xde, 0x6d, 0xbc, + 0xdc, 0x2d, 0x8b, 0x45, 0x4a, 0xfc, 0x0e, 0x56, 0x83, 0x2e, 0x68, 0x9f, 0x9d, 0x6a, 0xdb, 0x41, 0x7d, 0xae, 0x35, + 0x0a, 0x5e, 0xc8, 0xdc, 0xea, 0x48, 0xc3, 0x73, 0xe5, 0xe7, 0xd5, 0x42, 0xdf, 0x26, 0x79, 0x19, 0xc1, 0x14, 0x8e, + 0x94, 0x08, 0xcc, 0xc6, 0x35, 0x9d, 0x84, 0x1f, 0x75, 0x2a, 0x69, 0x59, 0x48, 0x80, 0x02, 0x47, 0xfa, 0x72, 0x5e, + 0x47, 0xa8, 0x67, 0x68, 0x07, 0x91, 0xf3, 0x4c, 0x68, 0xea, 0xb2, 0xa5, 0x0d, 0x25, 0x15, 0xcc, 0xc4, 0x42, 0xb2, + 0xc5, 0x1c, 0xce, 0xf7, 0x32, 0x2d, 0xc9, 0x78, 0xf2, 0xa5, 0x9e, 0x02, 0xe6, 0x9f, 0x77, 0x6a, 0xc6, 0xf2, 0x45, + 0x60, 0xe7, 0xf9, 0xca, 0x88, 0xfb, 0x6f, 0x5e, 0xe0, 0xc7, 0xa4, 0x73, 0xf8, 0x0a, 0x7f, 0xa4, 0xe4, 0x71, 0xe3, + 0x15, 0x9e, 0x70, 0x62, 0x78, 0x83, 0xe8, 0xcd, 0xeb, 0xeb, 0x17, 0xef, 0x5e, 0xbc, 0x7f, 0x7a, 0xf3, 0xe2, 0xd5, + 0xb3, 0x17, 0xaf, 0x5e, 0xbc, 0xfb, 0x88, 0xff, 0xa6, 0xe4, 0xd5, 0x51, 0xfb, 0xac, 0x85, 0x3f, 0x90, 0x57, 0x47, + 0x1d, 0x7c, 0xa7, 0xc8, 0xab, 0xa3, 0x13, 0x9c, 0xe5, 0xe4, 0xd5, 0x61, 0xe7, 0xe8, 0x18, 0x2f, 0x94, 0x69, 0x32, + 0x13, 0x93, 0x76, 0x0b, 0xff, 0x6d, 0xbf, 0x40, 0xbc, 0xaf, 0x66, 0x31, 0x61, 0x9b, 0xc6, 0x0f, 0x50, 0x86, 0x8e, + 0xa4, 0x36, 0x44, 0x39, 0xf7, 0xd0, 0x69, 0x9a, 0xfb, 0xe8, 0x64, 0x62, 0x28, 0x83, 0x0d, 0x23, 0xa0, 0x15, 0x27, + 0xb6, 0x1d, 0x7e, 0xd4, 0x66, 0xc7, 0x40, 0x9f, 0x78, 0x29, 0x1c, 0x97, 0x2a, 0x9c, 0xb6, 0xd5, 0x62, 0x8c, 0x33, + 0x21, 0x8a, 0x70, 0x01, 0x8c, 0x80, 0xd6, 0x5a, 0xf0, 0xa3, 0x32, 0x58, 0x93, 0x3c, 0x27, 0xed, 0x7e, 0x3b, 0x96, + 0xe7, 0xa4, 0xd3, 0xef, 0xc0, 0x9f, 0x6e, 0xbf, 0x1b, 0xb7, 0x5b, 0xe8, 0xd0, 0x1b, 0xc7, 0x1f, 0x35, 0xb4, 0x1e, + 0x0c, 0xb1, 0xed, 0x42, 0xfe, 0x5d, 0x28, 0xa7, 0xd2, 0x93, 0x56, 0x1d, 0xdb, 0xee, 0xc9, 0x73, 0xa6, 0xf5, 0xb0, + 0xfc, 0x07, 0x40, 0x6d, 0xed, 0x4f, 0x52, 0x6e, 0x1c, 0xfb, 0x8b, 0x1f, 0x49, 0x54, 0x8b, 0x08, 0x13, 0xb2, 0x55, + 0x0b, 0x01, 0xd3, 0xa8, 0xb3, 0xc1, 0x1c, 0x28, 0x92, 0xa2, 0x50, 0x2e, 0xaa, 0x7d, 0xde, 0x14, 0x28, 0x9a, 0x8b, + 0x79, 0x58, 0x53, 0x35, 0xfc, 0xea, 0x99, 0x39, 0xee, 0x73, 0xf9, 0x8a, 0xbe, 0x0a, 0x6b, 0x3c, 0x8f, 0xcf, 0xda, + 0xf9, 0xdb, 0xe2, 0x17, 0x6b, 0x45, 0x51, 0x03, 0x57, 0x09, 0x58, 0x37, 0xaa, 0xa6, 0x3a, 0x87, 0xe7, 0xfb, 0x58, + 0x43, 0x5d, 0x10, 0x8f, 0x7a, 0xfe, 0x54, 0x9a, 0x71, 0x95, 0xca, 0x68, 0xa7, 0x88, 0xd6, 0x66, 0x41, 0x4e, 0x11, + 0x7d, 0x9e, 0x6b, 0x20, 0x08, 0xc2, 0x07, 0x72, 0x08, 0x07, 0xbe, 0x19, 0xa0, 0xd0, 0x74, 0x0e, 0xd4, 0x4a, 0x95, + 0x99, 0x90, 0xfe, 0xd4, 0xb1, 0x09, 0x40, 0xf3, 0x54, 0xa9, 0xa0, 0xf4, 0x27, 0x16, 0xc8, 0x1b, 0xfa, 0x6f, 0xfe, + 0x06, 0x38, 0x0c, 0x35, 0x2a, 0xfa, 0x76, 0x35, 0xb2, 0x9e, 0xdf, 0x3e, 0x6b, 0x1d, 0xbd, 0xf2, 0xf2, 0xd3, 0xdc, + 0xd9, 0x7b, 0xfc, 0xe5, 0x51, 0x72, 0x13, 0x4d, 0xab, 0x8d, 0x5d, 0x20, 0xb4, 0x9e, 0x0f, 0x90, 0x43, 0x85, 0x8e, + 0xf4, 0x4b, 0x86, 0x3d, 0xa4, 0x0e, 0x49, 0xbb, 0x05, 0xd1, 0xcb, 0x76, 0x50, 0xbe, 0x9f, 0x36, 0x60, 0xaa, 0xa2, + 0xbb, 0x26, 0xd0, 0x6a, 0x78, 0xdc, 0xe8, 0xbe, 0xc9, 0xa3, 0x7b, 0x9c, 0x7b, 0x38, 0xc3, 0x0e, 0x59, 0x43, 0x1e, + 0x4a, 0x64, 0xa3, 0xbe, 0x9a, 0x0d, 0xa0, 0x68, 0xde, 0x31, 0x8f, 0xec, 0x39, 0xe3, 0xa8, 0xf3, 0x66, 0xd4, 0x3d, + 0x7c, 0x75, 0x70, 0x10, 0x8a, 0x06, 0x79, 0x8c, 0xf0, 0x92, 0x82, 0x11, 0x35, 0x38, 0x9d, 0x71, 0xc3, 0xc4, 0xc7, + 0xb9, 0x47, 0x1d, 0x17, 0x79, 0xed, 0x58, 0xab, 0x3a, 0x2b, 0x77, 0x83, 0x1b, 0x53, 0x07, 0x35, 0xbc, 0x34, 0x33, + 0xba, 0x4e, 0x0d, 0xca, 0x35, 0x0f, 0x31, 0xd8, 0x96, 0x8d, 0x8f, 0x14, 0xfd, 0xf0, 0xb8, 0xf9, 0xca, 0x9b, 0x70, + 0xcd, 0x34, 0xe9, 0x71, 0xe3, 0x31, 0xfa, 0xe1, 0xb1, 0xe7, 0xe3, 0xc7, 0x2b, 0xf6, 0xc4, 0x71, 0x23, 0x3f, 0x19, + 0xae, 0xf4, 0x27, 0x90, 0xec, 0x0b, 0xf2, 0x13, 0x60, 0x39, 0x25, 0x3f, 0x85, 0xa2, 0x99, 0x83, 0x41, 0xd7, 0x4f, + 0x61, 0x01, 0x3f, 0x32, 0xf2, 0x53, 0x08, 0xd8, 0x8e, 0xa7, 0xfa, 0x47, 0x51, 0xbd, 0xae, 0x0a, 0x6a, 0x14, 0xe3, + 0x5e, 0x56, 0xac, 0x56, 0xf2, 0xe0, 0x40, 0x98, 0x5f, 0xf4, 0x22, 0x39, 0x38, 0xc8, 0xce, 0xa7, 0x9e, 0xed, 0xad, + 0xda, 0x45, 0x5f, 0x34, 0x42, 0x61, 0xcf, 0x34, 0x8d, 0xfb, 0x33, 0xfe, 0xe4, 0x53, 0x56, 0xdd, 0x40, 0xf3, 0xb8, + 0xf3, 0xf0, 0xf4, 0x0c, 0xc3, 0xbf, 0x0f, 0xbd, 0x82, 0x3f, 0x97, 0x7c, 0x17, 0x69, 0xb3, 0xe6, 0x69, 0x85, 0x6c, + 0x17, 0x1e, 0x3e, 0x63, 0x86, 0x9a, 0xf2, 0xe0, 0x80, 0x9f, 0x7b, 0xb8, 0x8c, 0x19, 0x6a, 0x78, 0x16, 0x7b, 0x0f, + 0x4a, 0x7b, 0x32, 0xcd, 0x35, 0xc1, 0xab, 0xb6, 0x7c, 0x50, 0x0c, 0xcf, 0x95, 0xa5, 0x26, 0x7e, 0xec, 0xeb, 0x9c, + 0xb4, 0xec, 0x26, 0xeb, 0xc9, 0x66, 0x7e, 0xd1, 0xee, 0x21, 0x41, 0xf2, 0x86, 0xbc, 0xb8, 0x68, 0x63, 0x50, 0xc9, + 0xf7, 0x73, 0x22, 0x62, 0x49, 0x9c, 0x7f, 0xde, 0x32, 0x13, 0x71, 0x8e, 0xa7, 0x3c, 0x2e, 0xe5, 0xec, 0xd7, 0xce, + 0x7a, 0x5a, 0x7b, 0x4c, 0xea, 0x9e, 0x19, 0x96, 0xfd, 0xbc, 0xf4, 0xf4, 0x83, 0x4d, 0x9a, 0x0f, 0xe1, 0xd1, 0xc0, + 0x12, 0xf3, 0x98, 0x71, 0x6f, 0x63, 0x10, 0x94, 0x39, 0x6f, 0xb4, 0x21, 0x13, 0x3e, 0xd7, 0x71, 0x0e, 0x03, 0xd5, + 0x85, 0xcf, 0x81, 0x4c, 0x25, 0x95, 0x61, 0xb6, 0x6b, 0x18, 0x0a, 0x48, 0x28, 0x70, 0x41, 0x98, 0x27, 0xc1, 0xc3, + 0xed, 0x87, 0x47, 0x38, 0xea, 0xe4, 0xc2, 0x4c, 0xee, 0x3c, 0x87, 0xee, 0xe4, 0xf0, 0x5c, 0xf5, 0x90, 0x6c, 0x34, + 0x2c, 0xb7, 0x7d, 0x21, 0xf5, 0x20, 0x9a, 0xed, 0xe1, 0x05, 0xeb, 0xa1, 0xbc, 0xd9, 0x2c, 0x0d, 0x20, 0x2f, 0x5a, + 0xab, 0x55, 0x7e, 0xee, 0x1a, 0xe9, 0xbb, 0x73, 0x5c, 0xf3, 0x5d, 0x4e, 0xf0, 0xfc, 0x4d, 0x90, 0x41, 0x00, 0x54, + 0x15, 0xf8, 0x6c, 0x31, 0x0f, 0x70, 0xa0, 0xdf, 0x93, 0x83, 0xbf, 0xfa, 0x1d, 0xb0, 0x00, 0x07, 0xf6, 0x89, 0xb9, + 0x60, 0x58, 0x0d, 0x96, 0x27, 0x65, 0x74, 0x74, 0x1e, 0xdd, 0x00, 0xe3, 0xa0, 0xfe, 0x02, 0x4e, 0xbb, 0xfc, 0x1d, + 0x65, 0x36, 0x4e, 0x88, 0x74, 0xaf, 0x9e, 0xd9, 0x51, 0xad, 0x77, 0x7b, 0x66, 0x72, 0x1c, 0xb8, 0xaa, 0xf0, 0x7a, + 0xc0, 0x77, 0x9e, 0x5d, 0x6c, 0xdb, 0xd7, 0xbe, 0x97, 0x65, 0x0f, 0xc0, 0x79, 0xaf, 0xd7, 0x08, 0xff, 0x26, 0x76, + 0x3e, 0xfd, 0x1b, 0xdc, 0x88, 0xfc, 0x09, 0x55, 0x34, 0x68, 0xbc, 0xd6, 0x86, 0x6f, 0x46, 0xce, 0xea, 0x7d, 0x6b, + 0x1c, 0xec, 0xdf, 0xea, 0x1e, 0x22, 0x37, 0xd4, 0x5e, 0x29, 0x32, 0xb2, 0xaf, 0x8e, 0xd7, 0x21, 0x3c, 0xd3, 0xb7, + 0x1d, 0xf0, 0x70, 0x63, 0xa4, 0xe0, 0x4f, 0x6c, 0xf8, 0x24, 0x0a, 0x21, 0x51, 0x6b, 0x5e, 0xcc, 0x90, 0x62, 0xfa, + 0xd0, 0x1e, 0xaf, 0x6b, 0xe4, 0x73, 0xdd, 0xe3, 0xbc, 0x4e, 0x4c, 0xab, 0x6e, 0xb4, 0xd4, 0xc1, 0x36, 0x59, 0x70, + 0x56, 0xf5, 0xae, 0x25, 0x94, 0xea, 0x41, 0x37, 0xfd, 0x28, 0x67, 0xb3, 0xad, 0x5f, 0x39, 0x36, 0xcf, 0xbe, 0xe5, + 0x60, 0xc8, 0xbb, 0x5f, 0x46, 0xab, 0xba, 0x80, 0x63, 0x37, 0xf4, 0x20, 0x2b, 0xc8, 0xc5, 0xd2, 0x3e, 0xc9, 0xc6, + 0x07, 0x62, 0xb8, 0x2e, 0x1f, 0x68, 0xf3, 0xf0, 0xa0, 0x1a, 0xa9, 0x4c, 0x7c, 0xce, 0xc0, 0xbd, 0x6e, 0x58, 0xd3, + 0x0f, 0xf1, 0x7f, 0xe0, 0x80, 0xaf, 0x90, 0x34, 0x36, 0xea, 0x27, 0x78, 0x38, 0x09, 0x14, 0xde, 0xa6, 0xee, 0x27, + 0xe1, 0x7b, 0xa8, 0xd6, 0x75, 0x2a, 0xc6, 0x09, 0xb4, 0xae, 0x58, 0x29, 0x0b, 0x7b, 0xc7, 0x5d, 0x88, 0xd6, 0xb1, + 0x75, 0x18, 0xb5, 0xaf, 0x2d, 0x5d, 0xe6, 0xe0, 0xff, 0xc2, 0x45, 0xfe, 0xac, 0x80, 0xf0, 0x59, 0xbe, 0x3e, 0xed, + 0x67, 0xe1, 0x3f, 0x27, 0x3c, 0x80, 0x7b, 0xc2, 0x92, 0xe7, 0x2c, 0x9f, 0xc0, 0x86, 0x05, 0xca, 0x81, 0x42, 0xe5, + 0x58, 0xae, 0x56, 0xa1, 0xd4, 0x41, 0x15, 0x6c, 0x4c, 0x5d, 0xfb, 0x78, 0x86, 0xd6, 0xdf, 0x41, 0x5d, 0xec, 0xd4, + 0x23, 0xda, 0x84, 0x15, 0xf9, 0x97, 0x4e, 0x79, 0xe2, 0xf5, 0xb5, 0xab, 0x0f, 0x59, 0x4d, 0xb9, 0x1f, 0x6a, 0x7d, + 0xef, 0x3b, 0x3e, 0x63, 0x62, 0x01, 0xaf, 0x16, 0x61, 0x46, 0x24, 0x53, 0xee, 0x1b, 0x28, 0x08, 0x84, 0x9c, 0xe3, + 0x3e, 0x3e, 0x02, 0x5f, 0xe5, 0x68, 0x9d, 0x48, 0xdc, 0x5b, 0x18, 0x81, 0x8e, 0x55, 0x19, 0xf4, 0x03, 0x70, 0x5a, + 0x02, 0x11, 0x8a, 0x90, 0x80, 0xe5, 0x69, 0xd0, 0x0f, 0xb4, 0x8f, 0x54, 0x00, 0x56, 0x63, 0xa0, 0xe4, 0x0e, 0xf0, + 0x3c, 0xaf, 0x88, 0x98, 0x5f, 0x53, 0x79, 0x95, 0x58, 0xac, 0xcd, 0xb4, 0x8f, 0x3a, 0x15, 0x08, 0x8b, 0x64, 0x53, + 0x50, 0x56, 0x1b, 0xea, 0x02, 0x2c, 0x88, 0xc6, 0x58, 0x1e, 0xdd, 0x00, 0x37, 0xc7, 0x52, 0x5b, 0x74, 0xc9, 0xaf, + 0x41, 0x3d, 0x1d, 0x17, 0xf8, 0x46, 0x33, 0x6c, 0x69, 0x4c, 0xd7, 0x84, 0xe3, 0x84, 0x14, 0x11, 0xbd, 0x83, 0xa0, + 0x12, 0x33, 0x9e, 0xc7, 0x19, 0x9e, 0xd1, 0xbb, 0x78, 0x8a, 0x67, 0x3c, 0x7f, 0x62, 0x96, 0x3d, 0x4e, 0x21, 0xc9, + 0x7d, 0x2c, 0xd6, 0x44, 0xbf, 0x89, 0xf5, 0xbb, 0x64, 0xc5, 0x63, 0xe0, 0x55, 0x64, 0x88, 0x7a, 0xa9, 0xb6, 0x29, + 0x67, 0xaa, 0x32, 0x5e, 0x7f, 0xad, 0x42, 0x8a, 0x13, 0x9c, 0xa1, 0x28, 0x13, 0x98, 0xf5, 0x65, 0xfc, 0x1a, 0x02, + 0x4a, 0x27, 0xd8, 0xbc, 0xa7, 0xc5, 0xef, 0x58, 0xf6, 0x4c, 0x14, 0xef, 0xf5, 0x96, 0xcf, 0x10, 0x14, 0x02, 0x17, + 0x15, 0xd9, 0x84, 0xdb, 0xbd, 0x45, 0x5f, 0x54, 0x4d, 0xd1, 0x3b, 0xd3, 0x94, 0x1d, 0xe2, 0x14, 0x22, 0xf1, 0x46, + 0x53, 0xde, 0x68, 0x63, 0xd6, 0x6f, 0x7d, 0xa7, 0xd1, 0x29, 0x2a, 0x4b, 0x22, 0x0c, 0x4f, 0x04, 0x37, 0xf3, 0x58, + 0x10, 0xd9, 0xcc, 0xad, 0x84, 0xb7, 0xd4, 0xc0, 0x8e, 0x73, 0x9c, 0x88, 0x45, 0xae, 0x62, 0xe1, 0xe1, 0x0d, 0xad, + 0x36, 0xd7, 0xf2, 0xce, 0x40, 0x4c, 0xe1, 0x7b, 0xf3, 0x83, 0xe1, 0x1b, 0xad, 0xe2, 0x7f, 0x0b, 0x86, 0x3d, 0x32, + 0x96, 0x00, 0x3f, 0x30, 0x9c, 0x05, 0xc8, 0x19, 0x7e, 0xf2, 0x0e, 0xc0, 0x67, 0x58, 0xc8, 0x7b, 0x48, 0x65, 0x3a, + 0xf5, 0x1e, 0x52, 0x19, 0xa4, 0x6a, 0x57, 0xf2, 0x7d, 0x59, 0x29, 0x8b, 0xfc, 0x06, 0x49, 0x8e, 0x4b, 0x75, 0xb0, + 0x20, 0x32, 0x82, 0x76, 0xb5, 0x28, 0x37, 0xe3, 0x39, 0xc4, 0x14, 0x84, 0xc6, 0xcd, 0x37, 0xbd, 0x83, 0xef, 0x7b, + 0x93, 0xcf, 0x5c, 0xfe, 0xbd, 0xc9, 0xd7, 0x1d, 0x39, 0x8c, 0xaf, 0xdf, 0x76, 0x6a, 0xcb, 0x78, 0x61, 0xb1, 0xf6, + 0x43, 0xf9, 0x82, 0x4b, 0x4b, 0xbf, 0x94, 0x4d, 0xda, 0x78, 0xe2, 0x21, 0x65, 0xb3, 0xe2, 0xe1, 0x3a, 0xb8, 0xdd, + 0x3a, 0x0c, 0x79, 0x93, 0xb4, 0x11, 0x3a, 0xb4, 0xc2, 0x55, 0x1e, 0x6a, 0xc9, 0xe9, 0xf0, 0xf1, 0x11, 0xdc, 0xbd, + 0xcc, 0xf2, 0x0d, 0x5f, 0x29, 0x53, 0xad, 0xd9, 0x6e, 0x1d, 0xf2, 0x9d, 0x55, 0x1a, 0x6d, 0x3c, 0x63, 0x64, 0x09, + 0xce, 0x65, 0xb4, 0x30, 0xaa, 0x06, 0xf0, 0x21, 0x7d, 0x91, 0xff, 0xb6, 0xa0, 0xa9, 0xfe, 0x3e, 0x34, 0x29, 0xaf, + 0x17, 0xca, 0x25, 0x35, 0x39, 0x0c, 0xa2, 0x83, 0x6c, 0x49, 0x2f, 0x27, 0xe4, 0x47, 0x24, 0xea, 0xa2, 0xf3, 0x76, + 0x3f, 0xea, 0x1e, 0xf2, 0x43, 0x1e, 0x03, 0x0f, 0x1b, 0x36, 0x5d, 0x85, 0x66, 0xdb, 0xd5, 0xb9, 0x5a, 0x8c, 0x78, + 0x62, 0x9b, 0xaf, 0x3a, 0x28, 0x53, 0xcd, 0x1c, 0x21, 0x0b, 0x50, 0xcc, 0xf5, 0xe2, 0x65, 0xd7, 0xbb, 0x39, 0xe4, + 0x31, 0xf4, 0x03, 0xb5, 0x3a, 0xa6, 0x56, 0x39, 0xb8, 0xdf, 0x16, 0x80, 0x60, 0xae, 0xa3, 0xda, 0x5c, 0x4c, 0x7a, + 0x33, 0xac, 0x3a, 0x3b, 0xe4, 0xd5, 0x08, 0xfd, 0x32, 0xdb, 0xfd, 0xb9, 0xa9, 0x55, 0x5d, 0x1e, 0x7a, 0x10, 0xf9, + 0x6d, 0xc1, 0x73, 0xbf, 0x53, 0xbf, 0x5b, 0x9b, 0xe3, 0x77, 0x5a, 0x9f, 0xa5, 0x57, 0x64, 0xbb, 0xd7, 0xad, 0x99, + 0xd6, 0x67, 0x7b, 0x0d, 0x3e, 0x82, 0x30, 0x29, 0xbd, 0xd2, 0x89, 0x90, 0x21, 0x3f, 0xfc, 0x80, 0x6c, 0xeb, 0xaf, + 0x17, 0xca, 0xe5, 0x97, 0x88, 0x00, 0xd9, 0x55, 0xd7, 0x65, 0x75, 0xe8, 0xa3, 0x6c, 0xe2, 0xd5, 0x21, 0xf7, 0x56, + 0xee, 0xe9, 0xdd, 0x5c, 0xc4, 0x0e, 0x5f, 0xfb, 0xad, 0x78, 0x0b, 0x39, 0x81, 0x78, 0xd8, 0xee, 0xfc, 0xb2, 0x20, + 0x67, 0x37, 0xb7, 0x50, 0xd2, 0x9f, 0xb8, 0x2b, 0xfd, 0x81, 0x99, 0xeb, 0x06, 0x7e, 0x1e, 0x75, 0x61, 0xea, 0x9b, + 0x3d, 0x1c, 0x76, 0xa0, 0x0f, 0x0d, 0x87, 0xcd, 0x06, 0x5d, 0x66, 0x05, 0x91, 0x2b, 0x5e, 0x18, 0x3c, 0xbb, 0x20, + 0xed, 0x3e, 0x8f, 0xed, 0x66, 0xd2, 0xa2, 0x51, 0xbb, 0xc9, 0xbd, 0x99, 0x01, 0x7e, 0xd9, 0xb2, 0x7e, 0x11, 0xb7, + 0x4e, 0x1e, 0x94, 0x5c, 0xb1, 0x6a, 0x7d, 0x2a, 0x78, 0xd5, 0x1b, 0x8e, 0x37, 0xd3, 0xdd, 0xba, 0xc1, 0xed, 0xae, + 0x83, 0x27, 0xbc, 0xc0, 0x62, 0xd0, 0xda, 0x4d, 0x7c, 0x02, 0x1c, 0x50, 0xd4, 0x7a, 0xd8, 0x05, 0x17, 0xca, 0x12, + 0x96, 0xdb, 0xe5, 0x66, 0x5b, 0xe5, 0x0c, 0x1c, 0x4d, 0x49, 0x8f, 0x3b, 0xd8, 0x84, 0x28, 0x74, 0x70, 0xd8, 0xc1, + 0x51, 0xbb, 0xdd, 0xe9, 0xe2, 0xe8, 0xa4, 0x0b, 0x03, 0x6d, 0x44, 0xdd, 0xc3, 0x59, 0x6e, 0x00, 0xe8, 0xe5, 0xac, + 0x6d, 0xbb, 0x8f, 0x21, 0x5a, 0x93, 0x2f, 0x5e, 0xf3, 0xc3, 0x30, 0x6c, 0x47, 0x0f, 0x5b, 0xed, 0xee, 0x59, 0x03, + 0x00, 0xd4, 0xb4, 0x1f, 0xb6, 0xc6, 0xeb, 0x85, 0xaa, 0x57, 0x29, 0x11, 0xbe, 0x5e, 0xad, 0xe1, 0xaa, 0x35, 0xda, + 0xeb, 0x6a, 0x0a, 0xae, 0xaa, 0x15, 0xce, 0x4d, 0x11, 0xa7, 0xb4, 0xf1, 0xb7, 0x45, 0x68, 0x06, 0x12, 0x82, 0x74, + 0x1e, 0x75, 0x3b, 0x5d, 0x64, 0xc6, 0xa2, 0x2c, 0x7e, 0x94, 0xfb, 0x64, 0xab, 0x48, 0x43, 0x01, 0x92, 0x94, 0xb3, + 0x13, 0x0b, 0x90, 0xa8, 0x39, 0xb9, 0x68, 0x37, 0x67, 0x2c, 0x72, 0x13, 0xd0, 0xa9, 0xb0, 0x9c, 0xe5, 0x2a, 0xd8, + 0x24, 0x0f, 0x10, 0xe7, 0x60, 0x5c, 0xf4, 0xb0, 0xdb, 0x7f, 0x18, 0x75, 0x4f, 0x3b, 0x86, 0xe8, 0xf1, 0xf3, 0x4e, + 0x2d, 0x4d, 0x4f, 0x3d, 0xea, 0xea, 0x34, 0xe8, 0x3a, 0x7a, 0xd8, 0x85, 0x32, 0x36, 0xc5, 0x2f, 0x05, 0x51, 0x26, + 0xaa, 0x62, 0x10, 0x5a, 0x22, 0xae, 0xe5, 0x9e, 0xd6, 0xb2, 0xcf, 0x4e, 0x8e, 0x1f, 0x76, 0x7d, 0xa8, 0x95, 0xb3, + 0xd0, 0x0b, 0x6d, 0x27, 0xe2, 0x66, 0x07, 0x4b, 0x8b, 0x0e, 0xa3, 0x6e, 0xbc, 0x35, 0x41, 0xb3, 0x69, 0x0e, 0x35, + 0x0e, 0x78, 0x0a, 0xc7, 0x4b, 0x69, 0xf5, 0x25, 0xde, 0xfd, 0x58, 0x65, 0x68, 0xe2, 0x2b, 0x9c, 0xdd, 0x3d, 0xa5, + 0xf7, 0x90, 0xa4, 0x7f, 0x55, 0x79, 0x45, 0xf3, 0xaf, 0x54, 0xbe, 0xa1, 0x10, 0x3a, 0x23, 0x1f, 0x06, 0x36, 0xb0, + 0x77, 0x3d, 0xf7, 0x27, 0x70, 0x11, 0x66, 0x39, 0x5c, 0x68, 0x3a, 0x25, 0x68, 0xc5, 0x0b, 0x8c, 0x7c, 0x56, 0x57, + 0x0f, 0xab, 0xcf, 0x63, 0x6b, 0x52, 0xe0, 0xeb, 0xb6, 0x9e, 0xf3, 0xef, 0x95, 0x8b, 0xca, 0xab, 0xec, 0xa8, 0x8b, + 0x22, 0x7b, 0x59, 0x1e, 0xb5, 0x51, 0xe4, 0x99, 0x90, 0xd8, 0x23, 0x39, 0x49, 0xc8, 0x20, 0xb8, 0x0b, 0x70, 0x70, + 0x1f, 0xe0, 0xc0, 0xf8, 0x30, 0x7f, 0x00, 0x37, 0xf2, 0x00, 0x07, 0xc6, 0x95, 0x39, 0xc0, 0x81, 0x62, 0x39, 0x44, + 0xd4, 0x0a, 0x86, 0x38, 0x83, 0xd2, 0xda, 0xb3, 0xba, 0x2c, 0x7d, 0xe5, 0xbe, 0x4a, 0xd7, 0x6b, 0x93, 0xe2, 0x49, + 0x99, 0x53, 0xbd, 0x43, 0xcd, 0x8b, 0xd0, 0x01, 0x75, 0xcc, 0x7a, 0x80, 0x41, 0x00, 0xa1, 0xf7, 0xee, 0x45, 0xb9, + 0x2a, 0x18, 0x07, 0x3b, 0x86, 0x95, 0x06, 0x9f, 0xf3, 0xc0, 0x3f, 0xc3, 0x02, 0x3c, 0xce, 0x5d, 0x61, 0x10, 0x2b, + 0xdc, 0xef, 0x4c, 0x88, 0xb9, 0xfb, 0xad, 0x44, 0xf9, 0x0b, 0xde, 0x21, 0xb1, 0x16, 0x2d, 0x60, 0xb9, 0x65, 0x62, + 0xff, 0x8c, 0x58, 0x7d, 0x04, 0x37, 0x63, 0x1b, 0x9f, 0x0d, 0x24, 0xc2, 0x1b, 0x2d, 0x50, 0x39, 0xf9, 0xf0, 0xc6, + 0xc4, 0x0a, 0xd2, 0x9f, 0x10, 0x2c, 0x0c, 0xe2, 0x01, 0x0b, 0xb8, 0xd0, 0x98, 0x14, 0x4c, 0xca, 0xc0, 0x04, 0xd1, + 0x0b, 0x44, 0xee, 0x5e, 0x45, 0x74, 0x29, 0xe3, 0x3c, 0xd0, 0x1d, 0xd6, 0x67, 0x6b, 0xc4, 0xe1, 0x4c, 0x14, 0x32, + 0x36, 0x4f, 0xa4, 0x38, 0x30, 0xce, 0xcb, 0xf7, 0x07, 0xe3, 0x2c, 0x59, 0x63, 0x73, 0x87, 0x5d, 0x16, 0xb2, 0x57, + 0xda, 0x7e, 0xa9, 0x24, 0x59, 0x7f, 0x6b, 0x42, 0xb2, 0x36, 0x23, 0x6f, 0xa2, 0xd5, 0x80, 0xaa, 0x48, 0x1a, 0x50, + 0xd8, 0x44, 0x63, 0x89, 0x97, 0x65, 0xc9, 0x78, 0x59, 0x2e, 0xc3, 0x49, 0xab, 0xb5, 0x5e, 0xe3, 0x82, 0xe9, 0xa8, + 0x30, 0x3b, 0x4b, 0x40, 0xbe, 0x9c, 0x8a, 0x5b, 0x2f, 0x57, 0xc6, 0xe5, 0x2c, 0xf5, 0x12, 0x05, 0x9e, 0x11, 0x6c, + 0xb0, 0xc6, 0x5f, 0xb9, 0xe4, 0x00, 0x4f, 0x3b, 0xbb, 0x91, 0x10, 0x19, 0xa3, 0x10, 0x3c, 0xcc, 0x6b, 0x72, 0x8d, + 0xa7, 0x3c, 0x65, 0xbb, 0xdb, 0x04, 0x33, 0xe6, 0x7f, 0xaf, 0x45, 0x87, 0x40, 0x86, 0xdd, 0xd3, 0xa8, 0x03, 0x8b, + 0xb8, 0x82, 0x0e, 0x7c, 0x19, 0x3c, 0xf5, 0x71, 0x33, 0xa3, 0xf7, 0x62, 0xa1, 0x00, 0x2e, 0x0b, 0x25, 0xde, 0xd8, + 0xb8, 0x07, 0xfb, 0x2d, 0xec, 0x42, 0x20, 0x2c, 0x21, 0x64, 0x40, 0x0b, 0x9b, 0x10, 0x15, 0x2d, 0x3c, 0x12, 0x4a, + 0x89, 0x59, 0xdc, 0xc2, 0x3a, 0x5e, 0x44, 0x6b, 0x5d, 0x06, 0xf5, 0xba, 0xc9, 0xdd, 0x5b, 0x92, 0xd5, 0x26, 0x58, + 0x58, 0xe9, 0x50, 0x11, 0xe5, 0xdd, 0x1e, 0x32, 0xc2, 0x1b, 0x3f, 0x5f, 0xbf, 0x7e, 0x65, 0x43, 0x36, 0xf3, 0x31, + 0xb8, 0x6c, 0x5a, 0xd5, 0xd8, 0x8d, 0x7e, 0x84, 0x29, 0xac, 0x14, 0xa5, 0x46, 0x38, 0x85, 0x96, 0x5f, 0xe4, 0x2a, + 0x8b, 0xcc, 0xe5, 0xc5, 0x33, 0x51, 0xcc, 0xa8, 0xb9, 0x31, 0xc2, 0x37, 0xb9, 0x7d, 0x75, 0x5d, 0x3f, 0xec, 0x52, + 0x4d, 0xf2, 0xdd, 0xe6, 0x55, 0xc4, 0x22, 0xd1, 0xf2, 0x2b, 0x68, 0x03, 0x74, 0xe5, 0xf2, 0xd1, 0xdc, 0x82, 0xd8, + 0xc0, 0xf7, 0x1e, 0x79, 0x79, 0x6b, 0xa8, 0x4b, 0x10, 0x34, 0xb8, 0xc6, 0x4f, 0x56, 0xf0, 0xc4, 0xbb, 0x2e, 0xd4, + 0xec, 0x91, 0x15, 0x2f, 0x82, 0x56, 0x50, 0x7f, 0x74, 0x56, 0xab, 0x12, 0x5c, 0xd0, 0xd4, 0x28, 0x13, 0x20, 0x7a, + 0x94, 0xef, 0xdb, 0x71, 0x10, 0x4d, 0xdc, 0xdd, 0xf3, 0x45, 0xdb, 0xd1, 0xd9, 0xac, 0x52, 0x27, 0x96, 0x57, 0x26, + 0xe0, 0xe1, 0x68, 0x5e, 0x90, 0x41, 0xd8, 0x4b, 0x64, 0xa5, 0xf6, 0xd0, 0xe5, 0xa2, 0x5e, 0x98, 0x9d, 0xb7, 0x59, + 0xf3, 0x64, 0xb5, 0xca, 0x2e, 0xda, 0xac, 0xdd, 0x35, 0xef, 0xcd, 0x05, 0x32, 0x01, 0x9a, 0xcb, 0xc7, 0x3c, 0x09, + 0x40, 0x3b, 0x3b, 0x4e, 0x74, 0x38, 0x05, 0x17, 0x21, 0x99, 0x2c, 0x54, 0xd5, 0x97, 0x00, 0xe3, 0x52, 0x62, 0xf4, + 0xf8, 0x05, 0xea, 0xb7, 0xe3, 0x6d, 0x57, 0xe9, 0x66, 0xfb, 0xd0, 0xbb, 0x70, 0x29, 0x10, 0xee, 0x40, 0xc8, 0x03, + 0xd0, 0xef, 0x2e, 0x73, 0x30, 0x0d, 0x02, 0x54, 0xce, 0x41, 0xa4, 0xe5, 0xb3, 0xc5, 0xec, 0x59, 0x41, 0xf5, 0x32, + 0x3c, 0xe1, 0x13, 0xae, 0x64, 0x4c, 0x41, 0xba, 0xdd, 0x95, 0xbe, 0xde, 0x2d, 0x41, 0x25, 0xb5, 0xc0, 0xb3, 0x91, + 0xe2, 0xc9, 0x17, 0x69, 0x17, 0x0e, 0x61, 0xbd, 0xb2, 0x12, 0x27, 0x68, 0x8d, 0x33, 0x31, 0xa1, 0x05, 0x57, 0xd3, + 0xd9, 0xbf, 0xb5, 0x3a, 0x6c, 0xa0, 0x86, 0xfa, 0xc2, 0x0a, 0x40, 0x42, 0xf3, 0x74, 0xb5, 0xe2, 0x47, 0xdf, 0xbf, + 0x4f, 0x72, 0x3e, 0xe1, 0x6d, 0xdc, 0xc1, 0xc7, 0xb8, 0x8b, 0xdb, 0x2d, 0xdc, 0xee, 0xc2, 0xd5, 0x7d, 0x92, 0x2d, + 0x52, 0x06, 0xf6, 0xb1, 0xab, 0x95, 0xba, 0x88, 0xce, 0x0e, 0xcb, 0x70, 0xfb, 0xaa, 0x88, 0x2c, 0xba, 0x78, 0x51, + 0xdf, 0x6d, 0xb8, 0xbc, 0x20, 0xf0, 0x63, 0xb5, 0x8d, 0x7d, 0xd5, 0x49, 0xa9, 0x5f, 0xb8, 0x38, 0xee, 0x83, 0x3d, + 0xb7, 0x59, 0xd9, 0x26, 0x98, 0x7d, 0x9b, 0x9f, 0x71, 0xf5, 0xb3, 0xa9, 0x4a, 0xc4, 0x70, 0xd0, 0xab, 0xd0, 0x03, + 0x5d, 0x90, 0xf6, 0xc1, 0x01, 0x58, 0x1d, 0x79, 0xb3, 0xe1, 0x26, 0xfa, 0x01, 0x6f, 0xd6, 0xd2, 0x20, 0x58, 0x01, + 0x18, 0x77, 0xbe, 0xe1, 0x64, 0x69, 0x60, 0xab, 0x80, 0x0a, 0xab, 0xc2, 0x0f, 0x28, 0xe7, 0x93, 0x0a, 0x2d, 0x44, + 0xc3, 0x11, 0x66, 0x23, 0x9d, 0xec, 0xb7, 0xb0, 0x18, 0x8f, 0x25, 0x53, 0x70, 0x74, 0x14, 0xec, 0x2b, 0x2b, 0xa4, + 0x3e, 0x45, 0x46, 0x6c, 0xc2, 0xf3, 0x4b, 0xf5, 0x89, 0x15, 0x42, 0x7f, 0x6a, 0x0d, 0x46, 0x1c, 0xe8, 0x55, 0x0c, + 0x70, 0x92, 0xf1, 0x39, 0x54, 0x9d, 0x14, 0xe0, 0xf4, 0x03, 0x7f, 0x79, 0x1a, 0xfb, 0x6d, 0x02, 0xf9, 0xfa, 0x60, + 0xc2, 0xba, 0xe0, 0xb4, 0xa0, 0xb7, 0xaf, 0xf3, 0x2b, 0xd8, 0x51, 0x97, 0x05, 0xa3, 0x90, 0x0d, 0x49, 0xef, 0xa0, + 0x29, 0xf8, 0x80, 0x36, 0x5f, 0x6a, 0xc0, 0xc5, 0x67, 0xfa, 0xc3, 0x54, 0x74, 0x41, 0x0b, 0xa3, 0xb2, 0x2d, 0x9d, + 0xa9, 0x4f, 0xe9, 0x2a, 0xd3, 0x84, 0x85, 0x2a, 0xa7, 0xb0, 0xc6, 0x36, 0xea, 0x89, 0x3f, 0x98, 0x94, 0xca, 0x69, + 0x3c, 0x18, 0xea, 0xbf, 0xaf, 0x4d, 0xc9, 0x16, 0xb6, 0x41, 0x67, 0xd6, 0x58, 0xbf, 0x18, 0xea, 0x95, 0x6f, 0x63, + 0xb8, 0x87, 0x85, 0x67, 0x2b, 0x6b, 0xe4, 0xf3, 0xc4, 0x91, 0xcd, 0x93, 0xf5, 0x5a, 0x0f, 0x44, 0xc6, 0xa0, 0x07, + 0x7a, 0xeb, 0xb6, 0x4d, 0x0b, 0xb6, 0x47, 0xf9, 0xd5, 0x6d, 0xe1, 0x19, 0x87, 0x57, 0x38, 0x5d, 0x7b, 0xd7, 0xaa, + 0x10, 0x5f, 0x2c, 0x48, 0x5a, 0x5e, 0x8a, 0x99, 0x8e, 0xd7, 0xd9, 0x31, 0xf6, 0x46, 0x0e, 0xf4, 0xfc, 0xfa, 0x8b, + 0x81, 0xb5, 0xfb, 0xfd, 0xa6, 0x2c, 0x98, 0xd1, 0x11, 0xcb, 0xca, 0x09, 0x25, 0xee, 0xfc, 0x7c, 0xc3, 0xa3, 0x0a, + 0x15, 0xec, 0xf3, 0x55, 0xb0, 0xa7, 0x4d, 0x84, 0xcb, 0x19, 0xfd, 0xcb, 0xfc, 0x30, 0x81, 0x75, 0x4a, 0x2d, 0x5b, + 0x52, 0x08, 0x29, 0x2f, 0x4d, 0x9a, 0x39, 0x7a, 0xe0, 0x88, 0x7c, 0x09, 0x5d, 0x00, 0xaf, 0x9f, 0x16, 0x62, 0xae, + 0x11, 0xc1, 0xfe, 0xb6, 0xe3, 0xd6, 0xbe, 0x02, 0xe0, 0xed, 0xb0, 0x57, 0xfd, 0xd3, 0x02, 0xf6, 0x37, 0x28, 0x4b, + 0xba, 0xf1, 0x76, 0xcc, 0xf1, 0x5f, 0x08, 0x08, 0x97, 0x6e, 0xf0, 0x30, 0xb2, 0xe8, 0x54, 0xb2, 0x66, 0xe5, 0xcf, + 0xad, 0x92, 0x80, 0x61, 0xf5, 0x82, 0x3e, 0x1b, 0xb7, 0x55, 0xdc, 0x64, 0xfe, 0x07, 0x15, 0x34, 0x16, 0x7c, 0x6b, + 0x24, 0x15, 0xcb, 0xe2, 0xb6, 0x4f, 0x9d, 0xff, 0xaa, 0x73, 0x5c, 0xfb, 0xaa, 0xf6, 0x44, 0xe6, 0x48, 0x87, 0x27, + 0x0e, 0xd0, 0xc1, 0xc1, 0x46, 0x06, 0x1d, 0x03, 0xe0, 0x91, 0x65, 0xbf, 0xdc, 0xf2, 0x39, 0x76, 0x4c, 0x6b, 0x1e, + 0x8b, 0xc0, 0x67, 0xee, 0x1c, 0x37, 0x67, 0x26, 0xf2, 0x84, 0xca, 0xa9, 0x2b, 0x0c, 0x70, 0x7c, 0xbc, 0x95, 0x0a, + 0xf8, 0x1e, 0xac, 0x77, 0x4c, 0x60, 0x83, 0xdf, 0x32, 0x93, 0xda, 0x55, 0xd0, 0x2d, 0xd0, 0x72, 0x17, 0x53, 0xb9, + 0xb1, 0xc0, 0xc1, 0xe6, 0x44, 0x76, 0x0e, 0x7d, 0xa3, 0x4e, 0xc9, 0x7a, 0x3c, 0xd9, 0x6d, 0xf4, 0x95, 0xcb, 0x5d, + 0xc9, 0x15, 0x6d, 0x1b, 0xb1, 0xea, 0x99, 0x5c, 0x55, 0x99, 0x3a, 0x55, 0xd7, 0xbc, 0x95, 0xa5, 0x4d, 0x69, 0x97, + 0x64, 0xee, 0xb6, 0x98, 0x7f, 0x15, 0xde, 0x68, 0x94, 0x17, 0xa1, 0x60, 0x8f, 0x25, 0x87, 0x3d, 0x4e, 0xe0, 0x7a, + 0x61, 0xb5, 0x0a, 0xe1, 0xcf, 0xae, 0x31, 0xec, 0x32, 0x5d, 0xfa, 0xc0, 0x37, 0xf8, 0x15, 0x2f, 0x52, 0xaf, 0xb5, + 0x83, 0x04, 0xeb, 0x2e, 0x3b, 0x68, 0x38, 0x4e, 0xdc, 0x17, 0xbc, 0x13, 0xad, 0x9c, 0xcb, 0xc1, 0x24, 0xf9, 0xc6, + 0xdb, 0x72, 0x25, 0x6b, 0x59, 0x0b, 0xf3, 0xbe, 0x21, 0xc1, 0x10, 0xb3, 0x29, 0xad, 0xe3, 0x56, 0xd4, 0x46, 0x81, + 0x2d, 0x56, 0xa1, 0xff, 0xb7, 0x8a, 0x24, 0x26, 0xf3, 0xbf, 0x4e, 0x4f, 0x4f, 0x6d, 0x8a, 0xb5, 0xf9, 0x93, 0xda, + 0x03, 0x4e, 0x27, 0xb0, 0xaf, 0x3c, 0x61, 0x5a, 0x87, 0xfc, 0x16, 0x86, 0x42, 0x24, 0xb9, 0x70, 0xec, 0x12, 0x44, + 0xe5, 0x02, 0xca, 0x03, 0xec, 0xdf, 0x93, 0x8d, 0x72, 0xee, 0x9d, 0x24, 0x17, 0x47, 0xb8, 0x6c, 0x90, 0x7d, 0xd5, + 0x9f, 0x03, 0x63, 0x26, 0x03, 0x4f, 0x03, 0x04, 0xd8, 0xfc, 0xd6, 0x2c, 0xad, 0xb5, 0x94, 0xc1, 0x81, 0x12, 0x8b, + 0x64, 0x6a, 0x34, 0xff, 0xf6, 0x43, 0x97, 0xb5, 0x6f, 0xec, 0x40, 0x50, 0x2e, 0xb2, 0xb4, 0xe1, 0x30, 0x83, 0x1f, + 0xcb, 0xc8, 0x97, 0x7b, 0xaf, 0xd8, 0x82, 0xfd, 0x88, 0xf7, 0xaa, 0x14, 0xf8, 0xb8, 0x2c, 0x38, 0xcd, 0x7e, 0xc4, + 0x7b, 0x55, 0x04, 0x4c, 0x70, 0x85, 0xd4, 0x41, 0x24, 0xb1, 0x7e, 0x4f, 0x3a, 0x0e, 0x72, 0xa0, 0xa0, 0x59, 0xa0, + 0x0f, 0xb2, 0xe7, 0x36, 0x68, 0x62, 0xd4, 0xc1, 0x36, 0xee, 0x97, 0x09, 0x85, 0x6a, 0x22, 0x88, 0x43, 0x20, 0xb9, + 0x72, 0x36, 0xfa, 0xeb, 0xf1, 0xc6, 0x82, 0x68, 0x65, 0x32, 0xb9, 0x78, 0xce, 0xc3, 0x7c, 0x73, 0xb1, 0x90, 0x5f, + 0xcd, 0x5b, 0xa0, 0x5a, 0x95, 0x2a, 0xdd, 0x2f, 0xbe, 0x5d, 0x30, 0xf1, 0x8a, 0xe8, 0xad, 0x77, 0x79, 0x07, 0xcf, + 0x9d, 0xdf, 0x05, 0x2e, 0x09, 0x9e, 0x04, 0xd7, 0x98, 0xea, 0x5e, 0x80, 0x07, 0x42, 0xcf, 0xa4, 0x0a, 0xb0, 0xce, + 0x93, 0x10, 0x49, 0x6c, 0xbf, 0x85, 0x2d, 0x6b, 0xf4, 0x22, 0x77, 0x42, 0x0a, 0x9c, 0xab, 0xba, 0x89, 0x19, 0xe5, + 0x3a, 0xba, 0xd8, 0xa5, 0x9c, 0xb3, 0x44, 0x19, 0x04, 0xd8, 0xb7, 0x68, 0x28, 0xf2, 0xe7, 0x1a, 0x14, 0xfa, 0x2d, + 0x6b, 0x9b, 0x72, 0x05, 0x8b, 0xe7, 0xa5, 0x00, 0x51, 0xe3, 0xf9, 0xa4, 0xac, 0x33, 0xcf, 0x16, 0x13, 0x9e, 0x57, + 0xc8, 0x50, 0x30, 0x39, 0x17, 0x39, 0x3c, 0x25, 0x51, 0x16, 0xd1, 0x74, 0xa8, 0x86, 0xef, 0x86, 0x84, 0x95, 0x75, + 0xf4, 0x31, 0xc5, 0xf3, 0xaa, 0x06, 0x30, 0x17, 0x97, 0xfe, 0x9b, 0xf3, 0xf2, 0x75, 0xfe, 0x4e, 0xcc, 0xab, 0x7c, + 0x47, 0xe3, 0x5c, 0xc4, 0x76, 0x6b, 0x37, 0x8c, 0xd6, 0xfa, 0xb5, 0x27, 0x6f, 0xfb, 0x7e, 0xe0, 0xd5, 0x0b, 0x68, + 0x6b, 0xfd, 0x5e, 0x54, 0x99, 0x35, 0x62, 0xe5, 0xe3, 0x08, 0x55, 0x7b, 0xf5, 0xaa, 0xb9, 0xad, 0x08, 0x50, 0x29, + 0x78, 0xba, 0x95, 0xff, 0x44, 0x99, 0x7c, 0x73, 0x0e, 0x95, 0xe1, 0x81, 0x1c, 0x19, 0xaa, 0x7a, 0xc0, 0x45, 0xf9, + 0xa1, 0x9f, 0xbe, 0x0a, 0x74, 0xe0, 0xdc, 0x5d, 0x17, 0xc8, 0x9c, 0xc9, 0x50, 0xe0, 0xe5, 0x80, 0x0e, 0x63, 0x23, + 0x0f, 0xc5, 0x02, 0x6c, 0x7b, 0x6e, 0x0b, 0xae, 0x5c, 0x84, 0x5e, 0x3c, 0x60, 0xc3, 0x78, 0x59, 0x8f, 0xe2, 0x6b, + 0xe2, 0x08, 0x3b, 0x73, 0x4e, 0x1d, 0xf6, 0x96, 0x0e, 0x71, 0x46, 0xc0, 0xf6, 0xd8, 0xb1, 0xa7, 0x6f, 0xc2, 0x04, + 0xf5, 0xeb, 0x1c, 0xfe, 0x72, 0x8d, 0x33, 0x9c, 0xa0, 0xf8, 0x32, 0x84, 0x0b, 0xac, 0x35, 0x06, 0xf0, 0x25, 0x86, + 0x54, 0x81, 0x47, 0x6a, 0xa2, 0x25, 0x56, 0x7b, 0x11, 0x88, 0x96, 0xca, 0xbf, 0x1d, 0x67, 0x2e, 0x0e, 0xb6, 0xe6, + 0x5e, 0x9f, 0x69, 0xe1, 0x70, 0x92, 0x84, 0xb5, 0x73, 0x86, 0x93, 0x8b, 0x7d, 0x5e, 0x3b, 0x31, 0xc1, 0xda, 0xdb, + 0x3f, 0x55, 0x40, 0x8f, 0x06, 0xa7, 0x8a, 0xa1, 0x21, 0x10, 0x33, 0x01, 0xbc, 0x99, 0xfd, 0xa3, 0xcd, 0xc3, 0xf9, + 0x60, 0x8d, 0xbd, 0xaf, 0xb8, 0xd6, 0xd5, 0xa6, 0x12, 0x65, 0xbd, 0xc6, 0x83, 0x69, 0x82, 0xd3, 0x04, 0xcf, 0x93, + 0xa1, 0x77, 0xdc, 0xcc, 0x12, 0xdf, 0xa4, 0x6b, 0xb5, 0x7a, 0x6a, 0xcd, 0x08, 0x91, 0xf9, 0x69, 0xe8, 0x0f, 0xea, + 0x03, 0xc2, 0xc7, 0x90, 0x05, 0xb4, 0xa4, 0x6f, 0xff, 0x36, 0xcc, 0x33, 0xd9, 0xa8, 0x11, 0xf2, 0xc8, 0x90, 0x91, + 0xbe, 0xfb, 0x51, 0x66, 0x99, 0xd6, 0x1a, 0xc1, 0xfc, 0x6e, 0x2f, 0x68, 0xb8, 0xf6, 0x3c, 0x2d, 0x5b, 0x69, 0xb6, + 0x03, 0x88, 0x62, 0x8c, 0x93, 0x94, 0xb7, 0x46, 0x62, 0xb5, 0x0a, 0x4d, 0x0a, 0xe1, 0xd1, 0x8c, 0x51, 0xb9, 0x28, + 0xf4, 0xcb, 0x71, 0x61, 0x8e, 0x22, 0xcd, 0xef, 0x62, 0x6b, 0x23, 0x9a, 0x83, 0xdb, 0x23, 0x18, 0x6e, 0x84, 0x92, + 0x88, 0x9a, 0xc8, 0x3d, 0x4a, 0x2a, 0xcb, 0x20, 0x49, 0xa4, 0x16, 0xf9, 0xcd, 0x75, 0xa9, 0x39, 0x0c, 0xec, 0x1f, + 0xed, 0x0b, 0x08, 0x37, 0x6f, 0x13, 0x5a, 0x8c, 0xe8, 0x04, 0xd8, 0x58, 0x88, 0x43, 0xb8, 0x95, 0xb0, 0x5a, 0x0d, + 0x86, 0x3d, 0x43, 0x9e, 0xed, 0xcb, 0x79, 0x65, 0x43, 0xbb, 0x1b, 0x80, 0xab, 0x6e, 0x43, 0xcd, 0x95, 0xd6, 0xfd, + 0x50, 0xfd, 0xb8, 0x17, 0xb7, 0x49, 0xf6, 0x7d, 0x8e, 0xea, 0x09, 0xee, 0x9a, 0x05, 0xb8, 0x0e, 0x5d, 0x85, 0x53, + 0xbc, 0x30, 0x36, 0x9c, 0xfa, 0x25, 0x27, 0xaa, 0x1f, 0x70, 0x82, 0x77, 0xa3, 0x09, 0x1b, 0x24, 0x43, 0x9c, 0xba, + 0x38, 0xdf, 0xfb, 0x6f, 0xc3, 0x14, 0xa1, 0x82, 0x68, 0x98, 0x1a, 0x97, 0xed, 0xb4, 0xb2, 0xdb, 0xd7, 0x99, 0x9a, + 0x61, 0xd0, 0x46, 0xcc, 0xa9, 0x6f, 0xc4, 0x9c, 0x35, 0x1a, 0x68, 0x41, 0x52, 0x30, 0x62, 0x5e, 0x78, 0xad, 0x2d, + 0xcc, 0x2b, 0x9f, 0x5e, 0x7b, 0x0b, 0x84, 0x7a, 0x1c, 0x68, 0x9a, 0x82, 0xf7, 0x3c, 0xaa, 0xf7, 0xd4, 0xdd, 0xeb, + 0x52, 0x47, 0x1d, 0x50, 0x24, 0x8c, 0x2f, 0xdc, 0x24, 0x8c, 0x6b, 0xb8, 0x19, 0xf7, 0x58, 0x8f, 0xdb, 0xda, 0x36, + 0xe4, 0x03, 0x31, 0x48, 0x86, 0xc3, 0x9e, 0x70, 0x56, 0x12, 0x2d, 0x3c, 0xae, 0x5e, 0x00, 0xa9, 0x16, 0xef, 0xab, + 0xda, 0xbc, 0xf2, 0xe6, 0xee, 0x61, 0xd1, 0xcd, 0xf3, 0x18, 0x38, 0xa0, 0x7d, 0xb8, 0x1f, 0xaa, 0xe2, 0x83, 0x1d, + 0x75, 0x20, 0x0a, 0x5a, 0xdc, 0xaa, 0x09, 0xa4, 0xc6, 0xcc, 0x2e, 0x54, 0xcd, 0x1c, 0x1d, 0x4a, 0x08, 0x43, 0x96, + 0x57, 0xdd, 0xdd, 0xe7, 0x9e, 0x6a, 0x88, 0xc3, 0xa9, 0x7f, 0x65, 0x8c, 0x58, 0xc3, 0xa0, 0x93, 0x06, 0xda, 0x48, + 0xd2, 0x2c, 0x1f, 0x3c, 0xfa, 0x03, 0x56, 0x02, 0x2e, 0xf8, 0xb2, 0x4e, 0xd2, 0x86, 0x04, 0x6f, 0x59, 0xa2, 0x34, + 0x1f, 0xc2, 0x2d, 0x82, 0xf2, 0xc8, 0xc4, 0xda, 0xb4, 0x95, 0x0c, 0xe4, 0xaa, 0x2e, 0x6f, 0x3c, 0xf4, 0xbc, 0x4f, + 0xaa, 0xdd, 0x00, 0x1c, 0x99, 0x37, 0xb0, 0x64, 0x6b, 0x9f, 0x80, 0x47, 0x3e, 0xae, 0x10, 0xc4, 0x2d, 0x85, 0x8a, + 0x74, 0xa0, 0xea, 0x6b, 0xd8, 0xa0, 0x78, 0x0e, 0x0e, 0x82, 0x56, 0x82, 0xc3, 0xe0, 0x5d, 0x66, 0x34, 0xc9, 0x1a, + 0xb7, 0x66, 0x24, 0x9c, 0xaf, 0x56, 0x2d, 0x74, 0xf8, 0xb7, 0x7e, 0x8b, 0x79, 0x5c, 0x2a, 0xdc, 0xc7, 0x95, 0xc2, + 0x1d, 0x2c, 0x01, 0xc9, 0xd8, 0xd3, 0xb5, 0x63, 0xe1, 0xab, 0xd1, 0x21, 0x4c, 0xf8, 0x0b, 0x08, 0x1a, 0x6d, 0x8f, + 0x25, 0xd0, 0xb3, 0x6f, 0x15, 0x30, 0xba, 0xf6, 0xb2, 0x04, 0xd2, 0x82, 0xbb, 0xdb, 0x04, 0x5a, 0x85, 0xa8, 0x7d, + 0xfe, 0xf4, 0x73, 0x0e, 0x3c, 0xb2, 0xfe, 0x5c, 0x33, 0xcd, 0xba, 0x17, 0xf4, 0x56, 0x37, 0x1f, 0x8e, 0x71, 0x73, + 0x6c, 0xc0, 0x79, 0xd4, 0x81, 0x9f, 0x06, 0xa2, 0x47, 0x1d, 0x6c, 0x53, 0xf1, 0xb8, 0x04, 0xb2, 0x8f, 0x9e, 0xd6, + 0x40, 0x0a, 0x58, 0xe9, 0xd0, 0x68, 0x91, 0x26, 0x68, 0xb5, 0x9a, 0x9c, 0x93, 0x16, 0x42, 0x4b, 0x79, 0xcb, 0x55, + 0x32, 0x05, 0x1f, 0x69, 0x50, 0x0c, 0xbc, 0xa1, 0x6a, 0x1a, 0x22, 0x3c, 0x46, 0xcb, 0x94, 0x8d, 0xe9, 0x22, 0x53, + 0x71, 0xde, 0xe7, 0x91, 0x89, 0xa4, 0xcb, 0x4c, 0x24, 0xb8, 0xa3, 0x0e, 0x9e, 0x68, 0xfe, 0xf2, 0xb1, 0x36, 0x07, + 0x29, 0x12, 0x9d, 0x3c, 0xd1, 0x09, 0x98, 0x47, 0x49, 0x26, 0x24, 0x33, 0xcd, 0xf4, 0x8c, 0x6d, 0x39, 0xc4, 0xe2, + 0x0e, 0x54, 0xc1, 0xb5, 0x15, 0x65, 0x10, 0x4f, 0x49, 0xde, 0xcf, 0x8f, 0x3a, 0xf1, 0x04, 0xf3, 0x08, 0x38, 0xbd, + 0x77, 0x22, 0x64, 0x8d, 0xf2, 0x56, 0x74, 0x86, 0x0e, 0xa7, 0x58, 0x56, 0x97, 0xa8, 0x33, 0x74, 0x38, 0x41, 0x78, + 0xd6, 0x20, 0x59, 0x0e, 0x1e, 0xc3, 0x3c, 0xff, 0x3f, 0x52, 0xfe, 0x9b, 0xc3, 0x86, 0x98, 0xcf, 0x6f, 0x61, 0xa7, + 0xb0, 0x34, 0x88, 0x33, 0x02, 0x5e, 0x8b, 0xed, 0x53, 0x9c, 0x90, 0x49, 0x33, 0x73, 0x01, 0xf7, 0x74, 0x2b, 0x8d, + 0x3b, 0x85, 0x0e, 0x13, 0x9c, 0x6e, 0x26, 0x85, 0x7a, 0xae, 0xcd, 0x2c, 0x4e, 0xe0, 0x7c, 0xaf, 0x46, 0x61, 0xcb, + 0x2f, 0x36, 0x93, 0xfc, 0xf2, 0x16, 0xb8, 0xcd, 0x14, 0xcb, 0x26, 0xc5, 0x19, 0x9e, 0x35, 0x5f, 0xe1, 0x59, 0xf3, + 0x43, 0x99, 0xd1, 0x58, 0x60, 0x09, 0xc1, 0xfb, 0x20, 0x11, 0xcf, 0xaa, 0xe4, 0x14, 0xcb, 0x86, 0x2e, 0x8f, 0x67, + 0x8d, 0xaa, 0x74, 0x73, 0x81, 0x65, 0x43, 0x97, 0x6e, 0x7c, 0xc0, 0xb3, 0xc6, 0xab, 0x7f, 0x31, 0xe9, 0x20, 0x06, + 0x74, 0x99, 0xa3, 0x65, 0x62, 0x86, 0x78, 0xfd, 0xdb, 0xdb, 0x77, 0xed, 0x9b, 0xce, 0xe1, 0x04, 0xbb, 0xf5, 0x4b, + 0x34, 0x8e, 0x25, 0x2a, 0x64, 0x4d, 0x80, 0x68, 0x82, 0x3b, 0x87, 0x53, 0xdc, 0x39, 0x4c, 0x6c, 0x53, 0xeb, 0x59, + 0x83, 0xdc, 0x29, 0x1f, 0x8a, 0x2a, 0x88, 0x7d, 0xf8, 0xb8, 0xc9, 0xc6, 0x13, 0x54, 0x03, 0x25, 0x3a, 0x9c, 0xd4, + 0x40, 0x05, 0xdf, 0x8b, 0xda, 0x77, 0x55, 0xaf, 0xc2, 0x20, 0x03, 0x25, 0xe4, 0xaf, 0xb9, 0x06, 0x4f, 0x2d, 0x45, + 0x43, 0xc6, 0x4f, 0x31, 0x40, 0xf9, 0x0e, 0x28, 0xb4, 0xf2, 0x44, 0x0f, 0xdd, 0x9b, 0x8e, 0x4e, 0xfc, 0xff, 0x79, + 0x32, 0xe5, 0xd0, 0xcb, 0x2d, 0xb3, 0x35, 0x3d, 0x3b, 0x19, 0x7f, 0xf8, 0xc0, 0x63, 0xfd, 0x5f, 0x3b, 0x50, 0xac, + 0x41, 0x8a, 0xff, 0x2f, 0x1d, 0x9d, 0x0f, 0x46, 0xc8, 0x0a, 0xe2, 0xc2, 0x22, 0xfe, 0xf7, 0x87, 0xe5, 0x75, 0x5f, + 0x6c, 0x75, 0x5f, 0xe8, 0xee, 0xfd, 0xa6, 0xb5, 0x2a, 0x27, 0xae, 0x2a, 0x19, 0xf2, 0x5f, 0xa7, 0x5b, 0x5b, 0xa0, + 0x91, 0x35, 0x7a, 0x36, 0xf1, 0x1b, 0xdc, 0x6f, 0xc7, 0x3b, 0x90, 0x79, 0xcd, 0xcd, 0xd3, 0xa0, 0x70, 0xf8, 0x7a, + 0x77, 0xaa, 0x17, 0x2d, 0xf0, 0xde, 0x94, 0x5a, 0x5f, 0x19, 0xfa, 0x96, 0x83, 0xc5, 0xa6, 0x29, 0xb7, 0x36, 0x96, + 0x8e, 0xba, 0x58, 0xbb, 0x22, 0x42, 0xa5, 0xbb, 0x0b, 0x50, 0x8a, 0x8f, 0x55, 0x93, 0xe9, 0xaf, 0x73, 0x15, 0xe9, + 0x4b, 0xa8, 0x86, 0xfe, 0xbc, 0xbf, 0x50, 0x91, 0x12, 0x73, 0x93, 0x77, 0x7f, 0x0e, 0x7d, 0x82, 0x86, 0xb5, 0xe1, + 0xd9, 0xed, 0xb3, 0xc2, 0xea, 0x77, 0xaa, 0x43, 0xd0, 0x3f, 0x80, 0x2c, 0x69, 0x31, 0x7d, 0x60, 0xdd, 0x1a, 0xb6, + 0x5d, 0x34, 0xcb, 0x44, 0xd3, 0x6a, 0x53, 0xe7, 0x9a, 0x3d, 0xcc, 0xe7, 0x3e, 0x4f, 0xc1, 0x0b, 0xa3, 0x1f, 0xdf, + 0xc1, 0x6e, 0xdc, 0xd5, 0x18, 0x89, 0xba, 0x92, 0xa9, 0x84, 0x7e, 0x74, 0x87, 0x59, 0x74, 0xaf, 0xbd, 0x18, 0x73, + 0xed, 0xef, 0xa3, 0x03, 0xe5, 0x07, 0x95, 0x24, 0x07, 0x96, 0xfd, 0x0d, 0x16, 0xdd, 0x81, 0x79, 0x62, 0x59, 0x4d, + 0x60, 0x15, 0xdd, 0x7b, 0x8b, 0x28, 0x74, 0x23, 0x6b, 0xcd, 0x80, 0xea, 0x66, 0x8c, 0x7a, 0x70, 0x1f, 0x02, 0x3d, + 0xf4, 0xcb, 0x52, 0xca, 0x76, 0x16, 0xd7, 0xba, 0x57, 0xba, 0xfb, 0xcd, 0x01, 0x79, 0x7c, 0xa1, 0xc7, 0x35, 0xfd, + 0xab, 0x49, 0x44, 0x23, 0xf6, 0x0f, 0x67, 0xc5, 0xd5, 0xa2, 0xd0, 0x98, 0x26, 0xfb, 0x2a, 0x4a, 0xe6, 0x6d, 0x30, + 0xd5, 0x4b, 0xe6, 0x9d, 0x3b, 0x6c, 0xbf, 0xef, 0xcd, 0xf7, 0x3d, 0x96, 0x7d, 0xa6, 0x33, 0x62, 0xa6, 0x8b, 0xb9, + 0xef, 0x7b, 0xf3, 0x7d, 0x8f, 0xb7, 0x07, 0x73, 0xeb, 0x2e, 0x14, 0x4b, 0x76, 0x86, 0x0b, 0x30, 0x2c, 0xf7, 0xb8, + 0x9b, 0x5a, 0x96, 0x0e, 0x02, 0x5b, 0x4b, 0x80, 0x38, 0x9f, 0x4f, 0xc3, 0x8a, 0x57, 0x43, 0xc0, 0x7d, 0x3a, 0xd7, + 0xf6, 0x2a, 0x15, 0x78, 0x4c, 0xd0, 0x88, 0xe8, 0xd8, 0x36, 0xfa, 0x59, 0x2f, 0xe0, 0xf2, 0x88, 0x2a, 0xf5, 0x24, + 0x11, 0xf0, 0xaa, 0x5a, 0xe5, 0xad, 0x8b, 0x94, 0x5f, 0xc4, 0xcb, 0x71, 0xc5, 0x1e, 0x53, 0xc9, 0x00, 0x56, 0x65, + 0x49, 0x97, 0x40, 0xea, 0xf9, 0xde, 0x44, 0xbf, 0x6c, 0x22, 0x4f, 0xae, 0x6f, 0x4b, 0xbf, 0x30, 0x35, 0x2d, 0xc4, + 0x62, 0x32, 0x05, 0x1f, 0x5a, 0x60, 0x19, 0x0a, 0x5d, 0xaf, 0xb2, 0xf5, 0xaf, 0x49, 0x6e, 0x12, 0x28, 0x9c, 0x6a, + 0x8a, 0x88, 0x26, 0x6a, 0x41, 0x33, 0x6d, 0x49, 0xca, 0xf3, 0xc9, 0x63, 0x71, 0xf7, 0x12, 0xb0, 0x9b, 0x12, 0xd5, + 0xd8, 0x91, 0xf7, 0x16, 0x76, 0x00, 0x4e, 0x08, 0xdb, 0x55, 0xf1, 0x52, 0x82, 0xce, 0x1f, 0x67, 0x84, 0xed, 0xaa, + 0xfa, 0x84, 0x99, 0xec, 0x29, 0xd9, 0x18, 0x6e, 0x3f, 0x4c, 0x1a, 0x19, 0x3a, 0xea, 0xc4, 0x59, 0xcf, 0x11, 0x03, + 0x03, 0x50, 0x0f, 0xb8, 0x5b, 0xdb, 0xb3, 0xbc, 0xbb, 0x21, 0x79, 0x94, 0xb2, 0x44, 0x98, 0xeb, 0x72, 0x9d, 0xb2, + 0x5a, 0x75, 0x2a, 0x2a, 0x58, 0xe0, 0xa9, 0xb7, 0x17, 0xa8, 0xf9, 0xda, 0x41, 0x71, 0xae, 0x93, 0x4d, 0xd3, 0xf3, + 0xb2, 0xef, 0xde, 0x8e, 0x45, 0xc6, 0x26, 0xed, 0xed, 0x0e, 0x22, 0x61, 0x38, 0x61, 0xe5, 0x71, 0xc2, 0x55, 0x6d, + 0x8f, 0x00, 0xdd, 0x78, 0x22, 0x37, 0x16, 0x64, 0xb9, 0xae, 0x8c, 0xee, 0x3d, 0xbf, 0x5b, 0x4a, 0x84, 0x1d, 0x6d, + 0x49, 0x30, 0x5d, 0x82, 0x56, 0xd3, 0xe9, 0x37, 0x99, 0x6b, 0xcf, 0x0d, 0x6f, 0x8a, 0xb6, 0xb9, 0xbd, 0x49, 0xc7, + 0x7a, 0x7b, 0xe8, 0x18, 0xca, 0x20, 0x06, 0x3a, 0x1f, 0xf1, 0x5e, 0xa3, 0x91, 0x20, 0x50, 0xc8, 0x24, 0x43, 0x2c, + 0x22, 0xa7, 0x45, 0x3f, 0x38, 0xd0, 0xf0, 0xa8, 0x12, 0x10, 0xa6, 0x20, 0x84, 0xf8, 0x5d, 0x6b, 0x84, 0xf5, 0x97, + 0xab, 0x96, 0x0b, 0x1b, 0xa9, 0x36, 0x74, 0xf0, 0xff, 0xf2, 0x97, 0xad, 0x9e, 0x59, 0x2e, 0x8a, 0xc6, 0xcd, 0x4c, + 0x83, 0x45, 0x80, 0xf4, 0x68, 0xb2, 0x1d, 0x14, 0x77, 0xe7, 0x62, 0xbd, 0x21, 0x20, 0x31, 0x83, 0x09, 0xca, 0x86, + 0x75, 0x63, 0x0c, 0xf3, 0xa8, 0xd2, 0xb2, 0xd6, 0x24, 0x66, 0xcf, 0x97, 0xce, 0x5f, 0xf7, 0xe5, 0x5d, 0xcc, 0xf0, + 0x7d, 0x2c, 0xf1, 0x2d, 0x78, 0xd2, 0xc4, 0x02, 0xdb, 0xc7, 0x0b, 0x8a, 0x35, 0x51, 0x3d, 0xc7, 0xde, 0x16, 0xb0, + 0xce, 0x7a, 0x8f, 0x48, 0xef, 0x77, 0xf5, 0xab, 0x0d, 0xbe, 0x5b, 0xf8, 0x15, 0x58, 0x3f, 0x7b, 0x27, 0x29, 0x96, + 0x0d, 0xd1, 0x2c, 0xec, 0x91, 0x01, 0xe5, 0x2a, 0x7e, 0xd9, 0x4f, 0xdd, 0x2a, 0x86, 0x6b, 0x1f, 0xaf, 0xf0, 0x87, + 0x8d, 0x76, 0x1b, 0x79, 0x59, 0xdc, 0xec, 0x4d, 0xd9, 0x10, 0x55, 0xd3, 0x3b, 0x32, 0x37, 0x52, 0xea, 0x5f, 0x1f, + 0x70, 0x6b, 0xab, 0x7d, 0x37, 0xcd, 0xb7, 0x0e, 0x9d, 0xab, 0xa6, 0x5d, 0x6a, 0xad, 0x08, 0xf6, 0x7e, 0xb6, 0x70, + 0x73, 0x6b, 0xc0, 0x1e, 0xfc, 0xdc, 0x1d, 0xcd, 0x55, 0x02, 0xd1, 0xe9, 0x8d, 0x66, 0x7c, 0x15, 0xfe, 0x99, 0x36, + 0xc2, 0x7e, 0xfc, 0x67, 0xf4, 0x67, 0xda, 0x40, 0x7d, 0x14, 0xce, 0xef, 0x56, 0x6c, 0xb6, 0x82, 0x60, 0x6b, 0x0f, + 0x8e, 0xf0, 0x6b, 0xbf, 0x24, 0x57, 0x34, 0xe3, 0xc9, 0xca, 0xbe, 0x84, 0xb7, 0xb2, 0xcf, 0x04, 0xad, 0xf4, 0xe3, + 0x4e, 0xab, 0x50, 0x8c, 0x32, 0x08, 0x2c, 0x1c, 0xee, 0x35, 0xfb, 0x83, 0x56, 0xf3, 0xd1, 0xd0, 0xfc, 0xab, 0x23, + 0xdc, 0xa3, 0x5a, 0xc4, 0xb6, 0x37, 0x1b, 0x5b, 0x3f, 0x04, 0xc3, 0x0e, 0x08, 0x05, 0x0e, 0x72, 0xe9, 0x55, 0x82, + 0x8c, 0xef, 0xc9, 0x6a, 0xc5, 0x6c, 0x34, 0x6b, 0xab, 0xc1, 0x2f, 0x63, 0x33, 0x1d, 0xb6, 0xa3, 0x4e, 0xcf, 0x89, + 0xb1, 0xa4, 0x01, 0x91, 0xa6, 0x31, 0x83, 0x40, 0x52, 0x4b, 0xcd, 0x61, 0xcd, 0xef, 0x82, 0xb8, 0xba, 0x3f, 0x82, + 0x94, 0x1f, 0x82, 0x98, 0x1f, 0x11, 0x08, 0xa0, 0x6d, 0x98, 0xa3, 0xb2, 0x21, 0xe7, 0xbb, 0xf4, 0x40, 0x3b, 0x33, + 0x34, 0xf8, 0x6a, 0xd5, 0xaa, 0x86, 0x29, 0x8b, 0xfa, 0x30, 0x97, 0x6b, 0x2c, 0xc9, 0x1b, 0xd0, 0x35, 0xe7, 0x44, + 0xf6, 0x7d, 0x57, 0x79, 0x78, 0x08, 0x18, 0x0b, 0x02, 0x4e, 0xfa, 0x7d, 0xd9, 0x2f, 0xc8, 0xc5, 0x65, 0x08, 0x3e, + 0x66, 0x98, 0x0f, 0xd4, 0xa0, 0x18, 0x0e, 0x51, 0x6c, 0x9d, 0xce, 0x62, 0x1d, 0x71, 0xc5, 0xf3, 0x4b, 0x2e, 0xc0, + 0x2f, 0x39, 0x47, 0x6c, 0x50, 0x0c, 0xc9, 0x83, 0x24, 0x14, 0xe0, 0x94, 0xbf, 0xc3, 0xe7, 0xf1, 0xd2, 0x37, 0x30, + 0xd5, 0xc3, 0xd2, 0x17, 0xd9, 0x60, 0x31, 0x67, 0x2c, 0x81, 0xe0, 0x66, 0xc0, 0x5e, 0x6a, 0x43, 0xa2, 0xb6, 0x06, + 0x0a, 0xee, 0x02, 0xdf, 0xcc, 0xe8, 0xe9, 0x56, 0x1b, 0x83, 0xc0, 0xe2, 0x85, 0xbe, 0x86, 0x31, 0x08, 0xa4, 0x2f, + 0x57, 0x1d, 0xf6, 0x97, 0x1f, 0x26, 0xcb, 0x0f, 0x5e, 0xa1, 0x4d, 0x76, 0x4a, 0xab, 0x44, 0x3d, 0xbe, 0xca, 0x13, + 0x47, 0x13, 0x64, 0x62, 0xa8, 0x74, 0xc3, 0x32, 0x71, 0x25, 0x7d, 0x26, 0x9a, 0x6c, 0x37, 0x1c, 0x33, 0xe7, 0xbb, + 0xd9, 0xfe, 0x61, 0xdd, 0xce, 0x39, 0xe1, 0x5a, 0x2b, 0xa9, 0xb5, 0x51, 0xcf, 0x34, 0x55, 0xb5, 0xc1, 0xfc, 0x2e, + 0xad, 0x96, 0x16, 0x5b, 0x57, 0xef, 0x9e, 0xff, 0x2c, 0x5d, 0x19, 0x7f, 0x8b, 0x55, 0xa1, 0x15, 0x19, 0x6e, 0xb7, + 0x90, 0x33, 0x67, 0xba, 0x74, 0x8a, 0x5c, 0xa8, 0x0e, 0x7f, 0x51, 0x4f, 0xea, 0x97, 0x2a, 0x83, 0x86, 0x74, 0xe8, + 0xf7, 0x3a, 0x01, 0xe5, 0x1f, 0x4c, 0x4c, 0x64, 0x2c, 0xba, 0xa5, 0x45, 0x1e, 0xfe, 0xf8, 0x22, 0xd7, 0xb1, 0xaa, + 0xf6, 0x60, 0x20, 0x7b, 0xba, 0xe2, 0x1e, 0xdc, 0x9a, 0xf0, 0x31, 0x67, 0x69, 0xbc, 0x17, 0xfc, 0xd8, 0x90, 0x8d, + 0x1f, 0x83, 0x1f, 0xc1, 0xdd, 0xd9, 0x3e, 0x8b, 0x58, 0xc6, 0x85, 0x70, 0xf7, 0x58, 0x97, 0xa5, 0x4a, 0x19, 0x2b, + 0xa7, 0x5b, 0xf6, 0x2f, 0xa4, 0xde, 0x24, 0xe1, 0xa5, 0x25, 0xd6, 0x26, 0x05, 0x2b, 0x9f, 0x92, 0xc2, 0xb3, 0x2b, + 0xfa, 0x56, 0x8b, 0xd9, 0x4b, 0x2d, 0xe9, 0xae, 0xaf, 0x2e, 0x4b, 0x15, 0x34, 0x1c, 0x84, 0xb6, 0xb4, 0x81, 0x04, + 0x18, 0xb8, 0x94, 0x3e, 0x9d, 0xf6, 0x4c, 0x22, 0xb3, 0x24, 0x84, 0x77, 0x0f, 0x2a, 0x98, 0xff, 0xce, 0x36, 0xc2, + 0xaa, 0xc0, 0xe5, 0x4a, 0x15, 0xf5, 0x52, 0x10, 0x08, 0x40, 0x5f, 0x7a, 0x0f, 0x8a, 0xf3, 0xa2, 0xd7, 0x68, 0x08, + 0xd0, 0xc2, 0x52, 0x7d, 0xad, 0x8a, 0xe9, 0xbe, 0xff, 0x9c, 0x9f, 0xf7, 0xe1, 0x1c, 0xd2, 0x36, 0xde, 0xd4, 0xa4, + 0x84, 0x9a, 0x1d, 0xb4, 0x0f, 0x56, 0xd9, 0x5e, 0xf9, 0xb7, 0x21, 0x45, 0x26, 0x7f, 0xc0, 0x7e, 0xa0, 0xb6, 0xc3, + 0xa1, 0x2d, 0x58, 0xf5, 0x52, 0x46, 0xc1, 0x80, 0x95, 0x03, 0x6e, 0x4f, 0x46, 0x09, 0x4d, 0xa6, 0x0c, 0xd4, 0xfd, + 0xa6, 0x68, 0x35, 0xb7, 0x27, 0x75, 0xbf, 0x21, 0xed, 0xec, 0x23, 0xb5, 0xb3, 0x4f, 0x0e, 0x5e, 0x2c, 0x82, 0xfc, + 0x21, 0x42, 0x85, 0xc3, 0xbc, 0x29, 0xd1, 0x51, 0x07, 0xb8, 0x33, 0x70, 0xe0, 0x01, 0x5b, 0x94, 0x83, 0x03, 0x6a, + 0x2d, 0xee, 0x69, 0x23, 0x71, 0xde, 0x9e, 0x50, 0xbb, 0x08, 0x25, 0x6e, 0xd6, 0xcc, 0xb4, 0xa0, 0xb5, 0x42, 0x3b, + 0x8f, 0x7b, 0xbc, 0xcd, 0xb3, 0x5a, 0xfc, 0x84, 0x0d, 0x6b, 0xaa, 0xfa, 0x0d, 0x34, 0x47, 0xb5, 0x20, 0x37, 0x4f, + 0xb5, 0xb7, 0x2a, 0x19, 0x04, 0xc1, 0xd0, 0x70, 0x2a, 0x44, 0x93, 0x8c, 0x41, 0x6b, 0xe8, 0xdd, 0x6a, 0xaf, 0x56, + 0xdc, 0x21, 0xbe, 0xac, 0x79, 0xab, 0xe9, 0x5b, 0x00, 0x5a, 0x84, 0x41, 0x79, 0x6f, 0x12, 0x80, 0xf7, 0x6d, 0x19, + 0x21, 0x6d, 0x39, 0x30, 0x6f, 0x36, 0x96, 0x8a, 0xcd, 0x77, 0x74, 0x32, 0x8c, 0x03, 0x33, 0xa2, 0x00, 0xdf, 0x94, + 0x90, 0x84, 0xab, 0xa4, 0x1b, 0x99, 0x88, 0x39, 0x93, 0x31, 0xc7, 0x37, 0x85, 0x10, 0xea, 0xda, 0x7c, 0x09, 0x5c, + 0xdd, 0xc9, 0x48, 0x7c, 0x33, 0x61, 0xea, 0x1d, 0x2d, 0x26, 0x0c, 0xfc, 0x8a, 0xdc, 0xed, 0x58, 0x4c, 0xc9, 0xc5, + 0x53, 0x19, 0x0e, 0x28, 0x86, 0x07, 0x47, 0x87, 0x58, 0xe9, 0x10, 0x28, 0x15, 0x2e, 0xb2, 0xdb, 0xbd, 0x37, 0x85, + 0xb8, 0xbb, 0x0f, 0x0b, 0x6c, 0x1d, 0x00, 0x4b, 0xa7, 0x49, 0x80, 0x7f, 0xf9, 0x98, 0x8f, 0xd1, 0x98, 0x53, 0xad, + 0xeb, 0xb7, 0xbf, 0xa3, 0x1b, 0xa0, 0xb7, 0xa5, 0xa3, 0xe0, 0xa0, 0x35, 0x84, 0x5c, 0xb8, 0x0b, 0x83, 0x8b, 0x2f, + 0xbf, 0xb6, 0x28, 0xb4, 0x37, 0x16, 0x40, 0xef, 0xaf, 0x04, 0x2c, 0xd8, 0x30, 0xc7, 0x14, 0x5e, 0x6b, 0x9d, 0x30, + 0xe5, 0x45, 0x05, 0x79, 0x52, 0xbe, 0xc7, 0x59, 0xab, 0xfd, 0x96, 0x8d, 0xe1, 0x0e, 0x23, 0xfa, 0x76, 0xe1, 0xc8, + 0x82, 0x07, 0x64, 0x9a, 0xc4, 0x34, 0xfb, 0xc6, 0x45, 0x1e, 0x79, 0x3d, 0x0e, 0x77, 0xb5, 0xe4, 0xe7, 0xeb, 0x15, + 0x5d, 0x63, 0x08, 0x45, 0xe1, 0xf7, 0xfb, 0x15, 0x1e, 0x28, 0xad, 0x0c, 0xda, 0xa0, 0x61, 0x71, 0x9b, 0xff, 0x02, + 0x67, 0x0c, 0xad, 0x17, 0x32, 0x77, 0x74, 0xc6, 0xe1, 0xcc, 0x62, 0xc6, 0x94, 0xc0, 0xa8, 0x94, 0x28, 0xe8, 0x04, + 0x1c, 0x9d, 0xab, 0x0f, 0x92, 0x87, 0xd5, 0xb1, 0x02, 0xf0, 0x24, 0x53, 0xf8, 0x27, 0xdb, 0x04, 0xeb, 0x7e, 0xab, + 0x66, 0x98, 0xfa, 0x8b, 0xda, 0x76, 0x2d, 0x5f, 0xfa, 0x38, 0xd2, 0xc6, 0x10, 0x5a, 0xe7, 0xee, 0x1e, 0x50, 0xc4, + 0x06, 0xbd, 0x88, 0x15, 0xbe, 0x91, 0x8b, 0x91, 0x5e, 0x5f, 0xed, 0x3a, 0xa6, 0x00, 0x51, 0xac, 0xbb, 0x26, 0xbe, + 0xa9, 0x9e, 0x3f, 0x95, 0x71, 0x0e, 0x67, 0x10, 0x84, 0x38, 0x29, 0x2f, 0x1b, 0x62, 0x41, 0x2e, 0x74, 0xa7, 0x42, + 0x77, 0x5a, 0x21, 0x94, 0x4d, 0x8f, 0xca, 0xfb, 0x57, 0x08, 0x61, 0xa0, 0xcb, 0xec, 0xc0, 0xaa, 0x7c, 0x0b, 0xab, + 0xe0, 0xd5, 0x8b, 0x0d, 0xac, 0x12, 0x70, 0x3c, 0x97, 0x68, 0x54, 0x54, 0x38, 0xa4, 0x49, 0x9f, 0x8f, 0x45, 0x90, + 0x00, 0x58, 0xf4, 0x2e, 0xb1, 0x79, 0xdf, 0xc3, 0x21, 0xbf, 0x27, 0x11, 0xf9, 0xd3, 0x8d, 0x68, 0x06, 0xef, 0xe2, + 0xca, 0xbe, 0x43, 0x08, 0x58, 0x7a, 0x8e, 0xe1, 0x3d, 0xe4, 0xef, 0xbf, 0xc3, 0x6a, 0x2d, 0xc8, 0xe3, 0x7f, 0x89, + 0x92, 0xd0, 0xd8, 0x7f, 0x8e, 0x87, 0x16, 0x09, 0xfd, 0x81, 0x6f, 0x8e, 0xb0, 0xc2, 0xc1, 0xad, 0x22, 0x2e, 0x83, + 0x5b, 0x7c, 0xac, 0x43, 0x0f, 0x00, 0x4b, 0x28, 0xf6, 0x41, 0xbe, 0x81, 0x62, 0x1a, 0x07, 0x14, 0x59, 0xfa, 0x17, + 0xb8, 0x60, 0xb5, 0x50, 0xde, 0xdf, 0xb6, 0x9c, 0x94, 0x56, 0xbb, 0xe4, 0xd5, 0xe6, 0x40, 0xe5, 0xa7, 0x7f, 0xe1, + 0x2b, 0xf5, 0x43, 0xcd, 0xf6, 0x0b, 0xdf, 0x58, 0xa0, 0xc7, 0xa0, 0x08, 0xb0, 0xbf, 0xd7, 0x84, 0x3b, 0x8a, 0x5e, + 0xe6, 0x62, 0xbf, 0x6d, 0xaf, 0x7b, 0x89, 0xb9, 0xbc, 0xae, 0xb2, 0xe6, 0x60, 0x0a, 0x0d, 0x0e, 0xaa, 0x70, 0x46, + 0x60, 0x2e, 0x5f, 0x94, 0x05, 0xe7, 0x20, 0xde, 0xf7, 0xa5, 0xce, 0x29, 0xa3, 0x01, 0xbc, 0x48, 0xca, 0x47, 0xa7, + 0xfa, 0x1c, 0x5c, 0xc6, 0x35, 0x9b, 0xf8, 0x44, 0xba, 0x54, 0x60, 0x25, 0x8d, 0x71, 0x68, 0x40, 0x53, 0x3a, 0x07, + 0xb3, 0x0d, 0xa0, 0xe0, 0xf6, 0x7c, 0xd8, 0x58, 0x28, 0xef, 0x2d, 0xda, 0xda, 0xd3, 0xd1, 0x84, 0x58, 0x93, 0x26, + 0xef, 0x6e, 0x5b, 0x23, 0x83, 0x33, 0xbf, 0xfd, 0xb7, 0xc2, 0x22, 0xc1, 0x80, 0x4a, 0x4d, 0x12, 0x84, 0x27, 0x28, + 0x8d, 0x74, 0x2b, 0x37, 0x13, 0x48, 0x27, 0xb2, 0x66, 0xd4, 0xbd, 0x71, 0xbe, 0x5a, 0x6a, 0x20, 0x2b, 0x6a, 0x90, + 0x7b, 0xd4, 0x40, 0xd4, 0xb7, 0x7f, 0x01, 0x0b, 0x61, 0x22, 0x54, 0x49, 0x2f, 0x20, 0xc2, 0x5c, 0x69, 0x3e, 0xa0, + 0x88, 0x7c, 0xc8, 0x6b, 0x40, 0x85, 0x94, 0xbc, 0x04, 0xa3, 0x71, 0x78, 0xbd, 0x07, 0xdc, 0x2f, 0x0d, 0xc3, 0xe0, + 0x38, 0x05, 0x9d, 0xff, 0xd6, 0xe5, 0x43, 0xf5, 0x72, 0x75, 0x10, 0xc2, 0x4f, 0x24, 0x64, 0x98, 0x46, 0x7e, 0x01, + 0xb2, 0x99, 0x63, 0x71, 0x70, 0x20, 0x40, 0xe0, 0x87, 0x28, 0xc2, 0x1e, 0xcf, 0xf0, 0x32, 0xd9, 0x20, 0x7a, 0x6e, + 0x56, 0x79, 0x35, 0x2b, 0xe1, 0xcd, 0xaa, 0x70, 0x34, 0x8e, 0xae, 0x09, 0x03, 0xc1, 0x85, 0x9a, 0x7d, 0x83, 0x10, + 0x28, 0x5b, 0x6e, 0x35, 0x5d, 0x7a, 0x0a, 0xe6, 0xa3, 0x61, 0xf0, 0x96, 0xc1, 0x8b, 0xba, 0xda, 0xe4, 0x9f, 0x29, + 0x96, 0x28, 0xcd, 0x3c, 0x36, 0x3c, 0x27, 0x75, 0x8a, 0xa2, 0xbf, 0x04, 0xcf, 0xc3, 0xa0, 0x79, 0x11, 0xa0, 0x06, + 0xfc, 0xdb, 0xe0, 0xa8, 0x47, 0x23, 0x9a, 0xa6, 0x2e, 0xf8, 0x4d, 0x42, 0xf4, 0x26, 0x5b, 0xad, 0x64, 0x45, 0xd0, + 0x23, 0xb3, 0xc1, 0x80, 0x95, 0x78, 0x02, 0x3b, 0xd6, 0x70, 0xb0, 0xe4, 0x85, 0x0c, 0x73, 0x77, 0x4a, 0xe1, 0x1c, + 0x43, 0x3a, 0xc2, 0x89, 0x17, 0xb3, 0xf1, 0x3f, 0x9f, 0xa9, 0xbf, 0x7e, 0x6e, 0xbe, 0x96, 0x11, 0x11, 0x2e, 0x88, + 0x5c, 0x8d, 0x1d, 0x91, 0x5e, 0xd8, 0x32, 0x35, 0xb0, 0x65, 0x7e, 0x70, 0xd6, 0xd5, 0x43, 0x13, 0x2e, 0x0e, 0x0c, + 0xa8, 0x91, 0x67, 0xb4, 0x82, 0x33, 0x52, 0x0e, 0x1c, 0x94, 0x10, 0x8a, 0x15, 0xe1, 0x94, 0x5c, 0x40, 0x24, 0xbc, + 0x04, 0xf5, 0xc0, 0xb0, 0xc0, 0x93, 0xa0, 0xa6, 0x20, 0x41, 0x25, 0xae, 0x76, 0x0a, 0xb3, 0xce, 0xf4, 0x6c, 0xa7, + 0xa8, 0x67, 0x83, 0xfc, 0xfc, 0xa2, 0xc2, 0x14, 0x58, 0xda, 0x83, 0x83, 0x02, 0x22, 0x88, 0x01, 0x05, 0x2f, 0x25, + 0x40, 0x4f, 0x03, 0x5e, 0x6c, 0x68, 0xc0, 0xe7, 0x4a, 0x7b, 0x1d, 0x68, 0x5b, 0x9f, 0x32, 0xc8, 0xc5, 0xb3, 0x6a, + 0x4f, 0x13, 0x42, 0xf6, 0x5b, 0x7d, 0x15, 0x6f, 0x47, 0x48, 0xec, 0x7f, 0x54, 0x3a, 0xd0, 0x98, 0x25, 0xdd, 0xd5, + 0xc6, 0x7c, 0x55, 0xd3, 0x23, 0x56, 0x93, 0x10, 0x36, 0x48, 0x97, 0xe3, 0xd3, 0x9e, 0xc1, 0x15, 0xab, 0xd0, 0x72, + 0x70, 0x01, 0xfa, 0x6c, 0x40, 0x80, 0x02, 0x95, 0xa6, 0x12, 0x45, 0x11, 0x16, 0x51, 0xc9, 0x86, 0x61, 0x06, 0x61, + 0x0a, 0xab, 0x95, 0xa0, 0x1b, 0x6b, 0x00, 0xbc, 0x33, 0x33, 0x7f, 0x4a, 0x1f, 0x6c, 0xba, 0x76, 0xe6, 0x11, 0x40, + 0x40, 0xf6, 0xdb, 0x25, 0xbb, 0x2e, 0x37, 0x2a, 0x33, 0xbf, 0x96, 0xb6, 0x95, 0xdb, 0xf6, 0x18, 0x7b, 0x21, 0xb7, + 0xf9, 0x04, 0x08, 0x51, 0x5b, 0x32, 0x8d, 0x10, 0x21, 0xb1, 0x08, 0x55, 0x6d, 0xc8, 0x5a, 0x1b, 0x3a, 0xd0, 0x2f, + 0xd2, 0x43, 0xec, 0x03, 0x50, 0xbc, 0x59, 0x2e, 0xc1, 0x22, 0xbc, 0x74, 0x08, 0x7f, 0x97, 0x83, 0x33, 0x3c, 0x66, + 0x58, 0xac, 0x56, 0x50, 0xcf, 0xe1, 0x7d, 0xb2, 0x19, 0x9c, 0x54, 0x6c, 0x8c, 0x5d, 0x98, 0x89, 0x87, 0x65, 0x13, + 0x02, 0x27, 0xd0, 0xaf, 0xab, 0x88, 0xfa, 0xfb, 0xed, 0xf8, 0xa9, 0x0c, 0x6b, 0x3b, 0x10, 0x6b, 0xd6, 0x1b, 0xac, + 0x3e, 0x80, 0x96, 0xff, 0x93, 0xb8, 0x87, 0xca, 0xbc, 0x9b, 0x84, 0x7c, 0x73, 0x11, 0x7b, 0xac, 0x87, 0x18, 0xa9, + 0x2d, 0xee, 0x0e, 0x21, 0xfe, 0x9f, 0xad, 0x28, 0x06, 0x3c, 0xaa, 0xf8, 0xe7, 0x10, 0xf5, 0x20, 0x14, 0xb5, 0xf1, + 0xb0, 0x01, 0x4a, 0xbb, 0x5c, 0x57, 0x62, 0xa4, 0x4f, 0x20, 0xdf, 0xda, 0xf0, 0x82, 0xfa, 0x24, 0xca, 0x41, 0x4e, + 0xf6, 0xa2, 0x92, 0x26, 0x1b, 0xc2, 0x5c, 0x6f, 0x0b, 0xc7, 0xf4, 0xd5, 0x06, 0x2d, 0xc2, 0x17, 0xc0, 0xce, 0x70, + 0x2d, 0x59, 0x5a, 0xf0, 0xe5, 0x35, 0xf0, 0xb9, 0x35, 0xd7, 0x14, 0x25, 0x47, 0xfd, 0x17, 0x52, 0xdf, 0xfa, 0xc3, + 0xef, 0xd8, 0x13, 0x1f, 0xa9, 0xd5, 0x91, 0x6c, 0x84, 0x5a, 0xb3, 0x76, 0xbc, 0x6c, 0x33, 0xc2, 0xa0, 0x34, 0xd1, + 0xfb, 0x2a, 0x64, 0x95, 0x3b, 0x3b, 0x95, 0xde, 0x9c, 0xbe, 0xe6, 0x95, 0x73, 0x2a, 0x37, 0x8c, 0x6a, 0xa9, 0x69, + 0x80, 0x08, 0x57, 0x2e, 0x91, 0xbc, 0x4f, 0x74, 0xf8, 0x07, 0x8d, 0x71, 0xf5, 0x48, 0xe1, 0xef, 0x77, 0xc5, 0x0e, + 0xd9, 0x8e, 0x0e, 0xb7, 0x11, 0x34, 0xcf, 0x57, 0xf0, 0x80, 0xa3, 0x92, 0x21, 0x44, 0x39, 0xb9, 0xd8, 0xcf, 0x6b, + 0xa6, 0x6c, 0x37, 0x01, 0x42, 0x48, 0x39, 0x9c, 0x75, 0x0e, 0x91, 0xb5, 0xd0, 0x23, 0x55, 0x94, 0xc3, 0x2d, 0x9a, + 0x6b, 0x03, 0x54, 0x68, 0x81, 0x74, 0xf9, 0x85, 0xdd, 0xc7, 0x02, 0xa2, 0x97, 0xaf, 0x6d, 0x08, 0x63, 0x6b, 0x65, + 0x89, 0x0b, 0x3d, 0x6a, 0x13, 0x46, 0xd7, 0x6e, 0x0c, 0x6b, 0x03, 0xa3, 0xa7, 0x41, 0x49, 0x0b, 0x42, 0x5d, 0xf7, + 0xe8, 0x79, 0xa2, 0x03, 0x3d, 0x66, 0x84, 0x36, 0x18, 0x9e, 0x12, 0x05, 0x96, 0x4d, 0x05, 0x58, 0xf0, 0x2d, 0x8b, + 0x38, 0xd7, 0x36, 0x9b, 0x2c, 0xfc, 0xa8, 0x42, 0xfd, 0xb4, 0x5f, 0x56, 0x31, 0xcf, 0x85, 0xa5, 0x6e, 0xcf, 0x13, + 0x17, 0x8f, 0xee, 0xe9, 0x9b, 0xeb, 0x17, 0x2f, 0x5f, 0xbf, 0x5a, 0xad, 0xda, 0xac, 0xd9, 0x3e, 0xc1, 0x3f, 0xa9, + 0x32, 0x1e, 0x6c, 0x19, 0x05, 0xe8, 0xe0, 0x60, 0x9f, 0x6b, 0x17, 0x9e, 0x2f, 0x7c, 0x0e, 0x71, 0x83, 0xd4, 0x10, + 0x27, 0x45, 0x19, 0x13, 0xe4, 0x2e, 0xe8, 0x07, 0xf7, 0x01, 0x28, 0xa1, 0x2a, 0xf2, 0xf7, 0x61, 0x73, 0xf6, 0x7b, + 0x10, 0x98, 0x08, 0xea, 0x43, 0x04, 0x10, 0x88, 0x57, 0x8a, 0x0b, 0xc2, 0x5c, 0x02, 0x44, 0xf1, 0x5e, 0xc0, 0x9b, + 0x90, 0x3a, 0x6a, 0xd5, 0x22, 0x0f, 0x0b, 0x20, 0x89, 0x26, 0x1c, 0x25, 0x3d, 0xd2, 0x01, 0xbc, 0x21, 0x28, 0xa5, + 0xf9, 0xd5, 0xcb, 0xac, 0xbb, 0x54, 0x86, 0xfa, 0xad, 0x38, 0xc3, 0x53, 0xfb, 0x39, 0x85, 0xcf, 0x69, 0xcf, 0x9d, + 0x0e, 0xf2, 0x30, 0xc3, 0x0b, 0x22, 0x0f, 0xdd, 0xb3, 0x88, 0xcb, 0x79, 0xc1, 0xbe, 0x72, 0xb1, 0x90, 0xf1, 0xf2, + 0x2e, 0x16, 0xd1, 0x5d, 0x33, 0x3d, 0x0c, 0x8b, 0xe8, 0xae, 0x99, 0x47, 0x77, 0x08, 0xdf, 0xc7, 0x22, 0xba, 0x37, + 0x29, 0xf7, 0xcd, 0x1c, 0x6e, 0xbe, 0x70, 0x0e, 0x87, 0xa2, 0x29, 0xda, 0x58, 0x6c, 0x16, 0x35, 0x29, 0xb6, 0xa8, + 0x87, 0xc1, 0xbf, 0xef, 0xd8, 0xf8, 0x7e, 0xf8, 0x12, 0x5c, 0x9a, 0x34, 0x91, 0x9f, 0x40, 0xfa, 0x69, 0x55, 0x06, + 0xee, 0x53, 0xd2, 0xea, 0x4d, 0xcf, 0x65, 0xb3, 0xdd, 0x6b, 0x34, 0xa6, 0xb0, 0x77, 0x13, 0x92, 0xb9, 0x62, 0xd3, + 0x86, 0x8e, 0xaf, 0xb3, 0x9f, 0xac, 0x56, 0xfb, 0x19, 0xd2, 0x1b, 0x6e, 0xc2, 0x42, 0x35, 0x98, 0x0e, 0x71, 0x0b, + 0x3f, 0x4f, 0x10, 0x5a, 0xb2, 0xc1, 0x74, 0x48, 0xd8, 0x60, 0xda, 0x68, 0x0f, 0x8d, 0xa1, 0x9d, 0xde, 0x8a, 0x6b, + 0x08, 0xa1, 0x39, 0x1d, 0x1e, 0xe9, 0x92, 0xc2, 0xe6, 0x9b, 0x2f, 0x5a, 0x05, 0xf4, 0xcb, 0x6b, 0xc1, 0xcb, 0x04, + 0xee, 0x40, 0x5f, 0xf4, 0xdc, 0x3c, 0xdd, 0x5a, 0x90, 0xe3, 0xa3, 0xca, 0xd5, 0x9e, 0x22, 0xac, 0x7b, 0xca, 0x0f, + 0x8b, 0x43, 0xdd, 0x8c, 0xed, 0x52, 0xd8, 0x6f, 0x5f, 0x33, 0xf2, 0xd1, 0xc2, 0x02, 0x10, 0xa4, 0x82, 0x47, 0x52, + 0xd8, 0x70, 0x4a, 0x3e, 0x5c, 0x2c, 0x54, 0xb6, 0x60, 0x92, 0x91, 0x56, 0x2f, 0xd3, 0x96, 0xfe, 0x99, 0x8d, 0x68, + 0x4a, 0x31, 0x25, 0x89, 0x2b, 0x99, 0x69, 0xb0, 0xd0, 0x4d, 0xca, 0x33, 0x05, 0xbd, 0xd2, 0x10, 0xa7, 0x04, 0xe2, + 0x21, 0xf5, 0x0a, 0x6d, 0xe0, 0x15, 0x4e, 0x9b, 0xc5, 0x80, 0x0d, 0xd1, 0xd1, 0x31, 0xa6, 0x83, 0xcf, 0xc9, 0xbc, + 0x0d, 0x8f, 0x05, 0x7e, 0x1e, 0x92, 0x69, 0x53, 0x94, 0x09, 0x12, 0x12, 0xd2, 0xa6, 0x38, 0x84, 0xbd, 0x84, 0x70, + 0x62, 0x2a, 0x26, 0x03, 0x36, 0x6c, 0x4e, 0xcb, 0x8a, 0x1d, 0x57, 0xb1, 0x21, 0xca, 0x04, 0x53, 0xb1, 0x61, 0x2b, + 0xfa, 0xaf, 0x33, 0x68, 0x10, 0xf8, 0x00, 0x60, 0x00, 0x00, 0x85, 0xbc, 0x68, 0xbe, 0x38, 0x27, 0x6e, 0xb3, 0x9b, + 0x7b, 0xfc, 0x16, 0x58, 0xa0, 0xd5, 0xf6, 0xff, 0x2e, 0x94, 0x01, 0x7b, 0xca, 0x42, 0xc7, 0xcc, 0x2d, 0x8c, 0x8a, + 0x0e, 0xa0, 0x52, 0x22, 0x4c, 0xa1, 0x21, 0xb3, 0x9f, 0x68, 0xa8, 0x79, 0x5a, 0x83, 0x6c, 0xa0, 0x86, 0xcd, 0x04, + 0x8e, 0x18, 0x78, 0x87, 0x86, 0x4c, 0xb5, 0x31, 0x61, 0x98, 0xc1, 0x14, 0x13, 0x0d, 0x9e, 0x69, 0xdc, 0x5a, 0x0b, + 0x2d, 0xcb, 0xf5, 0xb3, 0xfe, 0xdf, 0x2a, 0xcc, 0x07, 0x45, 0xb3, 0x3d, 0x44, 0xfb, 0x84, 0x98, 0x8f, 0x21, 0x6c, + 0x32, 0x9b, 0xda, 0xd0, 0xdf, 0x47, 0x9d, 0xd8, 0x7c, 0xc2, 0x9f, 0xe1, 0x5a, 0xef, 0x00, 0x1d, 0x78, 0x50, 0xaf, + 0xbf, 0xa8, 0xa9, 0xbc, 0x3e, 0xee, 0x8c, 0x52, 0xb9, 0xeb, 0xdd, 0x69, 0x4f, 0x53, 0xec, 0x7b, 0xeb, 0xe1, 0xf2, + 0xa1, 0x1e, 0x02, 0x66, 0x0c, 0xfa, 0x96, 0x19, 0x7d, 0x2f, 0x44, 0x72, 0x41, 0x04, 0x16, 0x1a, 0x6b, 0x18, 0xec, + 0xad, 0x83, 0x03, 0x5d, 0x8d, 0x35, 0xe0, 0x79, 0x52, 0x04, 0x82, 0x81, 0x8b, 0xa0, 0x0c, 0x68, 0x92, 0xeb, 0xdb, + 0x70, 0xf2, 0x91, 0xd9, 0x5f, 0xb8, 0xbc, 0x7d, 0x2c, 0x8c, 0xb6, 0x55, 0x27, 0xdf, 0x97, 0x05, 0xee, 0xcb, 0x7b, + 0x49, 0xa3, 0xe0, 0x46, 0xe6, 0x26, 0x2f, 0xd7, 0x77, 0xeb, 0xae, 0x54, 0x67, 0x77, 0x33, 0x9d, 0xb2, 0x99, 0xce, + 0x76, 0x33, 0xbe, 0x66, 0xe6, 0x5b, 0x56, 0x91, 0xfa, 0x64, 0x8d, 0xe4, 0x9c, 0xe6, 0x3f, 0xd1, 0x39, 0x18, 0x05, + 0x73, 0x73, 0xaf, 0x0a, 0x27, 0x57, 0x46, 0x2e, 0xf6, 0x33, 0x4d, 0x5c, 0x91, 0xbe, 0x50, 0x87, 0x00, 0x2f, 0x2f, + 0xca, 0xc7, 0x07, 0xb8, 0xc8, 0x7f, 0x15, 0xa9, 0x8d, 0x72, 0x9a, 0x0b, 0x25, 0x72, 0x16, 0x20, 0x8d, 0xaa, 0x36, + 0x06, 0xf6, 0xd2, 0xec, 0x3d, 0xd9, 0xe7, 0x83, 0x2a, 0x62, 0xde, 0x50, 0x3f, 0xf7, 0xf1, 0x3d, 0x4d, 0xb1, 0x55, + 0x13, 0x27, 0xe4, 0x43, 0x12, 0x66, 0x20, 0x9b, 0x0d, 0xaa, 0xd7, 0x7e, 0x1b, 0x6d, 0x5c, 0x34, 0x43, 0xd9, 0xd7, + 0x4f, 0x9c, 0xfc, 0x50, 0x68, 0xe3, 0x00, 0xe3, 0xe8, 0x8f, 0x30, 0x35, 0x60, 0x4f, 0x22, 0x47, 0xa1, 0xa3, 0x3b, + 0x93, 0x76, 0xef, 0xa7, 0xdd, 0xeb, 0xb4, 0x0e, 0x94, 0x03, 0xd2, 0x6c, 0xcb, 0x74, 0xee, 0xdd, 0xf7, 0x3d, 0xbc, + 0x74, 0xbb, 0x86, 0x48, 0xdc, 0xf3, 0xc7, 0xda, 0x18, 0xe2, 0x0d, 0xd8, 0x88, 0xca, 0x83, 0x83, 0x3f, 0xac, 0xf7, + 0x6d, 0x25, 0xcb, 0xca, 0x6f, 0x84, 0x03, 0xdb, 0x60, 0x2a, 0x6d, 0x5e, 0x2a, 0x92, 0x05, 0xd8, 0x75, 0xee, 0xef, + 0x8e, 0x87, 0xff, 0x52, 0xfa, 0x4c, 0x8b, 0x71, 0x15, 0x7f, 0x25, 0xd2, 0xd2, 0x43, 0x54, 0x41, 0x04, 0xd2, 0xca, + 0xba, 0xd4, 0x37, 0x1d, 0xbd, 0x9e, 0xd2, 0x54, 0xdc, 0xbe, 0x15, 0x42, 0x0d, 0xcd, 0x8b, 0xdc, 0x2a, 0x82, 0x47, + 0x0b, 0x6b, 0x0c, 0xcd, 0x7d, 0xe9, 0x9d, 0x64, 0x02, 0xa2, 0xd6, 0xc7, 0xed, 0x4b, 0x22, 0xa1, 0xac, 0xee, 0x42, + 0x38, 0xdc, 0x85, 0x60, 0x5e, 0x06, 0x6d, 0x83, 0xd8, 0xed, 0x36, 0x68, 0x5b, 0x28, 0x89, 0x34, 0x81, 0xdb, 0xbd, + 0xc1, 0xc2, 0xde, 0x87, 0x97, 0x63, 0x39, 0x96, 0xee, 0x9a, 0xcc, 0x3c, 0x00, 0x04, 0x6a, 0x1f, 0x56, 0x3c, 0xb1, + 0x20, 0x88, 0xac, 0xe1, 0xe8, 0x7b, 0xce, 0x6e, 0x8d, 0xe5, 0xf0, 0x6c, 0xbe, 0x50, 0x2c, 0xd5, 0x77, 0xd4, 0x80, + 0x3f, 0x75, 0x3f, 0xaf, 0x9f, 0x92, 0x9a, 0x6e, 0xfc, 0x01, 0x84, 0x91, 0xb0, 0xca, 0x0e, 0xad, 0x90, 0x30, 0xc1, + 0xac, 0xca, 0x78, 0x6d, 0xbf, 0x41, 0xbc, 0x07, 0xa5, 0xc3, 0x09, 0x16, 0xb5, 0x0b, 0xaa, 0x00, 0x9b, 0x78, 0x63, + 0x5e, 0x94, 0x87, 0x37, 0x5b, 0x46, 0xd3, 0xcb, 0x35, 0x04, 0x3a, 0xee, 0x07, 0xcd, 0xa0, 0xc1, 0x62, 0x1b, 0x94, + 0xd9, 0x45, 0x18, 0xcf, 0xcf, 0x4f, 0x74, 0x9c, 0xf6, 0x52, 0xaf, 0xfe, 0x5b, 0x02, 0x06, 0xf8, 0x12, 0xbc, 0xc4, + 0xfc, 0xe8, 0xae, 0x03, 0xd5, 0x80, 0xfa, 0xa2, 0xc1, 0x86, 0x68, 0xb5, 0x6a, 0x95, 0xcf, 0x40, 0xd9, 0x6b, 0x2e, + 0x69, 0xae, 0xb9, 0xa4, 0xbd, 0xe6, 0x92, 0xee, 0x9a, 0x4b, 0xea, 0x6b, 0x2e, 0xe9, 0xae, 0xb9, 0x1c, 0x08, 0x3f, + 0x79, 0x71, 0x1c, 0x43, 0x0e, 0x71, 0x15, 0x95, 0x89, 0x8c, 0x07, 0x17, 0x9e, 0xfb, 0x2c, 0x92, 0xe5, 0xf2, 0xfb, + 0x31, 0xe4, 0xb6, 0x6c, 0x25, 0xb4, 0xdb, 0x14, 0x93, 0x10, 0x39, 0xfd, 0xe0, 0xa0, 0x74, 0x77, 0x06, 0x1f, 0xf5, + 0x98, 0xe3, 0xa5, 0x71, 0xa2, 0xfd, 0x03, 0x74, 0xf2, 0xfa, 0xd7, 0xc7, 0x58, 0xac, 0x89, 0xb4, 0x26, 0xf7, 0xfb, + 0x6d, 0x47, 0x29, 0x3e, 0x25, 0x3a, 0x3c, 0x39, 0x8f, 0x94, 0x16, 0x41, 0x10, 0xa2, 0x24, 0xc7, 0x09, 0x11, 0x66, + 0xbf, 0x3b, 0x57, 0x78, 0xad, 0x8a, 0x72, 0x66, 0x25, 0x57, 0x19, 0x38, 0xb1, 0x6b, 0x2b, 0x0c, 0xd4, 0x03, 0x17, + 0x82, 0x44, 0x27, 0xfc, 0xd1, 0xcc, 0x0c, 0x39, 0x4b, 0xca, 0xa4, 0x8f, 0xcd, 0x4c, 0x13, 0xb0, 0x82, 0xec, 0x3b, + 0x98, 0x2d, 0xef, 0x62, 0x8a, 0xef, 0xe3, 0x04, 0xff, 0xbf, 0xec, 0xbd, 0xeb, 0x92, 0xdb, 0x46, 0x96, 0x2e, 0xfa, + 0x2a, 0x55, 0x0c, 0x99, 0x06, 0xc4, 0x24, 0x8b, 0xa5, 0xbd, 0x67, 0x22, 0x0e, 0x58, 0x29, 0x86, 0x2c, 0x59, 0xdd, + 0x72, 0x5b, 0x97, 0x56, 0xa9, 0xdd, 0x76, 0x33, 0x78, 0x68, 0x14, 0x90, 0x24, 0x20, 0x83, 0x00, 0x0d, 0x80, 0x55, + 0xa4, 0x48, 0xbc, 0xfb, 0x8e, 0xb5, 0x56, 0x5e, 0x41, 0xb0, 0xa4, 0x9e, 0xd9, 0xf3, 0xeb, 0x9c, 0x3f, 0x52, 0x31, + 0x91, 0x48, 0xe4, 0x3d, 0x57, 0xae, 0xcb, 0xf7, 0xdd, 0x15, 0xbb, 0xa0, 0xb4, 0x7d, 0x41, 0x94, 0xe1, 0x6f, 0xe9, + 0xf5, 0xf2, 0x10, 0xe2, 0x7d, 0x7a, 0x69, 0x7e, 0x91, 0xb6, 0xa2, 0x00, 0x0f, 0x11, 0x7a, 0x54, 0x07, 0x82, 0x9d, + 0xf1, 0x84, 0x07, 0x70, 0xb2, 0x9a, 0xe5, 0xfc, 0x49, 0x0a, 0xe2, 0x44, 0xc1, 0x21, 0xe0, 0x6a, 0x77, 0x9b, 0x7e, + 0x01, 0xc3, 0x97, 0x0e, 0xb6, 0x1c, 0xde, 0x15, 0xbb, 0x1e, 0x2b, 0xf9, 0x07, 0x60, 0xdf, 0xea, 0xc9, 0x58, 0xdd, + 0x1e, 0x38, 0xeb, 0x52, 0x8a, 0x8e, 0x37, 0xc5, 0xe1, 0xed, 0xf9, 0xec, 0xb0, 0x0b, 0x22, 0xb6, 0x0f, 0x32, 0xac, + 0x75, 0xd2, 0xf0, 0x9f, 0x68, 0xeb, 0x60, 0x31, 0xc2, 0xfe, 0x2f, 0xeb, 0x81, 0x97, 0x90, 0x1a, 0x0a, 0x5c, 0x0c, + 0xb6, 0x1c, 0xad, 0xed, 0x32, 0x0d, 0xdc, 0xd4, 0xa0, 0xd7, 0x0f, 0x14, 0xa2, 0xbc, 0x64, 0x34, 0x37, 0x82, 0x4d, + 0x63, 0xc8, 0xc5, 0xe1, 0xb8, 0x59, 0x0e, 0x79, 0x49, 0xd3, 0x69, 0x10, 0x4a, 0x77, 0x96, 0x0d, 0x24, 0x51, 0xf6, + 0x41, 0xa8, 0x5d, 0x5b, 0x0e, 0xbb, 0xc0, 0xf6, 0xe5, 0x8f, 0x86, 0xb1, 0x7f, 0xb5, 0x7c, 0x2a, 0xa4, 0x8b, 0x78, + 0x05, 0x82, 0xa8, 0xfd, 0x3c, 0x1b, 0x6e, 0xfd, 0xab, 0xcd, 0x53, 0xa1, 0xfc, 0xc6, 0x2b, 0x5b, 0x0e, 0xa9, 0xb3, + 0x16, 0xbe, 0x30, 0x1e, 0x1e, 0x5c, 0x19, 0xda, 0x8e, 0x47, 0xa1, 0xff, 0x36, 0x6b, 0x04, 0x37, 0x36, 0xb4, 0xcf, + 0x17, 0x3e, 0x6c, 0x6d, 0x34, 0xd6, 0x14, 0xd3, 0x2d, 0xf4, 0x6f, 0x32, 0x5b, 0xda, 0xd3, 0xa8, 0xe4, 0xc5, 0xb9, + 0x69, 0xc4, 0x42, 0x18, 0x30, 0xf4, 0x93, 0xf9, 0x00, 0xaa, 0xb9, 0xd3, 0x11, 0xc8, 0xe4, 0x03, 0x3d, 0x58, 0x93, + 0x5a, 0xf5, 0xd7, 0x30, 0x93, 0xff, 0x47, 0x2a, 0x2c, 0x46, 0x77, 0xdb, 0x30, 0x53, 0x7f, 0x44, 0xf2, 0x0f, 0x56, + 0xf1, 0x7d, 0xea, 0x85, 0xda, 0x8f, 0x85, 0x15, 0x18, 0x94, 0xa8, 0x1a, 0xd0, 0x03, 0x11, 0x54, 0x65, 0x90, 0x66, + 0x58, 0x9d, 0x83, 0x7e, 0xf7, 0xb4, 0xea, 0x48, 0x0e, 0x69, 0xad, 0x86, 0x54, 0x30, 0x55, 0x6a, 0x50, 0x1d, 0x8f, + 0xab, 0x94, 0xe9, 0x32, 0xe0, 0x92, 0xbe, 0x4a, 0x95, 0x52, 0xf8, 0x4f, 0x04, 0xa0, 0x73, 0x70, 0x8f, 0xaf, 0xc7, + 0x40, 0x9a, 0x61, 0xe1, 0xb7, 0x66, 0xa7, 0xd7, 0x24, 0xdc, 0x26, 0xc1, 0xc5, 0x00, 0xe7, 0xe8, 0x3a, 0x2c, 0x57, + 0x29, 0x44, 0x50, 0x95, 0x50, 0xdf, 0xdc, 0x34, 0x28, 0x6d, 0x35, 0x08, 0x6b, 0x12, 0xea, 0x4c, 0xb2, 0x51, 0x69, + 0xbb, 0x51, 0x98, 0x2d, 0xe2, 0x7a, 0x46, 0x58, 0x73, 0x36, 0x53, 0x0d, 0x4c, 0x1a, 0x8e, 0x9b, 0x46, 0x6b, 0x51, + 0xa1, 0xa6, 0x30, 0xaf, 0x71, 0x55, 0xa9, 0xea, 0x6e, 0xcf, 0x2d, 0xa5, 0x65, 0x7b, 0xd5, 0x4d, 0xb2, 0x21, 0x97, + 0xa1, 0x0c, 0x83, 0xad, 0x1c, 0xc1, 0x04, 0x92, 0xe4, 0xcc, 0xdf, 0xca, 0x3f, 0xd4, 0xa6, 0x6b, 0x01, 0x73, 0x8c, + 0x59, 0x36, 0x2c, 0xe8, 0x15, 0xb8, 0x07, 0x5a, 0xe9, 0xd5, 0x34, 0xbb, 0xaa, 0x82, 0x64, 0x58, 0xe8, 0x65, 0x93, + 0xf1, 0x3f, 0x85, 0x91, 0x26, 0x33, 0x56, 0xb2, 0xc8, 0x76, 0x75, 0x4a, 0x9c, 0xc7, 0x09, 0x6c, 0x8f, 0xa6, 0xb7, + 0x7c, 0x9f, 0x41, 0x54, 0x10, 0x28, 0x98, 0x31, 0x5f, 0x76, 0xf5, 0xcc, 0xf7, 0x99, 0x65, 0xea, 0x3e, 0x1e, 0x8d, + 0x19, 0xdb, 0xef, 0xf7, 0xab, 0x7e, 0x5f, 0xcd, 0xb7, 0x7e, 0x3f, 0x79, 0x6e, 0xfe, 0xf6, 0x80, 0x41, 0x41, 0x4e, + 0x44, 0x53, 0x21, 0x82, 0x7f, 0x48, 0x9e, 0x22, 0x19, 0xdd, 0x69, 0x9f, 0x5b, 0xce, 0x96, 0xf9, 0x09, 0x08, 0xe6, + 0xf1, 0x78, 0xad, 0xc0, 0xae, 0x25, 0x8a, 0x84, 0x2c, 0xff, 0x29, 0x18, 0xcf, 0xdc, 0x07, 0x58, 0x32, 0x00, 0x61, + 0xab, 0x3c, 0x5d, 0xef, 0xf9, 0x2a, 0x78, 0xa7, 0xe3, 0x5d, 0x63, 0x45, 0x06, 0xe2, 0x16, 0xd8, 0x88, 0xb5, 0xf6, + 0x80, 0x9c, 0x29, 0xc0, 0xf1, 0xe2, 0x78, 0xbc, 0x94, 0xbf, 0x74, 0xb3, 0x75, 0x02, 0x95, 0x02, 0xb7, 0x47, 0x27, + 0x07, 0xff, 0x1d, 0x68, 0x06, 0xe5, 0x30, 0x6f, 0x76, 0xbf, 0x33, 0x27, 0x3f, 0x3d, 0xc5, 0x3f, 0xe1, 0x21, 0x3a, + 0xfd, 0x76, 0x6f, 0xfe, 0xa0, 0xa8, 0x3c, 0x1e, 0xd5, 0xe2, 0x07, 0x9e, 0x1f, 0xf8, 0x85, 0x6f, 0x02, 0xb3, 0xc9, + 0xd4, 0x3b, 0xfb, 0x26, 0xaf, 0x98, 0x7a, 0x8d, 0xe7, 0x4c, 0xbe, 0xc3, 0xe1, 0x5c, 0x8c, 0xea, 0xdd, 0xc8, 0x89, + 0x76, 0xaa, 0x30, 0x0e, 0x06, 0xff, 0x45, 0xb4, 0x4d, 0x08, 0x30, 0xa4, 0x6e, 0x49, 0x33, 0x1b, 0x57, 0x96, 0x78, + 0x96, 0x2e, 0xaf, 0x27, 0x75, 0xb9, 0xd7, 0x8a, 0xa7, 0x03, 0xb0, 0xb8, 0x6d, 0xc0, 0x0b, 0xe0, 0xde, 0x62, 0xeb, + 0x4a, 0xc1, 0xe1, 0x02, 0xe2, 0x14, 0x27, 0x20, 0x82, 0xf6, 0xfb, 0x12, 0xef, 0x15, 0xf4, 0x49, 0x3f, 0x42, 0x30, + 0xe4, 0xcf, 0x12, 0x70, 0xd7, 0xeb, 0xd5, 0x18, 0xdf, 0x4b, 0x21, 0xb8, 0x3e, 0xd3, 0x00, 0xb4, 0xe0, 0x77, 0xf9, + 0x58, 0x4e, 0xbf, 0x89, 0xc0, 0xb3, 0xe5, 0x60, 0xa2, 0xdc, 0x6d, 0x78, 0xda, 0x3f, 0x5a, 0x08, 0xc0, 0x52, 0x3c, + 0x53, 0x82, 0x05, 0x39, 0xc5, 0x5c, 0xfd, 0xbf, 0xe0, 0x23, 0xe6, 0x7b, 0xd2, 0x45, 0x6c, 0xb3, 0x7b, 0x72, 0x65, + 0x20, 0x81, 0xa6, 0x03, 0xe0, 0x21, 0x54, 0x40, 0x57, 0xc6, 0xcf, 0xcf, 0xb2, 0x1e, 0xeb, 0xe3, 0x3f, 0x05, 0xf7, + 0xe9, 0xa7, 0x0a, 0x1f, 0x1d, 0x8e, 0xab, 0x74, 0xb4, 0xa7, 0x14, 0x44, 0x47, 0xb7, 0xcf, 0xa7, 0x2a, 0xfb, 0xa6, + 0x02, 0x2a, 0xcb, 0x51, 0x7b, 0x2a, 0x00, 0x8b, 0x2d, 0x1d, 0x81, 0x4f, 0xb3, 0x7c, 0x42, 0xbe, 0xd7, 0x53, 0x71, + 0x73, 0xad, 0xd3, 0xc5, 0xf3, 0xf1, 0x14, 0xfe, 0x07, 0x62, 0x0f, 0xcb, 0x14, 0xd9, 0xb1, 0xeb, 0xe2, 0x07, 0xf1, + 0xb6, 0xb6, 0xa7, 0x3f, 0xf6, 0x10, 0xe9, 0x78, 0x20, 0x17, 0xea, 0x6b, 0x48, 0x25, 0x17, 0xea, 0x06, 0x62, 0x17, + 0x6a, 0xbc, 0xe3, 0x22, 0xd6, 0xfa, 0xdb, 0x1a, 0x05, 0x2b, 0x01, 0x67, 0xda, 0x5b, 0x30, 0xd8, 0xc0, 0xba, 0x65, + 0x19, 0xfc, 0x0d, 0xd7, 0x34, 0x81, 0x1b, 0x16, 0x59, 0xef, 0x0d, 0xb6, 0xd2, 0x5b, 0x70, 0xb4, 0x4c, 0x9c, 0x4b, + 0x49, 0x56, 0xb6, 0xc8, 0xb8, 0x7a, 0x14, 0x52, 0x35, 0x3d, 0xdc, 0x89, 0xfa, 0x41, 0x88, 0x3c, 0x58, 0xa7, 0x2c, + 0x2a, 0xd6, 0x20, 0xb3, 0x07, 0x7f, 0x0f, 0x19, 0x39, 0xca, 0x81, 0xa3, 0xd0, 0x5f, 0x9a, 0x40, 0xe7, 0xf9, 0x29, + 0xd4, 0x79, 0x24, 0xd8, 0x4a, 0x3d, 0x14, 0x56, 0x5e, 0x40, 0x74, 0xb0, 0x85, 0xb1, 0xdc, 0x93, 0x50, 0xb1, 0x29, + 0x13, 0x79, 0x1c, 0xd4, 0x12, 0x30, 0x56, 0x10, 0xcc, 0x59, 0x25, 0x5d, 0x90, 0xf2, 0x46, 0x0f, 0x8b, 0xcc, 0xfd, + 0x9d, 0xa0, 0xfc, 0xdf, 0xa9, 0x9c, 0x70, 0x7d, 0x19, 0x02, 0x1c, 0xed, 0x77, 0x20, 0x4a, 0x8c, 0xf5, 0x8b, 0x16, + 0xef, 0x64, 0xe6, 0x6c, 0x6a, 0x07, 0x09, 0x32, 0xb6, 0xc7, 0xaf, 0x10, 0x5a, 0x2d, 0x14, 0x59, 0x34, 0x5c, 0x30, + 0xdd, 0x9e, 0xd2, 0xaa, 0x7b, 0xd8, 0xf0, 0xac, 0xf4, 0x50, 0xa9, 0x6f, 0x63, 0x02, 0xcb, 0x2a, 0x65, 0xf8, 0x76, + 0x42, 0xd5, 0x89, 0x41, 0xc5, 0xba, 0x65, 0x4b, 0x38, 0xc4, 0x62, 0xd2, 0x58, 0x67, 0x03, 0x1e, 0xb1, 0x04, 0xfe, + 0xd9, 0xf2, 0x31, 0x5b, 0xf2, 0x68, 0xb2, 0xbd, 0x59, 0xf6, 0xfb, 0xa5, 0x17, 0x7a, 0xf5, 0x2c, 0xfb, 0x2e, 0x9a, + 0xcf, 0xaa, 0xb9, 0x8f, 0x8a, 0x8b, 0xc9, 0x60, 0xb0, 0xf5, 0xb3, 0xe1, 0x90, 0x25, 0xc3, 0xe1, 0x24, 0xfb, 0x0e, + 0x5e, 0xfb, 0x8e, 0x47, 0x6a, 0x49, 0x25, 0x37, 0x19, 0xec, 0xef, 0x03, 0x1e, 0xf9, 0xac, 0xf3, 0xd3, 0xb2, 0xe9, + 0xd2, 0xfd, 0xcc, 0x8e, 0xbb, 0xd0, 0x1d, 0x60, 0xe3, 0x6d, 0x83, 0x8e, 0xfc, 0xeb, 0x1d, 0x52, 0xea, 0x26, 0x03, + 0xb0, 0x1b, 0x0d, 0x70, 0xc8, 0x54, 0x2f, 0x45, 0x56, 0x2f, 0x65, 0xaa, 0x97, 0x64, 0xe5, 0x12, 0x2c, 0x24, 0xa6, + 0xca, 0x6d, 0x65, 0xe5, 0x96, 0x0d, 0xd7, 0xc3, 0xc1, 0x36, 0x8a, 0xcb, 0x66, 0x05, 0xf7, 0x85, 0x35, 0x05, 0xfe, + 0xdf, 0xb1, 0x05, 0xbb, 0x97, 0xc7, 0xc0, 0x5b, 0x74, 0x4c, 0x82, 0x0b, 0xc4, 0x3d, 0xbb, 0x03, 0x3b, 0x2c, 0xfc, + 0x05, 0xd7, 0xc9, 0x31, 0xdb, 0xe3, 0xa3, 0xd0, 0x2b, 0xd8, 0x9d, 0x4f, 0x40, 0xbb, 0x60, 0x6b, 0x80, 0x6c, 0x6c, + 0x87, 0x8f, 0x56, 0xc7, 0xe3, 0x5b, 0xcf, 0x67, 0x0f, 0xf8, 0xe3, 0x72, 0x75, 0x3c, 0xee, 0x3d, 0xa3, 0xde, 0xbb, + 0xe5, 0x09, 0x7b, 0xcf, 0x93, 0xc9, 0xed, 0x0d, 0x8f, 0x27, 0x83, 0xc1, 0xad, 0xbf, 0xe0, 0xf5, 0xec, 0x16, 0xb4, + 0x03, 0x97, 0x0b, 0xa9, 0x6b, 0xf6, 0xee, 0x78, 0xe6, 0x2d, 0x70, 0x6c, 0xee, 0xe0, 0xe8, 0xed, 0xf7, 0xbd, 0x15, + 0x8f, 0xbc, 0x3b, 0x52, 0x31, 0xad, 0xb9, 0xe2, 0x78, 0xdb, 0xe1, 0x7e, 0xba, 0xe6, 0x21, 0x3c, 0xc2, 0xaa, 0x4c, + 0x6f, 0x83, 0xf7, 0x3e, 0x5b, 0x6b, 0x16, 0xb8, 0x07, 0xcc, 0xb1, 0x21, 0x3b, 0xa1, 0x99, 0xf8, 0x6b, 0xec, 0x9f, + 0x5b, 0xd5, 0x3f, 0x34, 0xff, 0x4b, 0xdd, 0x4f, 0xe0, 0xf6, 0x45, 0x16, 0x24, 0xf6, 0x9e, 0xdf, 0xb2, 0x7b, 0x6e, + 0xd8, 0x66, 0x2f, 0x4c, 0xd9, 0x67, 0x4a, 0x8d, 0x1f, 0x29, 0x75, 0x63, 0x19, 0x56, 0x32, 0x77, 0x5f, 0x46, 0xe0, + 0x70, 0x40, 0x7e, 0x5a, 0x21, 0x0e, 0x42, 0xeb, 0x26, 0xab, 0xb9, 0xa2, 0x9c, 0x0b, 0x6d, 0x99, 0x79, 0x15, 0xb0, + 0x98, 0xa5, 0x14, 0x1a, 0x0b, 0x00, 0x04, 0x93, 0x42, 0x6b, 0xef, 0x65, 0x00, 0x39, 0x41, 0xc3, 0x9f, 0x9a, 0xab, + 0xa2, 0xac, 0x65, 0x4b, 0x42, 0x94, 0xed, 0x7a, 0x78, 0x8d, 0x90, 0x69, 0xfd, 0xfe, 0x25, 0x91, 0xac, 0x4d, 0xf2, + 0x9b, 0x1a, 0x2d, 0x01, 0x39, 0x59, 0x02, 0x26, 0x7e, 0xae, 0xf9, 0x04, 0xe0, 0x49, 0xc7, 0x83, 0xfc, 0x3b, 0x5e, + 0x33, 0x41, 0x64, 0x1b, 0xb9, 0x3f, 0x29, 0x9e, 0x23, 0x19, 0x41, 0xf1, 0x5d, 0xad, 0x32, 0x16, 0x86, 0x79, 0xa0, + 0x80, 0xbc, 0x07, 0x77, 0xea, 0x5b, 0xfb, 0x63, 0xc7, 0x9e, 0xad, 0x55, 0xa8, 0x85, 0x9a, 0xc2, 0x25, 0x87, 0xe8, + 0x0a, 0x32, 0x50, 0xc8, 0x78, 0xf2, 0x7a, 0x70, 0x3d, 0x89, 0x6e, 0xb8, 0x40, 0x67, 0x7c, 0x7d, 0xd3, 0x4d, 0x67, + 0xd1, 0x77, 0xf9, 0x7c, 0x42, 0x4a, 0xb2, 0xe3, 0x31, 0x1b, 0x55, 0x75, 0xb1, 0x99, 0x86, 0xf2, 0xa7, 0x87, 0xe0, + 0xeb, 0x05, 0xf5, 0x9a, 0xac, 0x52, 0xfd, 0x1d, 0x55, 0xca, 0x8b, 0x86, 0xd7, 0xfe, 0x77, 0xb9, 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, 0x93, 0xab, 0x0d, 0x67, + 0xae, 0x21, 0xb8, 0x2f, 0x2f, 0xd3, 0xd1, 0x02, 0x3e, 0xa4, 0xba, 0xbd, 0xc4, 0xcf, 0x87, 0x0d, 0x8f, 0x80, 0xcc, + 0x8e, 0xf8, 0xcc, 0x26, 0x92, 0x4e, 0xea, 0x52, 0x01, 0xbb, 0x5d, 0xbc, 0x05, 0x39, 0x62, 0xe6, 0xbe, 0x42, 0xf5, + 0x2d, 0x1a, 0x70, 0x65, 0xac, 0x7d, 0x4d, 0x32, 0x16, 0xde, 0x94, 0xd3, 0x70, 0x90, 0xc3, 0x73, 0xfa, 0xda, 0x72, + 0x9b, 0x65, 0x3f, 0x17, 0x10, 0x04, 0x51, 0x12, 0x8f, 0x0f, 0x78, 0x5f, 0xe6, 0x43, 0x8d, 0x92, 0x8f, 0x65, 0x23, + 0x95, 0x5e, 0x89, 0xfe, 0x6e, 0xcc, 0x25, 0x06, 0x7c, 0x9b, 0xb7, 0x05, 0x85, 0xcb, 0xea, 0x78, 0xbc, 0xac, 0x46, + 0xc6, 0xb3, 0x0c, 0x54, 0x2b, 0xd3, 0x3a, 0x88, 0xcd, 0x7c, 0xb1, 0xf0, 0x17, 0x3b, 0x27, 0x11, 0x51, 0x10, 0xd8, + 0x91, 0xf0, 0x20, 0x52, 0xbf, 0xcc, 0x3d, 0xdd, 0xa9, 0x3e, 0x3b, 0x2c, 0x6c, 0x22, 0xbd, 0xa0, 0x64, 0xf2, 0x49, + 0x70, 0x50, 0xfd, 0x1d, 0x84, 0x0d, 0xe1, 0xcd, 0xab, 0x5e, 0x67, 0x99, 0x9a, 0x95, 0x20, 0x61, 0xc6, 0x1c, 0xc1, + 0xe3, 0xb0, 0xd3, 0xd8, 0x96, 0xc7, 0x16, 0x1c, 0x9d, 0xb7, 0x61, 0x2b, 0xb6, 0x66, 0x77, 0xaa, 0x4e, 0x0b, 0x1e, + 0x4e, 0x87, 0xd7, 0x01, 0xae, 0xbe, 0xcd, 0x25, 0xe7, 0x2b, 0x3a, 0xc1, 0x36, 0x03, 0x1e, 0x4d, 0xc4, 0x6c, 0xf3, + 0x5d, 0xa4, 0x16, 0xcf, 0x66, 0xc8, 0x17, 0xb4, 0xfe, 0xc4, 0x6c, 0x65, 0x92, 0x57, 0x03, 0xbe, 0x98, 0x6c, 0xbe, + 0x8b, 0xe0, 0xd5, 0xef, 0xc0, 0x8a, 0x91, 0x39, 0xb3, 0x6c, 0xf3, 0x5d, 0x84, 0x63, 0xb6, 0xfa, 0x2e, 0xa2, 0x51, + 0x5b, 0xcb, 0x7d, 0xe9, 0xae, 0x01, 0x61, 0xe5, 0x8e, 0xc5, 0xf0, 0x1a, 0x88, 0x67, 0xda, 0x48, 0xba, 0x91, 0x86, + 0xde, 0x98, 0x87, 0xd3, 0x38, 0xd8, 0x50, 0x2b, 0xe4, 0x99, 0x21, 0x66, 0xf1, 0x77, 0xd1, 0x9c, 0xad, 0xb1, 0x22, + 0x5b, 0x1e, 0x0f, 0xae, 0x27, 0xdb, 0x1b, 0xbe, 0x01, 0xf2, 0xb3, 0xc9, 0xd6, 0x6c, 0x51, 0x77, 0x5c, 0xcc, 0xb6, + 0xdf, 0x45, 0xf3, 0xc9, 0x1a, 0x7a, 0xd6, 0x1e, 0x30, 0xef, 0x35, 0x88, 0x50, 0x12, 0x52, 0x53, 0x6e, 0x7a, 0x3d, + 0xb6, 0x19, 0x07, 0x2b, 0xb6, 0xb9, 0x0e, 0xee, 0xd8, 0x66, 0x0c, 0x44, 0x1c, 0xd4, 0xef, 0xde, 0x16, 0x16, 0x5f, + 0xc4, 0x36, 0xd7, 0x26, 0x6d, 0xfb, 0x5d, 0xc4, 0xdc, 0xc1, 0x69, 0xe0, 0x82, 0xb5, 0xcd, 0xbc, 0x35, 0x83, 0x4b, + 0xc8, 0xd2, 0x8b, 0xd9, 0x76, 0x78, 0xcd, 0x36, 0x23, 0x9c, 0xea, 0x89, 0xcf, 0x56, 0xfc, 0x8e, 0x25, 0x7c, 0xdd, + 0xc4, 0x37, 0x5b, 0xd0, 0x88, 0x9e, 0x64, 0xd0, 0x57, 0x50, 0x33, 0x73, 0x5e, 0x5a, 0x50, 0xb9, 0x87, 0x16, 0x1c, + 0x50, 0x90, 0xb6, 0x01, 0x82, 0x24, 0x9e, 0xdd, 0xcb, 0x70, 0x73, 0x2b, 0x85, 0x01, 0x37, 0x81, 0x19, 0x30, 0x30, + 0xfd, 0x0c, 0x7e, 0x58, 0xe9, 0x12, 0x21, 0xce, 0x7e, 0x4a, 0x49, 0x32, 0xcf, 0x4f, 0x45, 0x9a, 0xbb, 0x85, 0xeb, + 0x14, 0x66, 0x45, 0x81, 0xea, 0xa7, 0xa4, 0x34, 0xb0, 0x50, 0x89, 0x4c, 0xa5, 0xe0, 0x97, 0xcd, 0x79, 0x94, 0x9d, + 0xa2, 0x73, 0x5d, 0x5e, 0x4f, 0x9c, 0xd3, 0x49, 0xdf, 0x7f, 0xe0, 0x18, 0xb6, 0x90, 0x81, 0x0b, 0x7f, 0xea, 0x09, + 0xe3, 0xd4, 0x0a, 0xc4, 0x54, 0xf2, 0xec, 0x29, 0x7c, 0x26, 0xb4, 0x3a, 0xba, 0xf0, 0xfd, 0xa0, 0xd0, 0x26, 0xe9, + 0x16, 0x24, 0x29, 0x78, 0x8a, 0x5e, 0x72, 0xde, 0x06, 0x2a, 0xc5, 0x88, 0x16, 0x44, 0xda, 0x5a, 0x66, 0x0e, 0xd2, + 0x96, 0xe6, 0xbb, 0x26, 0x7e, 0x0e, 0x0b, 0xb8, 0x88, 0x16, 0x76, 0xa5, 0xe0, 0x51, 0x15, 0x2b, 0xf7, 0x36, 0xcf, + 0x11, 0xce, 0xe8, 0x5a, 0x26, 0x00, 0xae, 0xf7, 0xab, 0xb0, 0x56, 0x78, 0x45, 0xcd, 0x22, 0x2f, 0x6a, 0xfa, 0x64, + 0x0b, 0xdc, 0xc7, 0xa2, 0x44, 0x81, 0xb3, 0x16, 0x0c, 0xd8, 0x0a, 0x4b, 0x76, 0x52, 0xd8, 0x14, 0x2d, 0xa1, 0x77, + 0xc0, 0x4f, 0x07, 0x35, 0x93, 0x01, 0x34, 0x01, 0x34, 0x1e, 0xff, 0x02, 0x50, 0xd3, 0xdb, 0x5a, 0x6c, 0xaa, 0xa0, + 0x54, 0xca, 0x4d, 0xf8, 0x19, 0x18, 0x66, 0xf8, 0xa1, 0x90, 0xdb, 0x44, 0x89, 0x9c, 0x1f, 0x8b, 0x52, 0x2c, 0x4b, + 0x51, 0x25, 0xed, 0x86, 0x82, 0x47, 0x84, 0xdb, 0xa0, 0x31, 0x73, 0x7b, 0xa2, 0x8b, 0x56, 0x84, 0x72, 0x6c, 0x37, + 0x31, 0xd2, 0x28, 0xb3, 0xb3, 0x5d, 0x27, 0x0b, 0xed, 0xf7, 0x55, 0x0e, 0x59, 0x07, 0xac, 0x91, 0x7c, 0xbd, 0xe6, + 0xd0, 0x6d, 0xa3, 0xbc, 0x78, 0xf0, 0x7c, 0x05, 0xa7, 0x39, 0x9e, 0xd8, 0x5d, 0xaf, 0x3b, 0x45, 0x22, 0x5e, 0xe1, + 0xa4, 0xaa, 0x46, 0xb2, 0x70, 0xdc, 0xb9, 0xd3, 0x5a, 0xac, 0x95, 0x4c, 0xe3, 0x72, 0xd0, 0x00, 0xd0, 0x0c, 0x3e, + 0x95, 0x47, 0x7b, 0xa1, 0x6d, 0x51, 0x2c, 0x84, 0xd1, 0xa3, 0x13, 0x7e, 0x52, 0x02, 0xeb, 0xeb, 0x70, 0x58, 0xfa, + 0x11, 0x47, 0xbf, 0xd3, 0x68, 0xb4, 0x20, 0xa4, 0xe1, 0xa9, 0x17, 0x8d, 0x16, 0x75, 0x51, 0x87, 0xd9, 0xf3, 0x4a, + 0x0f, 0x14, 0x86, 0x11, 0xa8, 0x1f, 0x5c, 0x65, 0xf0, 0x59, 0x84, 0xa8, 0x79, 0x60, 0x9a, 0x0d, 0xe1, 0xa8, 0x0b, + 0x3c, 0xb4, 0x82, 0x16, 0x33, 0xf3, 0x51, 0x88, 0xe1, 0x43, 0xba, 0x38, 0x7f, 0x42, 0x56, 0x3e, 0xc0, 0xee, 0xd0, + 0x5d, 0x28, 0xe7, 0x4c, 0xce, 0x00, 0x3f, 0x0a, 0xc8, 0x47, 0x09, 0xb8, 0x19, 0x20, 0x7b, 0x64, 0x09, 0x20, 0x56, + 0x8c, 0x4e, 0x26, 0x9f, 0xfb, 0x5e, 0xa4, 0xe0, 0x9d, 0x7d, 0x56, 0xa9, 0x09, 0x43, 0xe1, 0x13, 0x03, 0xdd, 0xfc, + 0xc6, 0x6f, 0xcf, 0x5b, 0x30, 0xb2, 0x4b, 0x52, 0xbc, 0xd6, 0x0c, 0xf7, 0x1b, 0x70, 0x3b, 0x02, 0xca, 0x9a, 0xea, + 0x94, 0x64, 0x9b, 0x86, 0x48, 0x06, 0xcc, 0x88, 0x11, 0x41, 0x65, 0xb9, 0xf0, 0xbf, 0x07, 0x59, 0x14, 0x38, 0x80, + 0xab, 0x99, 0x0c, 0x5e, 0xbb, 0x30, 0x2a, 0x00, 0xce, 0x69, 0xe8, 0x94, 0x0e, 0xaa, 0xea, 0x90, 0xac, 0x9a, 0x1f, + 0xcc, 0xe6, 0x4d, 0xc3, 0xc4, 0x88, 0x20, 0xba, 0x08, 0x27, 0x98, 0x5e, 0x91, 0xbe, 0x56, 0x72, 0x3a, 0x5a, 0x75, + 0xb4, 0x96, 0x98, 0x98, 0x2b, 0x8a, 0xbf, 0x06, 0x3c, 0x6e, 0xf0, 0xea, 0x24, 0x4d, 0x27, 0xaa, 0x47, 0x4f, 0x5f, + 0xa7, 0xe9, 0xa4, 0xc4, 0x5d, 0xe1, 0x37, 0xe0, 0xa2, 0xd9, 0xe6, 0x43, 0x3f, 0x7d, 0x41, 0x11, 0x17, 0x35, 0xb8, + 0xf2, 0xce, 0xf5, 0x95, 0xea, 0x23, 0xa8, 0x85, 0x27, 0x46, 0xd6, 0xc2, 0x93, 0x4b, 0xd6, 0x5a, 0x10, 0xcc, 0x6c, + 0x0e, 0x5c, 0xc8, 0xaf, 0x94, 0x22, 0xde, 0x46, 0x42, 0x2d, 0x06, 0xad, 0xc7, 0xac, 0x58, 0x3e, 0x5a, 0xa8, 0xcc, + 0x08, 0xed, 0xdb, 0x5a, 0x74, 0x7e, 0x23, 0x3f, 0xe5, 0xa9, 0x7d, 0xd9, 0x1e, 0xe7, 0xd3, 0x3d, 0xba, 0xab, 0xce, + 0x32, 0x93, 0x32, 0x3e, 0x99, 0x25, 0x28, 0xdc, 0x25, 0xd8, 0x80, 0x24, 0xfb, 0xb5, 0x0e, 0x90, 0x51, 0x7b, 0xed, + 0x77, 0x9d, 0xe5, 0x5b, 0xa9, 0x66, 0x6b, 0x28, 0x2a, 0xb5, 0x92, 0x14, 0x07, 0x19, 0xae, 0xdb, 0xdc, 0x87, 0xcd, + 0x14, 0xf4, 0x8c, 0x91, 0xc8, 0x3c, 0x7f, 0x22, 0x5f, 0x82, 0x73, 0xc6, 0x59, 0x21, 0x30, 0x61, 0xac, 0xde, 0xb5, + 0x96, 0x4a, 0x43, 0x8a, 0xb1, 0x93, 0x51, 0x96, 0x55, 0x96, 0x2e, 0xb3, 0xb5, 0x84, 0x2d, 0xab, 0xc8, 0x2d, 0x6c, + 0x99, 0xc9, 0x6a, 0xbe, 0xcf, 0xb9, 0x83, 0xf2, 0xcd, 0x36, 0x19, 0x3f, 0x48, 0x64, 0xef, 0x36, 0x50, 0xc2, 0xf3, + 0xd1, 0x7f, 0x20, 0xfd, 0x36, 0xc3, 0x38, 0xe5, 0xb6, 0x92, 0x16, 0xe0, 0xf4, 0x8f, 0xc7, 0xf7, 0x39, 0x06, 0x0d, + 0x8e, 0x30, 0x8e, 0xac, 0xdf, 0xbf, 0xcb, 0xbd, 0x1a, 0x13, 0x75, 0xf4, 0x42, 0xbf, 0x9f, 0xd3, 0xc3, 0x69, 0x3e, + 0x5a, 0xa7, 0x3b, 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, 0xe9, 0x81, 0x05, 0x0e, 0x30, 0x39, 0xff, 0x87, 0xf0, 0x66, + 0x60, 0x83, 0x46, 0x89, 0xbe, 0x46, 0x57, 0xb5, 0xb9, 0xd1, 0xf1, 0xd2, 0x53, 0x48, 0x64, 0x05, 0xcb, 0xe7, 0xbe, + 0xdc, 0xc0, 0x69, 0x0f, 0x35, 0x87, 0xca, 0x12, 0x3c, 0x3d, 0x97, 0xf9, 0xf1, 0xb8, 0xc9, 0xa0, 0xb0, 0xfd, 0x46, + 0x68, 0x6f, 0xcc, 0x52, 0x0d, 0x15, 0xe1, 0xa0, 0xf3, 0xb5, 0x98, 0xd5, 0x23, 0xfa, 0x7b, 0x7e, 0x3c, 0xae, 0x09, + 0x0c, 0x38, 0x2c, 0x65, 0x26, 0x5a, 0x28, 0x96, 0xd6, 0xd9, 0x8c, 0xea, 0xc0, 0x03, 0x13, 0x73, 0x16, 0xee, 0x01, + 0xb4, 0x49, 0xad, 0x02, 0xbd, 0x8a, 0xe8, 0x27, 0xee, 0xd7, 0xf6, 0xeb, 0xf5, 0xc8, 0x2c, 0x1d, 0xb9, 0x31, 0x16, + 0x00, 0x1c, 0x78, 0x59, 0x93, 0x3c, 0x27, 0x5f, 0x43, 0xbb, 0x27, 0x17, 0xf2, 0x27, 0x28, 0x5b, 0x78, 0xa5, 0x9a, + 0x56, 0x16, 0x6b, 0xae, 0xaa, 0x57, 0x17, 0x3c, 0x37, 0x99, 0xd6, 0x69, 0x25, 0x54, 0xac, 0x5f, 0x43, 0x5d, 0xe2, + 0xb5, 0xa6, 0x19, 0xa5, 0x36, 0xea, 0x4c, 0xd4, 0x80, 0x0d, 0xf6, 0x53, 0xb5, 0xd1, 0xc9, 0xb9, 0x7c, 0x79, 0x6d, + 0x1c, 0x3e, 0xed, 0xea, 0xcd, 0x4c, 0xe5, 0xc0, 0x5f, 0xab, 0x1a, 0x5a, 0x3d, 0x06, 0x3a, 0x20, 0xa7, 0x3f, 0x86, + 0xc5, 0xc4, 0xee, 0xd0, 0xaa, 0xdd, 0x5d, 0x56, 0x17, 0xe9, 0x9d, 0xa6, 0x64, 0x56, 0x6f, 0xf9, 0xcc, 0xea, 0xd1, + 0x01, 0x2f, 0x1e, 0xeb, 0xbd, 0xc2, 0x4c, 0x22, 0xb8, 0x18, 0xaa, 0x49, 0x64, 0x77, 0xa0, 0x35, 0x8f, 0x72, 0x26, + 0xc0, 0x0f, 0x4a, 0xad, 0xe9, 0x83, 0xdd, 0x15, 0xea, 0x94, 0xc2, 0xe3, 0xd6, 0x92, 0x1f, 0x98, 0x3b, 0xed, 0x5a, + 0xe7, 0xe3, 0xe5, 0xb5, 0xef, 0x37, 0xf2, 0x84, 0x36, 0x3b, 0x93, 0xd3, 0x3f, 0x55, 0xab, 0x7f, 0x98, 0xea, 0x5b, + 0xe8, 0x4e, 0xd0, 0x67, 0xe8, 0xaa, 0xea, 0xae, 0xc4, 0x16, 0x86, 0x7a, 0x62, 0x91, 0x17, 0xf2, 0xa4, 0x35, 0x76, + 0x1c, 0xec, 0x0d, 0x70, 0xe2, 0x97, 0xc7, 0xa3, 0xb8, 0xa9, 0x7c, 0x76, 0xd9, 0x35, 0xb2, 0x72, 0x00, 0x73, 0x88, + 0x82, 0x71, 0x6b, 0x3e, 0xb6, 0x41, 0xba, 0xc4, 0xcd, 0xf8, 0xf4, 0x0d, 0xc5, 0x32, 0xd9, 0x44, 0x5c, 0x5c, 0x55, + 0xdf, 0x3d, 0x03, 0xd2, 0xb2, 0x7e, 0x3f, 0x7a, 0x7e, 0x3d, 0x7d, 0x36, 0x8c, 0x02, 0x70, 0xec, 0xb2, 0x97, 0x97, + 0x31, 0x5f, 0x5d, 0x33, 0xcb, 0x14, 0x16, 0xf9, 0x66, 0x40, 0x75, 0xc9, 0x6a, 0xe9, 0x7a, 0x05, 0x58, 0xba, 0xfc, + 0xe6, 0x21, 0x4c, 0x0d, 0x68, 0x64, 0xcd, 0xdd, 0x69, 0xae, 0x05, 0x4a, 0x3d, 0xef, 0x67, 0x86, 0x7c, 0x5d, 0x06, + 0x5d, 0x41, 0xba, 0xe7, 0x11, 0xe9, 0xe5, 0x41, 0x3a, 0xdd, 0x1f, 0x4a, 0x01, 0x96, 0xfa, 0x52, 0x7c, 0x06, 0x85, + 0x45, 0xe3, 0x1b, 0x01, 0xda, 0x1a, 0xaa, 0x69, 0xaf, 0x14, 0x55, 0x2f, 0xe8, 0x95, 0xe2, 0x73, 0x4f, 0x0f, 0x95, + 0xf9, 0xb2, 0x74, 0xf4, 0x3f, 0xa3, 0xe6, 0x82, 0x13, 0x62, 0x26, 0xe6, 0x00, 0x2a, 0x41, 0x1b, 0xdf, 0xfa, 0x64, + 0xe3, 0x53, 0xbd, 0x8a, 0x9b, 0x3e, 0xaf, 0xad, 0x65, 0x4e, 0x08, 0x9b, 0xee, 0x25, 0x40, 0x45, 0x5e, 0x09, 0x8f, + 0x60, 0xf9, 0xe5, 0x0f, 0x79, 0xba, 0x42, 0xb4, 0x8e, 0x7b, 0x96, 0xb9, 0x34, 0xf6, 0xaf, 0x0d, 0xa6, 0xaf, 0x6f, + 0xb7, 0x45, 0x7e, 0x6a, 0x62, 0xc2, 0x7a, 0xac, 0xe8, 0x9b, 0x77, 0xe1, 0x5a, 0xa0, 0xc0, 0xa1, 0x44, 0x62, 0x9b, + 0x2a, 0x14, 0xf1, 0x20, 0xe9, 0xd3, 0x45, 0xeb, 0xd3, 0x00, 0x53, 0x6b, 0x39, 0x30, 0x87, 0x70, 0x15, 0x17, 0x3e, + 0x7a, 0xfa, 0x16, 0xb3, 0x70, 0x3e, 0xf1, 0x3e, 0x7a, 0xc5, 0xc8, 0x7c, 0xdc, 0x47, 0xa5, 0x92, 0xfe, 0x79, 0x3c, + 0xce, 0xf2, 0xb9, 0xef, 0xd0, 0x47, 0x7a, 0xa8, 0x72, 0x41, 0xd9, 0x1b, 0x63, 0x12, 0x81, 0xd2, 0x18, 0xef, 0xe3, + 0xe0, 0x38, 0xef, 0xd3, 0x00, 0x52, 0xfb, 0xc4, 0x7b, 0x52, 0x72, 0x78, 0xce, 0x31, 0x27, 0x94, 0x56, 0x84, 0xe5, + 0x7c, 0x91, 0xa1, 0x5c, 0x77, 0x4e, 0xc1, 0x24, 0x87, 0x04, 0xc3, 0x5f, 0x35, 0x6f, 0x62, 0x05, 0xc2, 0xae, 0x91, + 0x33, 0x47, 0x4f, 0xaa, 0x24, 0x2c, 0x05, 0x1c, 0x95, 0x99, 0x67, 0xd8, 0x1b, 0x9e, 0x18, 0x46, 0x0e, 0x56, 0xf9, + 0xa3, 0x3a, 0x11, 0xb9, 0x47, 0x17, 0x18, 0x95, 0x85, 0x57, 0x0d, 0x5d, 0x69, 0x50, 0x49, 0x76, 0xfa, 0x15, 0xd7, + 0x80, 0xda, 0x1a, 0x23, 0x96, 0x83, 0x80, 0x51, 0xf0, 0xda, 0xfe, 0x10, 0xb2, 0x28, 0x5b, 0xbf, 0xc1, 0x31, 0x9f, + 0x95, 0xdc, 0xf5, 0x0e, 0x67, 0xa1, 0x25, 0xe4, 0xc9, 0x1d, 0x83, 0x34, 0x8d, 0xa5, 0x11, 0x70, 0x22, 0x92, 0x6d, + 0x2c, 0x85, 0x23, 0x80, 0x80, 0x40, 0x37, 0x65, 0x86, 0x31, 0x1d, 0x8c, 0x3c, 0x4f, 0x7a, 0xc6, 0x7b, 0x15, 0x9e, + 0x42, 0x9a, 0x6c, 0x5f, 0xcf, 0x3f, 0xe4, 0x5a, 0x90, 0x95, 0x5b, 0xce, 0xe9, 0xb0, 0xf8, 0xc6, 0xd9, 0x57, 0x39, + 0x79, 0x8a, 0x59, 0x46, 0x7a, 0xa7, 0x98, 0x17, 0xf0, 0xa7, 0xb2, 0xd4, 0x93, 0xf4, 0x96, 0xf9, 0x64, 0x15, 0x49, + 0x97, 0xde, 0xb6, 0xdf, 0x8f, 0x47, 0xea, 0x50, 0xf3, 0x0f, 0xf1, 0x48, 0x9e, 0x61, 0x5b, 0x96, 0xb0, 0xd0, 0x2a, + 0x18, 0x03, 0x48, 0x62, 0x23, 0xa2, 0xc1, 0x68, 0x6f, 0x8f, 0xc7, 0xcb, 0xad, 0x39, 0x4b, 0x0e, 0xe0, 0xfa, 0xca, + 0x13, 0xf3, 0x0e, 0x7c, 0x99, 0xc7, 0x04, 0x11, 0x9b, 0x79, 0x5b, 0x56, 0x83, 0x07, 0x3b, 0xb8, 0x3e, 0x62, 0x8b, + 0x62, 0xad, 0x63, 0xc9, 0xad, 0x83, 0xd3, 0x3a, 0x36, 0xcd, 0x48, 0x29, 0xb2, 0xcf, 0xb1, 0x7f, 0x70, 0x83, 0xab, + 0x6b, 0x63, 0x50, 0x6b, 0xdc, 0x61, 0xee, 0x9c, 0x0a, 0xa8, 0xc7, 0x74, 0x05, 0xd5, 0x8b, 0x8a, 0x7c, 0xf9, 0xad, + 0x9d, 0x03, 0x82, 0x46, 0x20, 0x70, 0xd1, 0x40, 0xab, 0x76, 0x29, 0xe7, 0x5d, 0x40, 0x88, 0x6f, 0x52, 0xd0, 0xa7, + 0x33, 0xd8, 0xc4, 0xe6, 0x13, 0x88, 0x45, 0xd3, 0x7d, 0xae, 0x35, 0xf3, 0xc5, 0x88, 0x76, 0x66, 0xdd, 0x2d, 0x72, + 0xab, 0x85, 0x48, 0x46, 0xcf, 0x36, 0x13, 0x2e, 0x3a, 0x94, 0x33, 0x12, 0x30, 0x41, 0x6b, 0x2b, 0x25, 0x9f, 0xeb, + 0x41, 0x27, 0x68, 0x0f, 0x24, 0xad, 0xfb, 0x37, 0x8b, 0xce, 0x28, 0x39, 0xb9, 0xde, 0xe4, 0x0c, 0x52, 0xb0, 0x60, + 0x07, 0x99, 0x13, 0x6e, 0x80, 0x4f, 0x6c, 0x96, 0x9c, 0xa6, 0x41, 0x1e, 0x0b, 0xe3, 0x91, 0xd7, 0xe6, 0x97, 0x05, + 0x74, 0x28, 0x59, 0x34, 0x42, 0x3c, 0xc0, 0xce, 0x21, 0xb9, 0x2a, 0x50, 0x37, 0x0d, 0x74, 0xe5, 0xca, 0x99, 0x62, + 0x0a, 0x5c, 0x08, 0x05, 0x51, 0x3b, 0x3a, 0x89, 0xca, 0x79, 0x9f, 0x54, 0x97, 0xd5, 0xb4, 0x90, 0xa6, 0x81, 0x6a, + 0x9a, 0x3b, 0xe6, 0x81, 0xbd, 0x6d, 0x5c, 0x13, 0x18, 0xe8, 0xd4, 0xbe, 0x16, 0x55, 0xfc, 0x83, 0x74, 0x2f, 0xce, + 0xe1, 0x2f, 0x6b, 0xfa, 0x20, 0xc2, 0x46, 0x0e, 0x1a, 0x4b, 0x89, 0xb1, 0x51, 0xe1, 0xdf, 0x12, 0x65, 0x43, 0x86, + 0x80, 0x10, 0xd2, 0x46, 0x45, 0x3f, 0xac, 0x2f, 0xef, 0x32, 0xed, 0xff, 0x49, 0xe2, 0xb7, 0xc1, 0x5e, 0x4e, 0xfd, + 0xa9, 0x47, 0x3c, 0x5e, 0x1b, 0xf4, 0x98, 0x92, 0x6e, 0x83, 0x3c, 0x55, 0x9e, 0x82, 0x64, 0xc2, 0x58, 0x42, 0xb0, + 0x28, 0x17, 0xbc, 0xe2, 0x39, 0x97, 0x70, 0x1f, 0xb5, 0xac, 0x88, 0x50, 0x95, 0xa8, 0xe8, 0xf3, 0x39, 0xf0, 0x4c, + 0x40, 0xa0, 0x63, 0x8c, 0x34, 0xaa, 0xe0, 0x4b, 0x60, 0xac, 0x03, 0x65, 0xa7, 0x19, 0x09, 0x2e, 0xbb, 0xb7, 0x48, + 0x94, 0xfa, 0x8a, 0x94, 0xa4, 0x6f, 0x45, 0x8d, 0x57, 0x62, 0x15, 0x91, 0x40, 0x86, 0x1a, 0x22, 0x56, 0xd5, 0x53, + 0xf7, 0xa6, 0x98, 0x0c, 0x06, 0xb9, 0x2f, 0xa7, 0x27, 0xde, 0xd0, 0x50, 0x79, 0xd7, 0x15, 0xed, 0xf4, 0x44, 0x2b, + 0xe5, 0x2d, 0xa4, 0x25, 0x68, 0x1a, 0x46, 0x9a, 0x43, 0xa9, 0x6b, 0xe9, 0x6e, 0x0c, 0xe2, 0x4b, 0x26, 0x7a, 0xb6, + 0x53, 0x3b, 0x4a, 0x5b, 0xd2, 0x1e, 0x42, 0x7a, 0xee, 0x92, 0x8f, 0x21, 0x42, 0x4c, 0x55, 0xa5, 0xbc, 0x09, 0xd1, + 0xc9, 0xfd, 0x80, 0x21, 0x11, 0xe8, 0x73, 0x8e, 0x61, 0x5d, 0x34, 0xd4, 0x18, 0x6c, 0x6d, 0xb6, 0x50, 0xc2, 0x7c, + 0xc9, 0x78, 0x2a, 0x19, 0x34, 0x00, 0x32, 0xe0, 0xb3, 0x97, 0x81, 0xe5, 0xaf, 0x20, 0x7e, 0xb4, 0xf1, 0xf1, 0xf8, + 0x67, 0x4d, 0x21, 0xb6, 0x7f, 0xc2, 0x66, 0x08, 0x8f, 0xea, 0x01, 0xcf, 0x7c, 0x13, 0x27, 0x68, 0x05, 0x24, 0x65, + 0x76, 0x34, 0x91, 0xbd, 0xea, 0x21, 0x9c, 0xca, 0x0a, 0xd4, 0x51, 0xd6, 0x59, 0x09, 0x3f, 0xc2, 0x54, 0xb7, 0x12, + 0x6b, 0x81, 0x36, 0x57, 0x2b, 0xd6, 0x02, 0x38, 0xf0, 0x2b, 0x08, 0x9e, 0xa8, 0xe6, 0xe0, 0x62, 0x50, 0x80, 0xcf, + 0x01, 0xf0, 0x22, 0x77, 0xe1, 0xc1, 0x3c, 0xb2, 0xac, 0x46, 0x18, 0x8e, 0x2a, 0x62, 0xfd, 0x9a, 0xed, 0xc8, 0x07, + 0x6e, 0xc7, 0xf8, 0x5c, 0x7b, 0x2c, 0x59, 0x0e, 0x46, 0x99, 0x7b, 0xb5, 0x44, 0xcf, 0x9b, 0x34, 0x6e, 0x46, 0x4f, + 0x0e, 0xb5, 0xfc, 0x5f, 0xd0, 0xcb, 0xa0, 0xbf, 0x85, 0x5b, 0x5e, 0xf3, 0xbb, 0x05, 0x91, 0x66, 0x7a, 0x05, 0x91, + 0x32, 0x6a, 0x44, 0xc6, 0x10, 0x36, 0xa9, 0x6e, 0x65, 0x93, 0xea, 0x42, 0xc0, 0xd3, 0x09, 0xa9, 0xae, 0x85, 0xb4, + 0x51, 0x4d, 0xeb, 0x40, 0xc6, 0x22, 0xbd, 0xfb, 0xf1, 0x2f, 0x2f, 0x3e, 0xbd, 0xf9, 0xe5, 0xc7, 0xc5, 0x9b, 0x77, + 0xaf, 0xdf, 0xbc, 0x7b, 0xf3, 0xe9, 0x37, 0x82, 0xf0, 0x98, 0x0a, 0x95, 0xe1, 0xc3, 0xfb, 0xdb, 0x37, 0x4e, 0x06, + 0xdb, 0x9b, 0x21, 0x6b, 0xdf, 0xc8, 0xc1, 0x10, 0x88, 0x6c, 0x10, 0x32, 0xc8, 0x4e, 0x6d, 0xfb, 0x33, 0x31, 0xc7, + 0xd8, 0x3b, 0x81, 0xc9, 0x16, 0x24, 0x87, 0x65, 0x5e, 0x32, 0x22, 0x57, 0x8e, 0xd6, 0x0f, 0x68, 0xc1, 0x5b, 0x70, + 0x91, 0x49, 0xf3, 0xd5, 0x2f, 0x04, 0xb1, 0x4f, 0x2b, 0xa9, 0xf2, 0xd5, 0xb6, 0xe6, 0xf9, 0xf6, 0x7e, 0x9f, 0xd3, + 0x8a, 0x99, 0x4b, 0x23, 0x6a, 0x01, 0x0e, 0xc0, 0x97, 0xf0, 0xc7, 0x8d, 0xb6, 0xa4, 0xc9, 0x2c, 0xfa, 0x2c, 0x84, + 0xa0, 0x4b, 0x03, 0x69, 0x62, 0x8f, 0xbc, 0xd4, 0x27, 0x0b, 0x09, 0xdc, 0x11, 0xc3, 0xa7, 0x15, 0x41, 0xaf, 0x18, + 0x51, 0x5c, 0x72, 0x85, 0x4a, 0x29, 0xf9, 0x37, 0xca, 0x2e, 0x2a, 0xe4, 0xac, 0x60, 0xf7, 0x8a, 0x1c, 0x19, 0x3f, + 0x08, 0x26, 0xbe, 0x0a, 0xdc, 0x7f, 0x89, 0x77, 0x38, 0x53, 0x1c, 0xc9, 0x09, 0x7f, 0xc8, 0x30, 0xb0, 0xbf, 0x02, + 0x9f, 0x57, 0x87, 0x79, 0x79, 0xab, 0x4f, 0xb9, 0x25, 0x1f, 0x4f, 0x96, 0x37, 0x60, 0xb0, 0x5f, 0xaa, 0xe6, 0x6e, + 0x78, 0x3d, 0x5b, 0xce, 0xd9, 0x61, 0x16, 0xcd, 0x83, 0x15, 0x9b, 0x65, 0xf3, 0x60, 0xdd, 0xf0, 0x0d, 0xbb, 0xe3, + 0x1b, 0xab, 0x6a, 0x1b, 0xbb, 0x6a, 0x93, 0x2d, 0xbf, 0x03, 0x09, 0xe1, 0x36, 0xf3, 0x72, 0x96, 0xb0, 0x95, 0xcf, + 0xb6, 0x20, 0xd1, 0xae, 0xd9, 0x16, 0x2e, 0x62, 0x1b, 0xfe, 0x63, 0xee, 0x6d, 0x59, 0xc9, 0x2e, 0xc7, 0xac, 0xc2, + 0xf9, 0xe7, 0xc3, 0x03, 0xda, 0x0b, 0xf5, 0xb3, 0x6b, 0xf5, 0x6c, 0xa2, 0xec, 0x66, 0xdb, 0xd1, 0xe2, 0x3e, 0xad, + 0xb6, 0x61, 0x86, 0x9e, 0xe5, 0xf0, 0xd1, 0x56, 0x0a, 0x7e, 0x7a, 0x81, 0x5f, 0xb2, 0xa3, 0xb6, 0xd2, 0xb6, 0x5d, + 0x95, 0xd8, 0x0a, 0x5a, 0x14, 0x59, 0xad, 0xf0, 0xc0, 0x8a, 0x3f, 0x87, 0x05, 0x8c, 0x3d, 0xc7, 0x39, 0xaf, 0xfd, + 0x11, 0x32, 0xde, 0x3b, 0x00, 0x68, 0x99, 0xe3, 0x00, 0x8f, 0x58, 0x31, 0x8a, 0x06, 0xef, 0xf2, 0x5a, 0x59, 0xad, + 0x34, 0x27, 0xa1, 0x6d, 0xc4, 0xaa, 0xe5, 0x48, 0xd5, 0x8c, 0x48, 0x1f, 0xa4, 0xe7, 0x7d, 0x8f, 0xa8, 0x06, 0x7b, + 0x32, 0xaf, 0x03, 0xfb, 0xf4, 0xb2, 0xb5, 0xaa, 0x3b, 0xbf, 0xa7, 0x4a, 0x97, 0x1c, 0xd9, 0xf2, 0xd3, 0x65, 0xf8, + 0xa0, 0xfe, 0x94, 0x5c, 0x1f, 0x0a, 0x1c, 0xe1, 0xb1, 0x0a, 0x38, 0x5f, 0xcf, 0x45, 0xbb, 0x13, 0x61, 0x57, 0x2e, + 0x01, 0x21, 0xbe, 0xa4, 0x69, 0x8e, 0xc7, 0x11, 0x4d, 0x44, 0xd8, 0xc4, 0xe8, 0x2f, 0xec, 0x3e, 0x94, 0x58, 0x2e, + 0x2b, 0x0d, 0x4a, 0x2e, 0x19, 0xbc, 0x27, 0xed, 0x35, 0x68, 0x96, 0x57, 0xae, 0x26, 0x13, 0x39, 0x28, 0x1f, 0x8f, + 0x05, 0xec, 0xa5, 0xc6, 0x4f, 0x13, 0x7e, 0xc2, 0xf2, 0xd6, 0xde, 0x9a, 0x52, 0x54, 0xd2, 0x00, 0x15, 0xf8, 0x98, + 0xc1, 0xff, 0xee, 0x0c, 0xb1, 0x60, 0x8a, 0x4e, 0x1f, 0xce, 0xc4, 0xdc, 0x7a, 0x6e, 0x95, 0x75, 0x92, 0xad, 0x51, + 0x4e, 0xc0, 0xbf, 0xa5, 0x3a, 0x4e, 0x12, 0xe1, 0xd4, 0x7b, 0xc4, 0x45, 0xdd, 0xcb, 0x21, 0xea, 0x86, 0xbd, 0xc9, + 0x75, 0xb0, 0xe5, 0x34, 0x0d, 0x4e, 0xc4, 0xaf, 0xd4, 0x67, 0xef, 0x33, 0x8b, 0x47, 0x1d, 0xd9, 0x88, 0x92, 0x34, + 0x8e, 0x45, 0x0e, 0xdb, 0xfb, 0x42, 0xee, 0xff, 0xfd, 0x3e, 0x84, 0x93, 0x56, 0x41, 0x52, 0x7a, 0x02, 0x11, 0xe1, + 0xe8, 0xf0, 0x23, 0xc2, 0x13, 0xa9, 0x2a, 0x7c, 0x52, 0x9f, 0xb9, 0x31, 0xbb, 0x17, 0xe6, 0xa8, 0xde, 0x01, 0x0c, + 0x63, 0xbd, 0xb3, 0x08, 0x49, 0xb4, 0xd2, 0x8c, 0xb6, 0x1e, 0x10, 0x23, 0xde, 0x6f, 0x2c, 0x32, 0x18, 0x6b, 0x4b, + 0x22, 0x01, 0x7c, 0x45, 0x42, 0x86, 0xb6, 0x8d, 0xc0, 0x8c, 0xe1, 0xed, 0xac, 0xb8, 0x74, 0x1d, 0xb6, 0x39, 0x87, + 0x2f, 0x64, 0xa1, 0x59, 0x47, 0x94, 0x26, 0x08, 0xf9, 0x07, 0x9c, 0x2c, 0x14, 0x46, 0xf3, 0xea, 0x24, 0x9d, 0x24, + 0xd6, 0xf7, 0x5d, 0xa5, 0x82, 0xcd, 0xe6, 0x16, 0xf5, 0x65, 0x27, 0xc9, 0x2f, 0xc1, 0x49, 0xc7, 0x49, 0x16, 0x39, + 0x88, 0x5a, 0x54, 0xce, 0x6d, 0x12, 0x96, 0x76, 0x75, 0xaa, 0xed, 0x66, 0x53, 0x94, 0x75, 0xf5, 0x4a, 0x44, 0x8a, + 0xde, 0x47, 0x3d, 0x7a, 0x22, 0x21, 0x15, 0x5a, 0x95, 0xda, 0xe7, 0x11, 0xb8, 0x6d, 0x6a, 0xc5, 0xb6, 0x5c, 0xc2, + 0x12, 0x35, 0xfe, 0x13, 0xf4, 0x51, 0x2e, 0x1e, 0x64, 0x80, 0x46, 0xc7, 0x53, 0xf3, 0xd6, 0x23, 0xaf, 0x9c, 0xe4, + 0x97, 0x56, 0x9b, 0xf4, 0x0b, 0x20, 0x33, 0xda, 0x3f, 0x5a, 0x4a, 0x20, 0x33, 0x30, 0x93, 0x96, 0x86, 0x44, 0x8e, + 0x62, 0x96, 0xe6, 0x7f, 0xe0, 0x8a, 0xad, 0x10, 0x69, 0x58, 0xcd, 0x3d, 0xfe, 0x22, 0xf7, 0x6a, 0xb9, 0x96, 0x99, + 0xe6, 0x66, 0x89, 0x63, 0xc5, 0xe2, 0xa2, 0x5e, 0x57, 0x22, 0x0b, 0x84, 0x38, 0xc2, 0x34, 0xd6, 0x53, 0x6f, 0x94, + 0x56, 0x1f, 0x90, 0x50, 0xe6, 0x47, 0xec, 0xed, 0xd8, 0xeb, 0x41, 0x16, 0xe2, 0xd8, 0x72, 0xb0, 0xd9, 0x7a, 0x9f, + 0xca, 0x54, 0xc4, 0x17, 0x75, 0x71, 0xb1, 0xad, 0xc4, 0x45, 0x9d, 0x88, 0x8b, 0xef, 0x21, 0xe7, 0xf7, 0x17, 0x54, + 0xf4, 0xc5, 0x43, 0x5a, 0x27, 0xc5, 0xb6, 0xa6, 0x27, 0xaf, 0xb1, 0x8c, 0xef, 0x2f, 0x88, 0xab, 0xe6, 0x82, 0x46, + 0x32, 0x1e, 0x5d, 0x7c, 0xc8, 0x80, 0xe4, 0xf5, 0x22, 0x5d, 0xc3, 0xe0, 0x5d, 0x84, 0x79, 0x7c, 0x51, 0x8a, 0x15, + 0x58, 0x9c, 0xca, 0xce, 0xf7, 0x20, 0xc3, 0x3a, 0xfc, 0x43, 0x5c, 0x00, 0xb4, 0xeb, 0x45, 0x5a, 0x5f, 0xa4, 0xd5, + 0x45, 0x5e, 0xd4, 0x17, 0x4a, 0x0a, 0x87, 0x30, 0x7e, 0x78, 0x4f, 0x5f, 0xd9, 0xe5, 0x6d, 0x16, 0x77, 0x59, 0xe4, + 0x4f, 0xd1, 0xab, 0x88, 0x98, 0x34, 0x72, 0xe1, 0xb5, 0xfb, 0xdb, 0xe6, 0xfe, 0xe1, 0x75, 0x63, 0xf7, 0xb3, 0x3b, + 0x46, 0x74, 0x41, 0x3d, 0x5d, 0x49, 0x4a, 0x05, 0x05, 0x04, 0x4e, 0x34, 0x6b, 0x3c, 0xb8, 0xe3, 0x80, 0x57, 0x03, + 0x5b, 0xb2, 0x8d, 0xcf, 0x9f, 0xc7, 0x32, 0x4c, 0x7b, 0x1b, 0xe0, 0x5f, 0x65, 0x6f, 0xba, 0x09, 0x96, 0x78, 0xdf, + 0x42, 0xb6, 0xa1, 0x37, 0xaf, 0xf8, 0x0b, 0xaf, 0x52, 0x7f, 0xb3, 0x7f, 0x00, 0x10, 0x06, 0xc4, 0xac, 0xfa, 0x68, + 0xe2, 0xde, 0x5b, 0x59, 0xf6, 0x4e, 0x96, 0x7d, 0x0f, 0xfd, 0x9a, 0xc4, 0xa8, 0xb4, 0xb2, 0x94, 0x4e, 0x96, 0x12, + 0xb2, 0x80, 0x4f, 0x8c, 0xa6, 0x36, 0x02, 0x08, 0xdb, 0x51, 0x2a, 0x5f, 0x00, 0xc2, 0x49, 0x02, 0x12, 0x62, 0x09, + 0x17, 0xa3, 0x7b, 0x2b, 0x19, 0x30, 0x1c, 0x42, 0x30, 0x07, 0xed, 0xb0, 0x37, 0x74, 0x13, 0xf1, 0xd7, 0xeb, 0xa2, + 0x7c, 0x13, 0x93, 0x4f, 0xc1, 0xfe, 0xec, 0xe3, 0x12, 0x1e, 0x97, 0x67, 0x1f, 0x87, 0xe8, 0x91, 0x70, 0xf6, 0x31, + 0xf8, 0x1e, 0xc9, 0x79, 0xdd, 0xf5, 0x38, 0x41, 0x6e, 0x21, 0xdd, 0xdf, 0x8e, 0x49, 0x80, 0xe6, 0x35, 0x2c, 0x47, + 0x4d, 0xc5, 0x35, 0x33, 0x63, 0x3c, 0x6f, 0xf4, 0xfe, 0xd8, 0xf1, 0x96, 0x29, 0x14, 0xb3, 0x98, 0xd7, 0xf0, 0x7b, + 0x56, 0x05, 0xea, 0xae, 0xb7, 0x49, 0x6e, 0x99, 0xd5, 0x73, 0xb4, 0xfb, 0xbe, 0xaf, 0x13, 0x41, 0xed, 0xef, 0xb0, + 0xe7, 0x99, 0xf5, 0xae, 0x8a, 0x81, 0x4b, 0x95, 0xec, 0x90, 0xa9, 0x6a, 0x7a, 0xa0, 0x52, 0x1a, 0x3c, 0xbd, 0xb4, + 0x2e, 0x5f, 0x2a, 0x6d, 0xe4, 0x99, 0xe6, 0x37, 0x80, 0x17, 0x53, 0x97, 0xc5, 0xfe, 0xab, 0xfb, 0x0a, 0x6e, 0xe3, + 0xfd, 0xfe, 0x32, 0xf7, 0xcc, 0x4f, 0x5c, 0x00, 0xf6, 0xa6, 0x42, 0xeb, 0x04, 0x4a, 0x0d, 0xeb, 0xf0, 0x65, 0x22, + 0xa2, 0x3f, 0xda, 0xe5, 0x3a, 0x73, 0x1d, 0x30, 0xa2, 0x88, 0xdf, 0xc6, 0xa3, 0x3f, 0x40, 0x71, 0x6d, 0xec, 0x01, + 0x61, 0x1d, 0x12, 0xfa, 0x8c, 0x00, 0xa4, 0x1e, 0x73, 0x94, 0x80, 0x66, 0x45, 0x73, 0xc7, 0xc0, 0xc1, 0x2f, 0xaf, + 0x94, 0xfe, 0x61, 0x99, 0x7b, 0x64, 0x4e, 0x69, 0x9b, 0x69, 0xac, 0xd6, 0xe4, 0x02, 0xe1, 0x15, 0x95, 0xac, 0xc2, + 0x67, 0xf3, 0x46, 0xf4, 0xfb, 0xf2, 0x08, 0x4f, 0xab, 0x1f, 0x77, 0x18, 0xdf, 0x0a, 0x88, 0x46, 0x02, 0xa0, 0x9f, + 0x00, 0xe6, 0x45, 0x36, 0xb3, 0xfb, 0x38, 0xa0, 0x4a, 0x89, 0xa6, 0x71, 0x36, 0xcf, 0x6f, 0xe9, 0x4d, 0xd9, 0x41, + 0xe7, 0x4e, 0x15, 0xb8, 0xe0, 0xaa, 0x64, 0xbc, 0xb2, 0x9e, 0xc9, 0xe7, 0x37, 0x77, 0xdb, 0x34, 0x8b, 0xdf, 0x97, + 0xff, 0xc0, 0xb1, 0xd5, 0x75, 0x78, 0x64, 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, 0xca, 0x8b, 0x2e, + 0x06, 0x6b, 0x5f, 0x8e, 0x59, 0x0e, 0x5d, 0xaa, 0x1e, 0xab, 0x87, 0xc6, 0x66, 0x0e, 0x3d, 0x6b, 0x55, 0x9e, 0x79, + 0xf9, 0xf1, 0x88, 0xf8, 0x30, 0xfa, 0x4b, 0x7e, 0xbf, 0xff, 0x8a, 0xe6, 0x1f, 0x13, 0x6a, 0xfc, 0x6c, 0x33, 0x40, + 0xd7, 0xbe, 0x2b, 0x0f, 0x44, 0x3d, 0xd7, 0x2a, 0x41, 0x88, 0x37, 0x88, 0x89, 0x66, 0xc4, 0x1c, 0x9c, 0x76, 0xa8, + 0xf9, 0x27, 0xa9, 0x01, 0x21, 0x4a, 0xbc, 0x8e, 0x29, 0x0b, 0x72, 0xda, 0xc4, 0x91, 0x7e, 0x14, 0x4e, 0xe4, 0x47, + 0x51, 0x15, 0xd9, 0x3d, 0x5c, 0x30, 0x98, 0x7a, 0x4f, 0xfb, 0x25, 0xfa, 0x2d, 0xe1, 0xc8, 0x39, 0x5a, 0x15, 0x82, + 0xc8, 0x19, 0x61, 0xad, 0x21, 0x4c, 0x10, 0x1b, 0xc4, 0xcb, 0xbe, 0x4b, 0x32, 0x1c, 0x29, 0xb8, 0xac, 0x63, 0xc7, + 0x98, 0xab, 0xa3, 0xea, 0x35, 0x80, 0xf1, 0xaa, 0x10, 0x34, 0x1b, 0x45, 0x76, 0x09, 0x51, 0x45, 0x8e, 0x27, 0xa0, + 0x76, 0x50, 0x1a, 0x9b, 0xe9, 0xe5, 0x38, 0x80, 0x09, 0x8e, 0x3a, 0x27, 0x96, 0xf1, 0x1a, 0x80, 0xb5, 0x2b, 0xd5, + 0xcf, 0xb3, 0x1a, 0x3c, 0x69, 0x88, 0xcf, 0xc7, 0x68, 0x7b, 0x65, 0x73, 0x50, 0x6d, 0xa7, 0xb3, 0xf2, 0x9c, 0xe9, + 0x72, 0x60, 0xdc, 0xb7, 0x3c, 0xa7, 0x38, 0xc3, 0x8f, 0x5e, 0x3e, 0xab, 0xe7, 0xfe, 0x74, 0x4b, 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, 0x53, 0xb8, 0x2f, 0x39, 0x59, 0x34, 0xf3, 0x10, 0xd8, 0x1b, 0x62, 0xb8, 0x3e, 0x2a, 0x3c, + 0xa2, 0xac, 0xdf, 0x87, 0xdf, 0x37, 0x19, 0x98, 0x62, 0xe0, 0xba, 0x42, 0x30, 0x1e, 0x02, 0x41, 0x3c, 0x4c, 0xa3, + 0x93, 0x41, 0x0d, 0xda, 0xf0, 0x2d, 0x40, 0x66, 0x80, 0x47, 0xe6, 0xd2, 0x23, 0xe0, 0x2e, 0x70, 0xed, 0xc9, 0x78, + 0xec, 0x4f, 0x4c, 0x43, 0xa3, 0xa6, 0x34, 0xd3, 0x73, 0xeb, 0x37, 0x1d, 0xd5, 0x72, 0xed, 0xfc, 0xa7, 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, 0x95, 0xd3, 0xf2, 0x45, 0xb9, 0x41, 0x3a, 0x74, 0x42, 0xb0, 0x44, 0x0e, 0xc1, + 0x12, 0x18, 0x17, 0xb1, 0xe1, 0xdb, 0x41, 0xc5, 0xe2, 0xd9, 0x76, 0xce, 0x91, 0xb0, 0x2e, 0x39, 0x1e, 0x0b, 0x09, + 0x36, 0x93, 0xcd, 0x36, 0x73, 0xb6, 0xf1, 0x19, 0x28, 0x01, 0x4a, 0x99, 0x26, 0x28, 0x4d, 0x2b, 0xb6, 0xe2, 0xa6, + 0x35, 0x58, 0xad, 0xa6, 0xec, 0x54, 0x53, 0xf6, 0x4e, 0x53, 0x4e, 0x2a, 0x28, 0x39, 0xa1, 0x14, 0x65, 0x18, 0xc0, + 0x88, 0x4d, 0xa2, 0x9b, 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, 0xa5, 0x8e, 0x07, 0x94, 0x6d, 0xfe, 0x26, 0xf6, 0x41, 0x88, 0xdc, 0x8d, 0x7b, 0xf5, 0x33, 0xe2, 0xbd, 0xfd, + 0x09, 0xc6, 0x4f, 0x76, 0xda, 0x22, 0x5c, 0x11, 0x6c, 0xa9, 0xe6, 0x10, 0x8c, 0xca, 0x24, 0x41, 0x2d, 0x4b, 0xe2, + 0x6f, 0x79, 0x32, 0xa8, 0xd8, 0x12, 0x3c, 0x68, 0xe7, 0x2c, 0x03, 0xfc, 0x15, 0xab, 0x45, 0xbf, 0xd5, 0xde, 0x12, + 0xe4, 0xa7, 0xad, 0xdd, 0x28, 0x4c, 0x8c, 0x20, 0x51, 0xb7, 0x2b, 0x03, 0xf9, 0xe1, 0x03, 0x4e, 0xc7, 0x53, 0x4f, + 0x19, 0x73, 0x2b, 0xd3, 0xcb, 0x74, 0xae, 0xe4, 0x1b, 0xb9, 0x97, 0x3e, 0xf6, 0x12, 0xec, 0x1c, 0xf0, 0x06, 0xd2, + 0x06, 0xde, 0xc2, 0x76, 0xe1, 0xb5, 0x41, 0xc2, 0x8c, 0x00, 0x5b, 0x9c, 0x1e, 0x23, 0x25, 0x30, 0x84, 0xe3, 0x2c, + 0x05, 0x60, 0x1a, 0x7d, 0x99, 0xcd, 0xed, 0xcb, 0xac, 0xd6, 0x6c, 0xa9, 0x9c, 0xee, 0x9d, 0x5b, 0xb7, 0xf3, 0x89, + 0x04, 0x00, 0x93, 0x3a, 0x07, 0xe2, 0xcc, 0x04, 0xbb, 0x34, 0x89, 0x2c, 0x1f, 0xc3, 0x7c, 0x25, 0x5e, 0x97, 0xc5, + 0x5a, 0x75, 0x45, 0xdb, 0x67, 0xa6, 0x9a, 0x91, 0x4e, 0x42, 0x05, 0x14, 0x14, 0x72, 0xad, 0x4f, 0xdf, 0x85, 0xef, + 0x82, 0x42, 0x03, 0xb3, 0xe5, 0xb8, 0xa7, 0xc9, 0x1a, 0xa9, 0x37, 0xf2, 0x7e, 0x9f, 0x5c, 0x03, 0xa9, 0xce, 0x1c, + 0x5a, 0xf6, 0x04, 0x9d, 0x64, 0x4f, 0x6e, 0xca, 0x52, 0xa8, 0x03, 0xa9, 0x07, 0x0c, 0x21, 0xda, 0xa6, 0x8f, 0x3f, + 0x19, 0x12, 0x5d, 0x80, 0x2d, 0x44, 0x1b, 0xf8, 0xf1, 0x27, 0xd8, 0x67, 0x41, 0x78, 0x4c, 0xf3, 0xb7, 0x90, 0x74, + 0x6a, 0xe0, 0xb4, 0xfa, 0x14, 0x7c, 0x90, 0xe4, 0x60, 0xa2, 0x0e, 0x5e, 0xee, 0x2f, 0xfd, 0x3e, 0x6c, 0xd9, 0x95, + 0x94, 0xea, 0x00, 0x6d, 0x4a, 0xb9, 0xab, 0x2b, 0x3f, 0x88, 0xb6, 0xe0, 0xc8, 0x22, 0xfe, 0x3e, 0x43, 0x44, 0x30, + 0x33, 0x88, 0xb0, 0x6b, 0xa1, 0xee, 0xf6, 0x9c, 0x5a, 0x16, 0xf5, 0xb6, 0xe7, 0x94, 0xba, 0x0d, 0xc3, 0x77, 0x13, + 0xcc, 0x14, 0x37, 0xfc, 0x8f, 0xcc, 0x0b, 0xf5, 0xc6, 0x63, 0x51, 0xa0, 0x7b, 0xfe, 0x61, 0xc9, 0xf3, 0xd9, 0x56, + 0x99, 0x30, 0x57, 0x7c, 0x39, 0x0b, 0x65, 0x57, 0x4b, 0xe3, 0xce, 0x67, 0x6f, 0xa9, 0xe6, 0x83, 0x7f, 0x3c, 0x26, + 0x10, 0x6f, 0x14, 0xdf, 0xac, 0x1a, 0xb9, 0x75, 0x4d, 0xb6, 0x37, 0x25, 0xa0, 0x7e, 0x5f, 0x6e, 0x70, 0xbf, 0xc5, + 0xfa, 0x77, 0x4f, 0x83, 0x8c, 0xd5, 0x0c, 0x57, 0x4c, 0xe1, 0x53, 0x00, 0x18, 0x1c, 0x4e, 0x05, 0x69, 0x81, 0xb7, + 0xbc, 0x1c, 0x5e, 0x4f, 0xb6, 0x64, 0xd2, 0xdd, 0xfa, 0xc8, 0x9d, 0x05, 0xaa, 0xde, 0x6f, 0x28, 0x4e, 0x1a, 0x24, + 0x1a, 0x7b, 0x0d, 0xbe, 0xc8, 0x32, 0xca, 0x45, 0x13, 0xf7, 0x31, 0xf9, 0x4a, 0x0f, 0x60, 0xa5, 0x42, 0x09, 0x10, + 0xfd, 0xc6, 0xb2, 0xd8, 0x88, 0xb6, 0xc5, 0x06, 0x96, 0x52, 0x3e, 0xd7, 0xab, 0xe9, 0xb3, 0x57, 0xa2, 0x79, 0x1f, + 0xcd, 0x38, 0xa5, 0xd1, 0x80, 0xe3, 0x34, 0x0a, 0x77, 0xef, 0xef, 0x45, 0xb9, 0xcc, 0xc0, 0x92, 0xad, 0xc2, 0x29, + 0xae, 0x1b, 0x75, 0x46, 0xbc, 0xc8, 0x63, 0x05, 0xd0, 0xf1, 0x98, 0x00, 0xa8, 0x2e, 0x08, 0xa8, 0x88, 0x96, 0xd2, + 0x5b, 0xa1, 0xc5, 0x42, 0xbd, 0xe1, 0x28, 0x85, 0x3f, 0xd2, 0x9f, 0x07, 0xd5, 0x14, 0x80, 0xd8, 0xf5, 0x71, 0xf4, + 0xba, 0x28, 0xe9, 0x53, 0xc5, 0xac, 0x92, 0x83, 0x09, 0xec, 0xea, 0x44, 0x86, 0x9a, 0x43, 0xde, 0xbc, 0x2b, 0x6f, + 0x6e, 0xf2, 0x36, 0xc6, 0x29, 0xf9, 0x91, 0x9b, 0x8e, 0x35, 0x62, 0xe0, 0x95, 0xa7, 0x75, 0x9a, 0x20, 0x4d, 0x2e, + 0x80, 0x61, 0x88, 0xef, 0x32, 0xef, 0x85, 0xe7, 0x48, 0x55, 0x90, 0xcc, 0xf6, 0x99, 0xa7, 0x2e, 0xa2, 0xfa, 0xca, + 0xa9, 0xa5, 0x33, 0xa7, 0x1f, 0x01, 0xbc, 0xc7, 0xd4, 0xa4, 0x21, 0x1f, 0xe1, 0xb6, 0x14, 0x5f, 0xef, 0xd4, 0x35, + 0x5e, 0x1a, 0x9d, 0xbb, 0x97, 0x2f, 0xdd, 0x69, 0xd0, 0x4f, 0x41, 0x50, 0xce, 0x17, 0xa5, 0x80, 0x3d, 0x65, 0x36, + 0xd7, 0xab, 0x55, 0x2b, 0xb4, 0x8e, 0xc7, 0xb1, 0x76, 0x14, 0xd2, 0xea, 0x2c, 0x60, 0xab, 0x91, 0x4e, 0x09, 0x10, + 0x82, 0xe3, 0x34, 0xec, 0x0c, 0xe3, 0x2e, 0x9d, 0x46, 0x64, 0xbd, 0x52, 0x92, 0x2e, 0xcc, 0x20, 0xf9, 0x27, 0x79, + 0x3d, 0x03, 0x5a, 0x02, 0x38, 0x14, 0xb1, 0x84, 0x87, 0x93, 0xe4, 0x06, 0xa0, 0xd3, 0xe1, 0xa0, 0xd2, 0xd0, 0x9c, + 0xf9, 0x2c, 0x99, 0x4f, 0x62, 0xa9, 0xaa, 0x3c, 0x1e, 0x3d, 0xe5, 0x66, 0xd0, 0xef, 0x67, 0xd3, 0x52, 0xb9, 0x00, + 0x04, 0xb1, 0x2e, 0x0c, 0x10, 0x8f, 0xb4, 0xf0, 0x64, 0xd1, 0xa7, 0x24, 0x7e, 0x39, 0x4b, 0xe6, 0x26, 0x1b, 0xde, + 0x81, 0x11, 0x6c, 0xc6, 0x75, 0x49, 0x99, 0xf6, 0xa8, 0xfc, 0x9e, 0xd1, 0x53, 0xdb, 0xd7, 0x5a, 0x6d, 0x11, 0xeb, + 0x3a, 0xb8, 0x2a, 0x51, 0x4f, 0xf1, 0x41, 0x49, 0x82, 0xf7, 0x2b, 0xe7, 0x66, 0xa4, 0x7c, 0x2d, 0x2a, 0x3f, 0x68, + 0x67, 0x6a, 0xe5, 0xc0, 0x11, 0xa8, 0xb0, 0x8a, 0x4a, 0x5e, 0xef, 0x3a, 0x04, 0x4f, 0xee, 0x4a, 0x05, 0xca, 0xc1, + 0xcf, 0x41, 0x8c, 0xae, 0x6f, 0x3a, 0x6b, 0xa8, 0x99, 0x46, 0x95, 0x47, 0xd0, 0xb9, 0x03, 0x78, 0x52, 0xf0, 0x52, + 0xab, 0x1f, 0x8f, 0x47, 0xcf, 0xfc, 0xe0, 0x2f, 0x33, 0x7d, 0x0b, 0x31, 0x51, 0x4e, 0x35, 0x42, 0xe2, 0x4a, 0x49, + 0x22, 0x3e, 0x5d, 0xb4, 0xac, 0x18, 0x95, 0xe1, 0x03, 0xb0, 0x00, 0x51, 0xf9, 0xea, 0x54, 0xe5, 0xc5, 0x48, 0xdb, + 0x12, 0x78, 0x4d, 0xfe, 0x21, 0x72, 0xcd, 0x5b, 0x5f, 0x77, 0x95, 0xa1, 0x6f, 0x65, 0x05, 0x3a, 0x82, 0xad, 0x2c, + 0x25, 0x07, 0x7c, 0x52, 0xdf, 0x55, 0x5b, 0x9f, 0x53, 0xb6, 0x11, 0x6e, 0xf2, 0xeb, 0xd8, 0xc1, 0x91, 0xf2, 0x1b, + 0xbc, 0x14, 0xc0, 0x5e, 0x03, 0xf6, 0xe6, 0x8a, 0x15, 0xcd, 0xa3, 0x43, 0xda, 0x16, 0x68, 0x64, 0xe6, 0x76, 0xae, + 0xee, 0xdb, 0xf2, 0x28, 0x8d, 0x21, 0x32, 0xed, 0x91, 0xe9, 0x60, 0x33, 0xca, 0x7f, 0x4b, 0xf9, 0xad, 0xc2, 0x31, + 0xf0, 0xed, 0xdc, 0x3b, 0x80, 0xaa, 0xa7, 0x0d, 0x32, 0xd6, 0x0c, 0x43, 0x2b, 0xbb, 0x5c, 0x0a, 0x2d, 0x41, 0x4b, + 0xdd, 0x04, 0xc1, 0xf9, 0x11, 0x51, 0x8e, 0x00, 0x74, 0x91, 0x02, 0x26, 0xf8, 0x39, 0x6d, 0x77, 0xbf, 0xbf, 0x49, + 0x3d, 0x72, 0xef, 0x0a, 0x95, 0xcd, 0xf2, 0x4d, 0x8e, 0x30, 0xf6, 0x13, 0x8d, 0x19, 0x74, 0x72, 0x45, 0x4e, 0x78, + 0xd6, 0xea, 0xb0, 0xae, 0x9b, 0x32, 0x28, 0x8b, 0x63, 0x9e, 0x4f, 0x67, 0xbf, 0x3f, 0x39, 0xd4, 0x0d, 0xb2, 0x90, + 0xff, 0xce, 0x7a, 0x48, 0x06, 0xdd, 0x83, 0x50, 0x88, 0xde, 0x3c, 0x98, 0xe1, 0x7f, 0x6c, 0xcb, 0xb3, 0x6f, 0xb8, + 0x51, 0x27, 0x80, 0x39, 0xe2, 0x7a, 0xe9, 0x29, 0xda, 0x7a, 0xb8, 0x05, 0xb2, 0x0d, 0x5e, 0xde, 0xda, 0x6b, 0xa0, + 0xa2, 0x38, 0xfe, 0x15, 0xcf, 0xd4, 0xca, 0x06, 0x3f, 0x3d, 0x65, 0x3b, 0xf0, 0xf0, 0x22, 0x04, 0x14, 0xc3, 0xb2, + 0xf1, 0x2b, 0xcb, 0x71, 0x46, 0xff, 0xcd, 0x23, 0x86, 0xc1, 0x22, 0xf2, 0xe3, 0xcb, 0x52, 0x88, 0x2f, 0xc2, 0x7b, + 0x93, 0x7b, 0x2b, 0x72, 0xca, 0x5c, 0xe9, 0x61, 0x74, 0x5d, 0x92, 0xbe, 0x49, 0x3e, 0xb6, 0x86, 0xed, 0x77, 0xed, + 0x7e, 0x33, 0x44, 0x10, 0x42, 0x39, 0x7e, 0xce, 0xe8, 0x84, 0xc6, 0x87, 0x35, 0xd7, 0x3b, 0xbd, 0x7e, 0xef, 0x12, + 0x2f, 0xd8, 0x1a, 0x0d, 0xf0, 0x74, 0xe8, 0x62, 0x9e, 0xa8, 0xa1, 0xd3, 0x75, 0xed, 0x1c, 0x3c, 0x30, 0xc8, 0xf2, + 0xe4, 0x1b, 0x86, 0x25, 0xf6, 0x27, 0x11, 0x4f, 0xda, 0xaa, 0x8d, 0xed, 0x89, 0x6a, 0xa3, 0x66, 0xe0, 0x07, 0xaf, + 0xa0, 0xc0, 0xe8, 0x82, 0xb4, 0x06, 0xe3, 0x70, 0x04, 0x20, 0x2b, 0xc6, 0xf1, 0xc8, 0x60, 0x02, 0x43, 0xba, 0xa1, + 0x28, 0x00, 0x0f, 0x8f, 0xd3, 0x41, 0xc8, 0x00, 0xd2, 0x05, 0x0f, 0x0d, 0xdb, 0x24, 0xa4, 0xfc, 0x3c, 0x2f, 0x6b, + 0x35, 0x84, 0xbe, 0xb3, 0x50, 0x1d, 0xfb, 0x91, 0xf6, 0x8a, 0x75, 0xad, 0x4a, 0x27, 0xb6, 0x3a, 0x40, 0xdf, 0x90, + 0x81, 0x6f, 0x1d, 0x5b, 0x00, 0x44, 0x4b, 0xfc, 0x96, 0x7a, 0xb5, 0x2f, 0x63, 0x56, 0xa8, 0xd7, 0x17, 0xa6, 0x5d, + 0xaf, 0xa4, 0x45, 0x01, 0x15, 0xb7, 0xad, 0xda, 0x9e, 0xc8, 0xf9, 0x8f, 0xef, 0x3a, 0xda, 0xf1, 0xd9, 0xa9, 0xb1, + 0x25, 0x94, 0xb9, 0xc5, 0x13, 0x59, 0x1d, 0x6d, 0xa9, 0x4e, 0xf5, 0x01, 0x97, 0x9a, 0x54, 0x67, 0xda, 0xa7, 0xc9, + 0x12, 0xa0, 0xdc, 0x42, 0x24, 0x8d, 0xc3, 0xc1, 0xf9, 0x64, 0x50, 0x30, 0xb7, 0x48, 0x40, 0x02, 0xdb, 0xda, 0xda, + 0x45, 0x73, 0xfd, 0xfa, 0x2d, 0xf5, 0xf2, 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, 0xe5, 0x6f, 0x29, 0x2a, 0x62, 0xcf, + 0x15, 0xa7, 0xac, 0xdf, 0x6e, 0x99, 0x8b, 0xca, 0x92, 0x37, 0xa8, 0xa2, 0xb5, 0x3a, 0x6a, 0x72, 0xd7, 0xcd, 0x55, + 0x4b, 0x26, 0x88, 0xd1, 0x7d, 0xbe, 0xd6, 0x95, 0x53, 0xef, 0x83, 0x8a, 0x23, 0x06, 0x82, 0x9b, 0xee, 0xf1, 0xc1, + 0x41, 0x68, 0x54, 0x94, 0x0b, 0x6e, 0x94, 0x56, 0x95, 0x94, 0x42, 0xde, 0xaa, 0x68, 0xc5, 0xf4, 0x11, 0x00, 0x11, + 0x60, 0x95, 0xa8, 0xff, 0xcd, 0x97, 0xc6, 0x78, 0xf0, 0xc0, 0xd7, 0xe4, 0x3a, 0xb6, 0xde, 0x3f, 0xaf, 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, 0x2a, 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, 0xf1, 0x3c, 0x27, 0xb9, 0x74, 0x91, 0xe6, 0x95, 0x28, 0x6b, 0xdd, 0x8c, 0x9c, 0x15, 0xc3, 0x9c, 0xd5, + 0x7e, 0x50, 0xdc, 0xe4, 0x66, 0x42, 0x23, 0x36, 0x90, 0xca, 0x52, 0xb0, 0x7c, 0x58, 0xf8, 0x4d, 0xfb, 0x4d, 0x72, + 0xd2, 0xbb, 0x1c, 0xb7, 0xce, 0x1d, 0xfb, 0xde, 0x51, 0x48, 0x69, 0x0f, 0xc5, 0x04, 0x41, 0xf0, 0xd3, 0x3a, 0x9c, + 0x3f, 0xe3, 0xcf, 0x09, 0x4c, 0x45, 0x36, 0x63, 0xc0, 0x41, 0x88, 0xc8, 0x8c, 0xdf, 0x73, 0xf8, 0x9c, 0x97, 0x93, + 0x70, 0x38, 0xf4, 0x41, 0x1f, 0xca, 0xb3, 0x59, 0x38, 0x14, 0x73, 0xe9, 0xbd, 0x0e, 0xd6, 0xba, 0x90, 0xd7, 0x93, + 0x10, 0xd1, 0x42, 0x43, 0x1f, 0x9c, 0xd7, 0x5d, 0x73, 0x84, 0x25, 0x00, 0x4d, 0x1c, 0x7d, 0x59, 0xbf, 0x1f, 0x79, + 0xda, 0xd0, 0x22, 0xc5, 0x45, 0xa3, 0xcc, 0x66, 0x95, 0xec, 0x84, 0xad, 0x6b, 0xb7, 0x40, 0x28, 0x1e, 0xa6, 0x2d, + 0x54, 0xad, 0xa7, 0x7a, 0x3d, 0x37, 0xed, 0xbe, 0x7b, 0x54, 0xad, 0x72, 0xa2, 0xb3, 0x36, 0x5d, 0xa9, 0xd5, 0x2d, + 0xa3, 0x6a, 0x93, 0xa5, 0x11, 0x55, 0x6e, 0x52, 0xb9, 0x46, 0x2d, 0xf8, 0x64, 0x43, 0xcd, 0xb5, 0xb3, 0x35, 0x38, + 0x71, 0xe4, 0xb9, 0xe4, 0x96, 0xef, 0xce, 0x2b, 0xba, 0x3b, 0xd5, 0xbe, 0x05, 0xb8, 0x37, 0xc3, 0x86, 0xcc, 0x79, + 0x8d, 0x9d, 0x06, 0x61, 0x12, 0xf8, 0x11, 0xfb, 0x98, 0x21, 0x1b, 0x0c, 0xe8, 0x28, 0xa4, 0x26, 0xc0, 0x32, 0x47, + 0x02, 0x26, 0x7f, 0x3d, 0xf7, 0x9b, 0x45, 0x91, 0xc3, 0x62, 0xfc, 0xb0, 0xc5, 0x48, 0x63, 0xb5, 0x06, 0xc3, 0x72, + 0x85, 0xc8, 0x9f, 0xda, 0x33, 0xd4, 0x54, 0xc7, 0x9b, 0xf5, 0x5a, 0xf3, 0xab, 0xa7, 0x4f, 0x75, 0x7d, 0xfe, 0xdb, + 0xf7, 0x97, 0x61, 0xcd, 0xec, 0x0f, 0x41, 0x28, 0xed, 0xc1, 0x2d, 0xce, 0x1d, 0x89, 0xde, 0xa9, 0xd2, 0xcc, 0x2e, + 0xed, 0x9a, 0x5d, 0x9b, 0xd2, 0x6e, 0xc9, 0xf5, 0xea, 0x2b, 0xe5, 0x8d, 0x9d, 0x57, 0x4c, 0xf7, 0x1f, 0x84, 0xde, + 0x51, 0xce, 0xd5, 0x04, 0x22, 0x9a, 0xb4, 0x23, 0x71, 0xbb, 0x57, 0x86, 0xcf, 0x26, 0x55, 0xbb, 0x84, 0x93, 0xae, + 0x61, 0x95, 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, 0xe4, 0x9e, 0x42, 0xd8, 0x60, 0xf6, 0xf3, + 0xd3, 0x87, 0x16, 0xac, 0xaa, 0x0b, 0x14, 0xc9, 0x01, 0x74, 0xee, 0x9a, 0x11, 0xde, 0xef, 0x18, 0xe7, 0xfe, 0xcd, + 0x0f, 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, 0xb7, 0x6c, 0xef, 0xf3, 0xe7, + 0xeb, 0xd4, 0xbb, 0x47, 0x87, 0xe0, 0xcb, 0xb1, 0x3f, 0xbd, 0x0e, 0x0c, 0x2e, 0x34, 0x7b, 0xfb, 0x54, 0xb0, 0x3d, + 0xdb, 0x3f, 0x45, 0xa4, 0xa2, 0xee, 0xfc, 0xc3, 0x6b, 0x13, 0x3d, 0xef, 0xbc, 0xb0, 0xe2, 0x4b, 0x00, 0x0f, 0x64, + 0x31, 0xa0, 0xf8, 0x2c, 0xbd, 0x7f, 0xb2, 0x04, 0xd4, 0xe4, 0x77, 0x7c, 0xe3, 0xbd, 0xa3, 0xd4, 0x05, 0xfc, 0x39, + 0xa0, 0xf4, 0x49, 0xc5, 0xbd, 0xd5, 0xf0, 0xce, 0xbf, 0x7a, 0x06, 0xce, 0x13, 0xeb, 0xe1, 0x02, 0xfe, 0x2a, 0xf8, + 0xd0, 0x5b, 0x0d, 0x30, 0xb1, 0xe4, 0x43, 0x6f, 0x3d, 0x80, 0x54, 0x85, 0x0b, 0x89, 0xb1, 0x0f, 0xbf, 0x06, 0x15, + 0xc3, 0x3f, 0x7e, 0xd3, 0x18, 0xac, 0xbf, 0x06, 0x85, 0x46, 0x63, 0x2d, 0x55, 0xc8, 0x52, 0x2c, 0x2e, 0x04, 0xd8, + 0x84, 0xe3, 0x6e, 0x5f, 0xac, 0x6a, 0xbb, 0x11, 0xf4, 0xe7, 0x23, 0xbe, 0x47, 0x63, 0x75, 0x55, 0xce, 0x45, 0xf9, + 0x11, 0xe9, 0x53, 0x1d, 0x1f, 0xa3, 0x62, 0x5b, 0x77, 0xa7, 0x53, 0xad, 0x3a, 0xd2, 0x7e, 0x53, 0xae, 0xc1, 0x8e, + 0xd7, 0xc9, 0x89, 0xa5, 0xf0, 0xa2, 0xc3, 0xce, 0x4b, 0xa7, 0x44, 0x87, 0x61, 0xbc, 0xdb, 0xaa, 0x67, 0x0c, 0xe5, + 0x95, 0xc1, 0x98, 0x2e, 0x78, 0xc4, 0x9f, 0x0f, 0x2a, 0x19, 0x1a, 0xf3, 0x01, 0xd9, 0x30, 0x94, 0x0f, 0x2d, 0x32, + 0x24, 0x44, 0xbc, 0x87, 0x4a, 0xc0, 0xb6, 0x05, 0x65, 0x52, 0xc0, 0x59, 0x34, 0xf8, 0xad, 0xf6, 0x2a, 0xe0, 0x3d, + 0x88, 0xfc, 0x46, 0xba, 0x94, 0x4b, 0x6c, 0x74, 0xe2, 0x58, 0x16, 0xda, 0x79, 0x5c, 0x7f, 0x1d, 0x83, 0xfa, 0xbd, + 0xd2, 0x6f, 0x50, 0xce, 0xfe, 0x28, 0x59, 0xa7, 0x8d, 0x27, 0xc6, 0xdf, 0x5d, 0xe5, 0x9f, 0xa2, 0xa5, 0x1e, 0xfe, + 0x3f, 0x63, 0x0a, 0xa5, 0x7f, 0x99, 0x96, 0xd1, 0x76, 0xbd, 0x14, 0xa5, 0xc8, 0x23, 0x71, 0xf6, 0xb5, 0xc8, 0xce, + 0xe5, 0x3b, 0x9f, 0x42, 0xbf, 0x00, 0xb4, 0xec, 0x13, 0x64, 0xf4, 0x0f, 0x4c, 0xf0, 0xe1, 0x0f, 0xda, 0xb9, 0xb6, + 0xe2, 0xe3, 0x49, 0x75, 0x63, 0xed, 0xdd, 0x8e, 0x17, 0x89, 0x51, 0x8c, 0x55, 0xbe, 0xea, 0x66, 0xe5, 0x44, 0x25, + 0x07, 0x46, 0xba, 0x26, 0x7b, 0x95, 0x92, 0x75, 0x3b, 0xdd, 0x4a, 0x20, 0xa2, 0x0a, 0xbc, 0xc7, 0xb8, 0x8a, 0x7d, + 0x04, 0xd3, 0x75, 0xc7, 0x65, 0xb4, 0xe3, 0x3d, 0xe3, 0xd5, 0x89, 0xb2, 0x82, 0xdb, 0x8d, 0x68, 0x4f, 0xe8, 0xe8, + 0xa7, 0x49, 0x6d, 0x59, 0x38, 0x00, 0xb9, 0x4b, 0x18, 0xcb, 0x86, 0x60, 0xc5, 0xa0, 0xf4, 0xf5, 0x9a, 0x92, 0x65, + 0x01, 0x16, 0x9d, 0x5d, 0x46, 0x20, 0x86, 0x75, 0xd3, 0x9c, 0xd1, 0xf1, 0xd2, 0xc5, 0xf9, 0xa0, 0x55, 0xa4, 0xe0, + 0x19, 0x2d, 0x3a, 0xe6, 0xa6, 0x23, 0xdd, 0x18, 0xed, 0xed, 0x0f, 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, 0x3e, 0x87, 0xad, 0x09, 0xb3, 0xad, 0x8b, + 0x2d, 0xd0, 0x56, 0x31, 0x34, 0x08, 0x6b, 0x43, 0xcc, 0xc7, 0x34, 0x5f, 0xfd, 0x13, 0x8b, 0xb1, 0x3d, 0x81, 0xd8, + 0xc1, 0xed, 0x9a, 0x84, 0xe9, 0x5e, 0x8b, 0x1b, 0xeb, 0xe5, 0xf6, 0x94, 0x63, 0x6a, 0xc7, 0xda, 0xaa, 0x1d, 0x6b, + 0xa9, 0x77, 0xac, 0x8d, 0xde, 0xb1, 0x56, 0x0d, 0xff, 0x90, 0x79, 0x31, 0x4b, 0x40, 0xbf, 0xbb, 0xe6, 0xaa, 0x41, + 0xd0, 0x8c, 0x2d, 0xbb, 0x83, 0xdf, 0x12, 0x6b, 0xb7, 0xf4, 0xaf, 0x96, 0x6c, 0x61, 0xfa, 0x40, 0xb7, 0x0e, 0xb0, + 0x8c, 0xa8, 0xc9, 0xf7, 0xc8, 0xbb, 0xe9, 0xac, 0x28, 0xdc, 0x9e, 0xd8, 0xc2, 0x67, 0x6f, 0xcd, 0x9b, 0xf7, 0x4f, + 0x23, 0xc8, 0xbd, 0xe7, 0xde, 0xfd, 0xf0, 0xad, 0x7f, 0xa5, 0x5b, 0x20, 0x27, 0xb3, 0x9c, 0x81, 0xd4, 0x11, 0x9f, + 0x20, 0x5a, 0xd9, 0x53, 0xbe, 0x13, 0x72, 0x67, 0xdb, 0x3c, 0xbd, 0x77, 0xb7, 0xb5, 0xd5, 0xd3, 0x7b, 0x96, 0x8f, + 0x28, 0x56, 0x9c, 0xa6, 0x48, 0x98, 0x45, 0x5b, 0xe0, 0xa9, 0x97, 0xef, 0x77, 0xec, 0x98, 0xc3, 0xfd, 0xd3, 0x8e, + 0x8e, 0x97, 0x73, 0xc0, 0xee, 0xfe, 0x93, 0x4d, 0xd8, 0x58, 0xe9, 0x5a, 0x85, 0x0e, 0xf7, 0x4f, 0x33, 0x8d, 0xe7, + 0x70, 0x22, 0x9f, 0x8e, 0x35, 0x36, 0x08, 0xea, 0xfa, 0x9c, 0x41, 0xed, 0xd8, 0x7d, 0x4d, 0xd8, 0x65, 0xc7, 0xbc, + 0xd6, 0x35, 0x6f, 0xaf, 0x3c, 0x15, 0x1b, 0x02, 0x3a, 0x7c, 0xad, 0x6e, 0x90, 0x7f, 0x09, 0x9c, 0x22, 0x00, 0xe4, + 0x70, 0xba, 0xe4, 0xb1, 0xef, 0xd3, 0x2c, 0xad, 0xf7, 0xa8, 0xb5, 0xc8, 0x2d, 0xcb, 0xb0, 0xf6, 0x7e, 0xd0, 0x8a, + 0x61, 0xa9, 0xe9, 0x9f, 0x8e, 0x03, 0xb7, 0xb3, 0xdd, 0xca, 0xd8, 0x65, 0x3c, 0x2d, 0xae, 0x7e, 0x38, 0x2f, 0x94, + 0x6b, 0x37, 0x6f, 0xe3, 0x37, 0xad, 0x96, 0x2c, 0xad, 0xf5, 0x90, 0x97, 0x96, 0x45, 0x04, 0x02, 0x18, 0x4e, 0x94, + 0x5d, 0x2c, 0xe1, 0x1e, 0x61, 0x75, 0x0f, 0x42, 0xc9, 0xbc, 0x70, 0xf5, 0x8c, 0xc5, 0x90, 0x08, 0xb0, 0xdd, 0xa1, + 0x62, 0x5b, 0xb8, 0x7a, 0xc6, 0xb6, 0xbc, 0xe8, 0xf7, 0x33, 0xd5, 0x29, 0x64, 0xdd, 0x59, 0xf2, 0xad, 0x6a, 0x8e, + 0x35, 0xd4, 0x6c, 0x63, 0x92, 0xad, 0x71, 0x6e, 0x2b, 0x3e, 0x56, 0x6d, 0xc5, 0xc7, 0xda, 0x5a, 0x97, 0xee, 0xf5, + 0x1e, 0xd5, 0x05, 0xb0, 0xf5, 0xdf, 0x9d, 0xae, 0x5c, 0xcf, 0x67, 0x04, 0xf0, 0xb5, 0xe0, 0xe3, 0xc9, 0x02, 0xbd, + 0x4a, 0x16, 0xfe, 0xdd, 0x40, 0x8d, 0xbf, 0xd3, 0xb9, 0x0b, 0x80, 0xae, 0xa4, 0xbc, 0x02, 0xf2, 0x0e, 0x2a, 0xcc, + 0x2d, 0xbb, 0xf2, 0xfe, 0xec, 0x3b, 0xec, 0x2d, 0xaf, 0x67, 0x8b, 0x39, 0xdb, 0x83, 0x53, 0x41, 0x32, 0xb0, 0x97, + 0x15, 0xdb, 0x07, 0xb1, 0x9d, 0xf0, 0x1b, 0x01, 0x53, 0xbe, 0x80, 0x20, 0xae, 0xe0, 0x0e, 0xe2, 0xf0, 0xe4, 0x9f, + 0x83, 0xfb, 0xd6, 0x66, 0x7d, 0xcf, 0xac, 0xce, 0x09, 0x36, 0xcc, 0xea, 0xc1, 0x60, 0xd9, 0x4c, 0xd6, 0xfd, 0xbe, + 0xb7, 0xd7, 0x8e, 0x4f, 0x2b, 0xa9, 0x13, 0x3b, 0xaf, 0xd5, 0x5a, 0xb0, 0xb7, 0x52, 0xeb, 0x62, 0x0c, 0x3d, 0x80, + 0x5f, 0x70, 0x37, 0xe0, 0xf7, 0x1d, 0x6b, 0xcb, 0x7b, 0xcb, 0x16, 0x6c, 0x0f, 0x97, 0xa0, 0xa6, 0xbd, 0xec, 0xcf, + 0x2a, 0x17, 0xb4, 0x63, 0x97, 0xc4, 0xc3, 0x19, 0xb3, 0x5c, 0x99, 0x59, 0x27, 0xf9, 0x8d, 0xe8, 0x8c, 0xe9, 0xac, + 0xf5, 0x7c, 0xce, 0xe7, 0x93, 0x42, 0x83, 0xfa, 0x5d, 0x12, 0x1f, 0x51, 0xd1, 0x79, 0x02, 0x5b, 0xcb, 0x0a, 0xc8, + 0xbd, 0x2e, 0xc1, 0x5a, 0xab, 0x5d, 0xfa, 0xbd, 0x6a, 0xc0, 0x6d, 0xca, 0x61, 0x4d, 0x40, 0xd0, 0x9c, 0x59, 0x51, + 0x8f, 0xd9, 0x8e, 0x71, 0xf3, 0xd3, 0xcb, 0x1f, 0x9c, 0xb0, 0x64, 0xc5, 0x6a, 0x7f, 0xfa, 0xc3, 0x53, 0x4f, 0x7f, + 0xa7, 0xf6, 0xaf, 0x84, 0x1f, 0x8c, 0xff, 0x5d, 0xbb, 0xaf, 0xb5, 0x18, 0x95, 0xad, 0x72, 0x84, 0xc6, 0xdd, 0x4a, + 0x9a, 0x2c, 0x3f, 0x09, 0x4f, 0x58, 0x0b, 0x9e, 0x99, 0x33, 0x20, 0x2b, 0x60, 0x85, 0xb5, 0x4c, 0xc2, 0x39, 0xc6, + 0x6a, 0x69, 0xab, 0x6f, 0xd1, 0x34, 0xa7, 0x87, 0x73, 0x6d, 0x50, 0xa6, 0x9c, 0x9d, 0x11, 0xab, 0xe1, 0x32, 0x2c, + 0x4d, 0x28, 0x42, 0xf6, 0x60, 0x07, 0x37, 0x76, 0xca, 0x52, 0xca, 0x70, 0x8e, 0xc1, 0x84, 0x27, 0x62, 0x54, 0xee, + 0xfb, 0x87, 0x92, 0x57, 0x6d, 0x39, 0x28, 0x47, 0xd8, 0x47, 0x12, 0x25, 0x70, 0x2b, 0xd2, 0x42, 0x91, 0xb2, 0xf8, + 0xdb, 0x01, 0xba, 0xc0, 0x0b, 0xa8, 0xab, 0x51, 0xb7, 0x3f, 0x1c, 0xf1, 0xf0, 0x91, 0xa9, 0x0f, 0x8c, 0x58, 0x12, + 0xa8, 0xed, 0x45, 0x96, 0xae, 0x40, 0x85, 0xdf, 0xc3, 0xd5, 0x44, 0xec, 0xe7, 0x96, 0x14, 0x15, 0xd9, 0x48, 0x6f, + 0x68, 0x0d, 0x1e, 0xa1, 0x35, 0xe5, 0x07, 0x27, 0xd5, 0x26, 0x9d, 0x77, 0x84, 0x1c, 0xab, 0x6f, 0x2d, 0x61, 0xb4, + 0x2b, 0x7a, 0xf1, 0xe0, 0xe8, 0x3d, 0xcf, 0x57, 0xbd, 0xf2, 0x27, 0xae, 0x98, 0x27, 0xb7, 0x11, 0xa8, 0x5b, 0x41, + 0x75, 0x7b, 0xaf, 0x12, 0x2c, 0x58, 0xd2, 0xee, 0xe3, 0xb7, 0xb3, 0x76, 0x20, 0x2a, 0x63, 0x95, 0xbe, 0x26, 0x09, + 0x7b, 0x62, 0xd0, 0x29, 0x54, 0x55, 0x76, 0x77, 0xb4, 0x05, 0xae, 0x53, 0x96, 0xa2, 0x17, 0xb6, 0xc8, 0xdd, 0xf2, + 0xef, 0x9e, 0x2b, 0x72, 0xf6, 0x6b, 0x40, 0x70, 0x6a, 0xbe, 0x22, 0xbe, 0x9c, 0xe0, 0x51, 0x75, 0x0b, 0x1c, 0xe7, + 0xef, 0x00, 0xfe, 0xf1, 0x78, 0x0d, 0x9a, 0x80, 0x58, 0xb0, 0x5e, 0x1a, 0xf7, 0x58, 0x2f, 0x2e, 0xb6, 0xab, 0x24, + 0xdf, 0x82, 0x33, 0x03, 0xa5, 0x5a, 0xfa, 0x81, 0x53, 0xb5, 0x80, 0x0a, 0x07, 0xb3, 0x93, 0x7a, 0x61, 0x19, 0xf5, + 0x98, 0x3e, 0x3f, 0x83, 0x83, 0x23, 0x24, 0x00, 0xee, 0x97, 0x7d, 0x40, 0x02, 0x1e, 0x3a, 0xb3, 0x03, 0xc2, 0x09, + 0xb3, 0xa8, 0x0a, 0x24, 0x92, 0x23, 0xfd, 0xec, 0x31, 0x13, 0xc9, 0x1f, 0xcc, 0x7a, 0xce, 0x29, 0xd1, 0x63, 0x3d, + 0x75, 0x84, 0xf4, 0x58, 0xcf, 0x3a, 0x22, 0x7a, 0xac, 0x67, 0x1d, 0x1f, 0x3d, 0xd6, 0x33, 0xc7, 0x4e, 0x0f, 0x02, + 0x13, 0x20, 0xf2, 0x80, 0xf5, 0x68, 0x32, 0xf5, 0x14, 0xf7, 0x00, 0xd1, 0x20, 0xb0, 0x9e, 0x14, 0xce, 0x7b, 0x80, + 0x3c, 0x46, 0x62, 0x75, 0xd0, 0xfb, 0x8f, 0xf1, 0x77, 0x3d, 0x23, 0x23, 0x8f, 0x5b, 0x87, 0xd5, 0xff, 0xfa, 0x4f, + 0x08, 0x80, 0xc3, 0xb3, 0xa9, 0x77, 0x3d, 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, 0x46, 0x44, 0x35, 0x51, 0xa4, 0x5c, 0x33, 0x8a, 0x62, 0xa9, 0x82, + 0x43, 0x26, 0x56, 0x10, 0x35, 0x4f, 0x5b, 0x6d, 0x15, 0x1c, 0x56, 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, 0x8a, 0x20, 0xb2, 0x00, 0x01, 0x1c, 0x37, 0x10, 0xf8, 0xbc, 0xc6, 0x27, 0xd0, 0x28, 0x04, + 0xf2, 0x03, 0xeb, 0x70, 0xe3, 0x21, 0x2d, 0xb5, 0x46, 0x44, 0x89, 0xf8, 0xc9, 0xd5, 0x73, 0x6c, 0x5f, 0x3d, 0x8d, + 0xb5, 0xa5, 0x34, 0x41, 0xfc, 0xc4, 0x62, 0x0b, 0x31, 0x41, 0x54, 0x87, 0xe8, 0x04, 0x96, 0x13, 0x42, 0x14, 0xfe, + 0x10, 0xfa, 0xa9, 0x81, 0xbf, 0x64, 0xcb, 0x22, 0xaf, 0x09, 0x16, 0x33, 0x67, 0x80, 0x56, 0x45, 0xe0, 0x99, 0xce, + 0x96, 0xca, 0x9c, 0xe6, 0xd1, 0x91, 0x1d, 0x5c, 0x76, 0x1d, 0xec, 0xa5, 0x2f, 0x63, 0x27, 0xcb, 0xa6, 0x51, 0x1b, + 0x1b, 0x22, 0xe1, 0x15, 0xf9, 0xcb, 0x2c, 0x35, 0xce, 0x91, 0x6a, 0x7d, 0xd7, 0xc5, 0x6a, 0x45, 0xdb, 0x84, 0x55, + 0x88, 0x50, 0xb7, 0x0d, 0x95, 0x4b, 0x61, 0x36, 0x36, 0x4d, 0x03, 0x7c, 0xa1, 0xa8, 0x54, 0xca, 0x53, 0x5b, 0xa9, + 0xe4, 0x84, 0x77, 0x7d, 0x55, 0x8b, 0xd4, 0x15, 0xc1, 0x36, 0x66, 0xa8, 0x87, 0x72, 0xa3, 0xc6, 0xbe, 0xee, 0x58, + 0xa5, 0x77, 0x98, 0xa0, 0x62, 0xe4, 0x45, 0x0e, 0x2e, 0x4a, 0x0a, 0x32, 0x57, 0x43, 0x98, 0x3f, 0x6a, 0xf8, 0xb4, + 0xb0, 0xdc, 0x43, 0x09, 0x98, 0x1d, 0x35, 0xbc, 0x8c, 0x10, 0x88, 0xb8, 0x54, 0xf6, 0x15, 0x13, 0xbf, 0xa7, 0x60, + 0x96, 0x4c, 0xe8, 0x5e, 0xc4, 0xc2, 0x08, 0x6d, 0x7c, 0x92, 0x24, 0x53, 0xb9, 0x3e, 0x41, 0x17, 0x2a, 0x5c, 0x20, + 0x23, 0xb4, 0x48, 0xf3, 0x4f, 0x87, 0x53, 0x09, 0x3e, 0xa2, 0x4e, 0x01, 0xc7, 0xf3, 0xcb, 0xc2, 0xfa, 0xc9, 0x2a, + 0x89, 0xb9, 0xac, 0xcd, 0x7f, 0xd9, 0xc9, 0x31, 0xd8, 0xe5, 0x69, 0xe2, 0xb8, 0xfa, 0x8f, 0xaa, 0xa4, 0x78, 0xf8, + 0x39, 0xcd, 0x01, 0x45, 0x30, 0xb3, 0xa7, 0x18, 0x1f, 0xfb, 0x2c, 0x53, 0xc0, 0xdf, 0xae, 0xb7, 0x96, 0x4c, 0xec, + 0x92, 0x76, 0x2b, 0x65, 0xfc, 0x52, 0x1b, 0x76, 0x1c, 0x5c, 0x1a, 0x80, 0xe2, 0xac, 0xd1, 0x61, 0x79, 0xad, 0xdb, + 0x56, 0x8e, 0x0a, 0xd4, 0xfa, 0xdf, 0xbb, 0x85, 0x29, 0x6f, 0xf3, 0x52, 0x79, 0x9b, 0x87, 0x26, 0x40, 0x20, 0x32, + 0x43, 0x9e, 0x35, 0x1d, 0x93, 0xc4, 0xbd, 0x23, 0x25, 0xed, 0x3b, 0x52, 0xfc, 0xe8, 0x1d, 0x09, 0xf9, 0x96, 0xd0, + 0x91, 0x7d, 0xc9, 0xc9, 0x09, 0x94, 0x19, 0xec, 0xe5, 0x0d, 0x93, 0xfd, 0x03, 0xda, 0x0b, 0xe7, 0xb2, 0xbc, 0xe6, + 0x6f, 0x85, 0xb7, 0xf1, 0xa7, 0x9b, 0xf3, 0xae, 0xaa, 0x77, 0x5f, 0x99, 0x99, 0xc7, 0x63, 0x71, 0x3c, 0xe6, 0x26, + 0x68, 0x77, 0xc1, 0xc5, 0xa0, 0x62, 0xf7, 0x6e, 0x7c, 0xfc, 0x5b, 0x8e, 0x22, 0xb6, 0x52, 0x1e, 0x49, 0x17, 0x2a, + 0x31, 0xbc, 0x36, 0xf0, 0x30, 0x7b, 0x3e, 0x9e, 0xec, 0x6f, 0xee, 0x27, 0x83, 0xc1, 0x5e, 0xf5, 0xed, 0x8e, 0xd7, + 0xb3, 0xfd, 0x9c, 0x3d, 0xf0, 0xbb, 0xe9, 0x2e, 0x38, 0x34, 0xb0, 0xed, 0xee, 0x6f, 0xc4, 0xf1, 0xb8, 0x7f, 0xce, + 0x17, 0xfe, 0xe1, 0x01, 0x01, 0x9d, 0xf9, 0xe5, 0xb8, 0x8d, 0xf1, 0x73, 0xdb, 0x76, 0xd5, 0xda, 0x03, 0x3c, 0xfd, + 0x8f, 0xde, 0xed, 0x6c, 0x39, 0xf7, 0xd9, 0x13, 0xfe, 0x00, 0xfe, 0xf9, 0xb8, 0x49, 0x22, 0xf5, 0x89, 0x76, 0x99, + 0xbc, 0x05, 0x07, 0xf2, 0xbd, 0xcf, 0xde, 0xf0, 0x87, 0xd9, 0x72, 0xce, 0x8b, 0xe3, 0xf1, 0xfd, 0x34, 0x44, 0xb2, + 0xa6, 0xb0, 0x22, 0x96, 0x14, 0xcf, 0x0f, 0xc2, 0xd3, 0xf7, 0x22, 0x32, 0x44, 0x5a, 0xee, 0xdd, 0x21, 0xbb, 0x65, + 0x91, 0x1f, 0xc0, 0x07, 0xd9, 0xde, 0x9f, 0xc8, 0x9a, 0xd2, 0xfd, 0xe2, 0x89, 0x7f, 0x3c, 0xd2, 0x5f, 0x6f, 0xfc, + 0xe3, 0xf1, 0x3d, 0x7b, 0x40, 0x70, 0x74, 0xbe, 0x87, 0xfe, 0xd1, 0xb7, 0x0e, 0xa8, 0xca, 0xf0, 0xed, 0x6c, 0x3b, + 0xf7, 0x9f, 0xaf, 0xd9, 0x0a, 0xb8, 0x50, 0x94, 0x17, 0xda, 0x2d, 0x7b, 0x40, 0xaf, 0x33, 0x72, 0x22, 0x9a, 0xed, + 0xe7, 0x3e, 0x8b, 0xf1, 0xb9, 0xba, 0x2f, 0x26, 0x5f, 0xbd, 0x2f, 0xee, 0xd9, 0xae, 0xfb, 0xbe, 0x28, 0xdf, 0x74, + 0xd7, 0xcf, 0x8e, 0xed, 0xd9, 0x03, 0xcc, 0xb0, 0xb7, 0xfc, 0xb6, 0x39, 0x75, 0x8c, 0xfd, 0xea, 0x8d, 0x11, 0x40, + 0x99, 0x2d, 0x58, 0x2c, 0x38, 0x28, 0xd5, 0xaa, 0x6d, 0x49, 0xe4, 0xb9, 0x0e, 0x54, 0x9b, 0x11, 0xdc, 0x97, 0xde, + 0xa2, 0xd4, 0x45, 0x4f, 0xfb, 0x14, 0xe4, 0x88, 0x16, 0x0e, 0x1b, 0xf0, 0x57, 0xda, 0x3a, 0xc6, 0x30, 0xcd, 0x7c, + 0xa6, 0x1d, 0x3d, 0xaf, 0xbf, 0xed, 0x3d, 0x93, 0xdf, 0xc8, 0xc0, 0x16, 0x22, 0x29, 0x1c, 0xc7, 0x57, 0xcf, 0xce, + 0xf8, 0xaf, 0x5a, 0x1e, 0xb5, 0xda, 0x2f, 0x94, 0xfa, 0xf4, 0x25, 0x1d, 0xd1, 0xc4, 0xbd, 0x68, 0xcb, 0xb0, 0x46, + 0x59, 0x53, 0x4b, 0x87, 0x61, 0x5c, 0xc3, 0xbe, 0x3c, 0x70, 0xe8, 0x3b, 0x20, 0xd0, 0xe6, 0xa9, 0x14, 0x68, 0xe1, + 0x18, 0x46, 0x61, 0x16, 0x52, 0x1e, 0x17, 0x66, 0x29, 0xef, 0xa9, 0x40, 0x8b, 0x5b, 0x75, 0x8f, 0xa9, 0xed, 0x16, + 0x44, 0x58, 0xbd, 0x65, 0x5c, 0x5e, 0x37, 0xaa, 0x70, 0x5b, 0x80, 0xa2, 0x08, 0xca, 0xe0, 0x40, 0x72, 0xdb, 0x42, + 0x49, 0xb3, 0x51, 0x58, 0x8b, 0x55, 0x51, 0xee, 0x7b, 0x0d, 0x5b, 0xe0, 0x05, 0x55, 0x3f, 0x21, 0x6c, 0xcb, 0x9e, + 0x75, 0x28, 0x17, 0xe9, 0xbf, 0x65, 0xe9, 0xf9, 0x76, 0x6b, 0xce, 0xff, 0xf4, 0x15, 0x7d, 0x54, 0xfe, 0xfb, 0x97, + 0xf4, 0xb3, 0xc1, 0x32, 0x72, 0x4a, 0xfd, 0x10, 0x8d, 0xee, 0xd2, 0x9c, 0x30, 0xb6, 0x7c, 0xfd, 0xf4, 0x1b, 0x64, + 0x0a, 0x92, 0x43, 0x29, 0x55, 0x39, 0xd9, 0x43, 0x5f, 0x78, 0xdd, 0x87, 0x99, 0x60, 0x00, 0xc2, 0x6b, 0xb4, 0xa9, + 0x26, 0x4c, 0xe2, 0xd1, 0x15, 0xfc, 0xdf, 0x08, 0x62, 0xd0, 0x3e, 0x51, 0xd4, 0xb1, 0x6d, 0xa4, 0xeb, 0xb6, 0x73, + 0x90, 0xdc, 0xa9, 0x73, 0x7f, 0x54, 0x4e, 0xfe, 0x1d, 0x0d, 0x91, 0x57, 0xdc, 0x20, 0x56, 0x16, 0x5c, 0x62, 0x31, + 0x54, 0xa4, 0x00, 0xd7, 0x10, 0x44, 0xca, 0xa2, 0xa4, 0x70, 0xc7, 0x41, 0x55, 0x04, 0x60, 0x5c, 0xad, 0x8e, 0x3a, + 0x13, 0x3e, 0x6e, 0xad, 0x45, 0x88, 0x3a, 0x34, 0x6a, 0x65, 0xad, 0xc0, 0x17, 0xa4, 0x2f, 0x1d, 0x0a, 0x62, 0x7a, + 0x14, 0x52, 0x55, 0x3a, 0x14, 0x48, 0x73, 0xa8, 0xf8, 0xc6, 0x60, 0xa3, 0xc8, 0x49, 0xcf, 0x5f, 0x9a, 0x14, 0x65, + 0xcc, 0xf8, 0x20, 0xca, 0x48, 0xe4, 0x75, 0xb8, 0x12, 0xd3, 0x02, 0xf9, 0x46, 0x4f, 0x1f, 0x04, 0xd7, 0xf0, 0x6e, + 0xc8, 0xbd, 0x02, 0x6c, 0x09, 0xd8, 0x01, 0xee, 0x95, 0x19, 0xe5, 0x3a, 0xad, 0xeb, 0xb7, 0xd6, 0x43, 0x31, 0x0c, + 0x9f, 0x5a, 0x02, 0xdb, 0xc9, 0x3a, 0x3a, 0xd1, 0xc3, 0x87, 0xff, 0x75, 0x55, 0x73, 0xd4, 0xa9, 0x5c, 0xce, 0x4e, + 0x27, 0x2c, 0x45, 0xcc, 0xa0, 0xfb, 0xeb, 0xee, 0xa5, 0x00, 0xba, 0x5d, 0x16, 0xf3, 0x6c, 0xb4, 0x97, 0x7f, 0x4b, + 0x37, 0x56, 0x94, 0x36, 0xf1, 0x2e, 0xeb, 0x8d, 0xfd, 0xe1, 0xe8, 0x3f, 0x9e, 0xbe, 0x9b, 0x10, 0xaa, 0xce, 0x96, + 0x6d, 0x74, 0x9c, 0xcb, 0xff, 0xfa, 0xcf, 0x31, 0x59, 0x41, 0x50, 0x10, 0x96, 0x9d, 0x62, 0xa2, 0x82, 0x51, 0xa4, + 0xd8, 0xf0, 0xf1, 0x64, 0x83, 0x3a, 0xe1, 0x8d, 0xbf, 0xd4, 0x3a, 0x61, 0x62, 0x64, 0xa5, 0xf2, 0x37, 0x2c, 0x67, + 0x2b, 0x95, 0x59, 0x40, 0xe6, 0x41, 0x35, 0xd9, 0x18, 0x0d, 0xe6, 0x9a, 0xd7, 0xb3, 0xcd, 0x5c, 0x2a, 0x9f, 0xc1, + 0x94, 0xb3, 0x1c, 0x9c, 0x2d, 0x85, 0xdd, 0x93, 0x40, 0xd1, 0x9a, 0xa1, 0x1b, 0x7f, 0x8a, 0xad, 0x7a, 0x95, 0x56, + 0x35, 0xc0, 0x03, 0x42, 0x0c, 0x0c, 0xb5, 0x57, 0x0b, 0x0f, 0xad, 0x05, 0xb0, 0xf1, 0x47, 0xa5, 0x1f, 0x8c, 0x27, + 0x4b, 0xbe, 0x40, 0xfe, 0xe5, 0xc8, 0x51, 0xbb, 0xf7, 0xfb, 0xde, 0x3d, 0x48, 0xc1, 0x91, 0x6b, 0xa1, 0x40, 0x22, + 0xa0, 0x05, 0xdf, 0xfa, 0xca, 0x07, 0xe3, 0x2d, 0x6a, 0xab, 0x41, 0x41, 0xed, 0xe8, 0x96, 0xc7, 0x8e, 0xde, 0xf9, + 0xfe, 0x8c, 0xbe, 0x7a, 0xa1, 0x85, 0xe3, 0xaf, 0x9c, 0x91, 0x1b, 0xb6, 0xee, 0x90, 0x23, 0x9a, 0x49, 0x87, 0x10, + 0xb1, 0x66, 0x1b, 0xf6, 0x96, 0x54, 0xce, 0x9d, 0x43, 0x76, 0xfe, 0x08, 0x55, 0x7a, 0xad, 0xc7, 0xb7, 0x13, 0xa5, + 0xbb, 0x3d, 0xdd, 0x4d, 0xbe, 0x65, 0x13, 0x11, 0x83, 0x01, 0x6d, 0x10, 0xce, 0xc8, 0x3a, 0x44, 0x2a, 0x1d, 0x20, + 0x04, 0x8e, 0x09, 0x68, 0xfa, 0xf7, 0xaf, 0x49, 0x14, 0x70, 0xa4, 0x8d, 0x90, 0xb5, 0xec, 0x78, 0xac, 0x40, 0xa3, + 0xdc, 0xfc, 0xe1, 0x15, 0xea, 0x34, 0x07, 0xe6, 0xe9, 0x12, 0xf6, 0x1c, 0x3c, 0xd2, 0x8b, 0xd3, 0x23, 0xfd, 0xbf, + 0xa3, 0x89, 0x1a, 0xff, 0xfb, 0x9a, 0x28, 0xa5, 0x45, 0x72, 0x54, 0x4b, 0xdf, 0xa4, 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, 0x26, 0x09, 0x91, 0xe1, 0xd2, 0x80, 0x1f, 0x55, 0x70, 0x88, 0xd3, 0x6a, + 0x93, 0x85, 0x7b, 0x54, 0xa2, 0xde, 0x89, 0x55, 0x9a, 0xbf, 0xa8, 0xff, 0x25, 0xca, 0x02, 0xa6, 0xf6, 0xaa, 0x4c, + 0xe3, 0x80, 0x2c, 0xfc, 0x59, 0x58, 0xe2, 0xe4, 0xc6, 0x36, 0xfe, 0x2c, 0xc7, 0xd3, 0x7e, 0xd5, 0x99, 0x79, 0x20, + 0x81, 0x1a, 0x88, 0x3f, 0x72, 0x2e, 0x2b, 0x8b, 0x07, 0x84, 0x6e, 0xfe, 0xb1, 0x2c, 0x8b, 0xd2, 0xeb, 0x7d, 0x4a, + 0xd2, 0xea, 0x62, 0x2d, 0xea, 0xa4, 0x88, 0x15, 0x94, 0x4d, 0x0a, 0x30, 0xfa, 0xb0, 0xf2, 0x44, 0x1c, 0x5c, 0x20, + 0x50, 0xc3, 0x45, 0x9d, 0x84, 0x00, 0x34, 0xac, 0x10, 0xf6, 0x2f, 0xa0, 0x85, 0x17, 0x61, 0x1c, 0x6e, 0x00, 0x26, + 0x27, 0xad, 0x2e, 0x36, 0x65, 0x71, 0x9f, 0xc6, 0x22, 0x1e, 0xf5, 0x14, 0x25, 0xcb, 0xc7, 0xca, 0x95, 0x73, 0xfd, + 0xc3, 0x1f, 0x14, 0xc0, 0x6e, 0xc0, 0x6c, 0x5b, 0x60, 0x07, 0x00, 0x09, 0x0a, 0x64, 0x0b, 0x75, 0x1a, 0x5d, 0xa8, + 0xa5, 0x02, 0xef, 0xb9, 0x1e, 0xe0, 0x1f, 0x2b, 0xc0, 0x32, 0xae, 0x0b, 0x19, 0x30, 0x82, 0x00, 0x46, 0xe0, 0xa0, + 0x04, 0x0c, 0x9d, 0x61, 0x6d, 0xf1, 0xb8, 0x43, 0x73, 0xa5, 0xdb, 0x92, 0x9b, 0x46, 0x39, 0x5b, 0x89, 0x00, 0xfa, + 0xea, 0xa6, 0xc4, 0xe9, 0x72, 0xd9, 0x4a, 0xc2, 0xbe, 0x7d, 0xdf, 0x4e, 0x15, 0x79, 0x7c, 0x92, 0x86, 0xbc, 0x02, + 0x4f, 0x32, 0x8e, 0x24, 0x51, 0x22, 0xf8, 0x58, 0x35, 0x66, 0x1c, 0x5e, 0xb4, 0x29, 0xa7, 0x0e, 0x66, 0xbd, 0x00, + 0x9c, 0x27, 0x68, 0xcb, 0x00, 0x63, 0x01, 0x83, 0x73, 0x21, 0x96, 0x3c, 0x45, 0xf0, 0x4b, 0x27, 0x52, 0x18, 0x77, + 0x39, 0x0c, 0xf3, 0xa0, 0xe8, 0x5d, 0x52, 0x7f, 0xf4, 0xfb, 0xa8, 0x4d, 0x06, 0x43, 0x50, 0x09, 0xa0, 0xb2, 0x6e, + 0x90, 0x18, 0x58, 0x95, 0x16, 0x12, 0x97, 0x10, 0x2f, 0xf3, 0xd5, 0xb4, 0x8e, 0x82, 0xf7, 0xf5, 0x84, 0x10, 0x4e, + 0x30, 0x3e, 0xc4, 0x0d, 0x10, 0x30, 0x58, 0xc5, 0x05, 0x06, 0xc9, 0x73, 0x89, 0xee, 0x8f, 0xe7, 0x3b, 0x06, 0xb8, + 0x72, 0xde, 0x53, 0xed, 0xea, 0x81, 0xbd, 0x5c, 0xa5, 0x4b, 0x46, 0x08, 0x2b, 0xfe, 0x2f, 0x22, 0xef, 0xdb, 0x61, + 0x02, 0x6a, 0x1b, 0xf9, 0x63, 0x90, 0x98, 0xcb, 0x44, 0x11, 0xc4, 0xa3, 0xac, 0x60, 0x49, 0x1a, 0x6c, 0x47, 0x49, + 0x0a, 0x1a, 0x4d, 0x8c, 0x21, 0x53, 0xa1, 0x1d, 0x92, 0x46, 0xb3, 0x31, 0xd9, 0xc7, 0x90, 0xd7, 0x70, 0xb1, 0x58, + 0xe0, 0x7d, 0x3f, 0x0b, 0xd5, 0xc1, 0xb6, 0x34, 0x87, 0x80, 0x93, 0x04, 0x7b, 0xea, 0x8a, 0x94, 0x84, 0xd9, 0xe8, + 0x53, 0xc8, 0xb9, 0x01, 0x1d, 0x27, 0x8d, 0xa1, 0xfa, 0xc0, 0x24, 0xbc, 0x89, 0xd0, 0x49, 0x59, 0x21, 0x2c, 0xe0, + 0xbe, 0x91, 0xd1, 0x68, 0x25, 0x0d, 0x02, 0x6f, 0x33, 0x6c, 0x05, 0x36, 0xa1, 0xe1, 0x2f, 0x32, 0x0f, 0xd3, 0x6a, + 0x56, 0x82, 0x39, 0xdf, 0x40, 0x25, 0xc6, 0x93, 0xe5, 0x0d, 0xdf, 0xba, 0x58, 0x89, 0xc9, 0x6c, 0x39, 0x9f, 0x6c, + 0x24, 0xd5, 0x1c, 0x08, 0x19, 0x19, 0x5b, 0xc2, 0xfe, 0x61, 0x60, 0x28, 0x1d, 0xd8, 0xd1, 0x54, 0xd3, 0x26, 0x01, + 0x26, 0xd3, 0x25, 0xe7, 0xc3, 0x6b, 0x44, 0x93, 0xd5, 0xa9, 0x7b, 0x99, 0xaa, 0x76, 0x70, 0x4d, 0xce, 0xe4, 0xf4, + 0x48, 0x3d, 0xd5, 0xba, 0x97, 0x6a, 0xb4, 0x1b, 0xe6, 0xa3, 0x9d, 0x1f, 0x80, 0x5b, 0xa7, 0xb0, 0xd3, 0xf7, 0xc3, + 0x7c, 0xb4, 0xf7, 0x35, 0xec, 0x2e, 0x29, 0x04, 0xaa, 0x3f, 0xcb, 0x9a, 0xcc, 0xc5, 0x9b, 0xe2, 0xc1, 0x2b, 0xd8, + 0x33, 0x7f, 0xa0, 0x7f, 0x95, 0xec, 0x99, 0x6f, 0x33, 0xb9, 0xfe, 0x99, 0x76, 0x8d, 0xc6, 0x4c, 0xc7, 0x6b, 0xe7, + 0x60, 0x85, 0x06, 0xc8, 0x2f, 0xd8, 0xd1, 0xde, 0xe4, 0x20, 0x10, 0xa0, 0x7b, 0x09, 0x8e, 0xa2, 0x80, 0xa8, 0x69, + 0x55, 0x79, 0x74, 0xba, 0xf7, 0x0f, 0xf8, 0x46, 0x09, 0xd8, 0xe4, 0xa9, 0x75, 0x6f, 0x19, 0xfb, 0xc7, 0x23, 0x84, + 0xd0, 0xcb, 0xe9, 0x37, 0xda, 0xb1, 0x7a, 0xb4, 0x67, 0x12, 0xfc, 0x1c, 0x31, 0xe9, 0x15, 0x8c, 0x61, 0xe8, 0xc2, + 0x2a, 0x46, 0xf2, 0x0c, 0xc8, 0x1a, 0xbf, 0x41, 0x74, 0x01, 0x8b, 0x5e, 0xef, 0xd5, 0x09, 0x0d, 0x22, 0xa0, 0xd2, + 0x6b, 0xfe, 0x52, 0xe4, 0x73, 0x55, 0x88, 0xde, 0x07, 0x6b, 0xe7, 0xcd, 0x8c, 0x64, 0x99, 0x34, 0x52, 0xed, 0x56, + 0x16, 0x9b, 0xca, 0x9b, 0x9d, 0x91, 0x2e, 0xe6, 0x18, 0x2a, 0x83, 0xc7, 0x01, 0x28, 0x3d, 0xff, 0x12, 0x7a, 0x25, + 0x43, 0xa6, 0x59, 0xa2, 0x99, 0xdd, 0x37, 0xfe, 0x64, 0x9d, 0x7a, 0x31, 0x22, 0x66, 0x03, 0x5b, 0x88, 0xdb, 0xa2, + 0xd2, 0x6d, 0x51, 0x28, 0x5b, 0x14, 0xe9, 0x43, 0xed, 0x42, 0x77, 0x66, 0xe1, 0xb3, 0xdc, 0xb4, 0xef, 0x4d, 0x66, + 0xc6, 0x06, 0x68, 0xbb, 0x08, 0xdf, 0x40, 0x07, 0x2a, 0x84, 0xfc, 0x47, 0x44, 0x44, 0x22, 0x60, 0x97, 0x73, 0x77, + 0x62, 0xd3, 0x21, 0x99, 0x87, 0x98, 0x15, 0x6a, 0x94, 0x97, 0x3c, 0x39, 0x19, 0x90, 0x9c, 0x50, 0xb7, 0xfb, 0xfd, + 0xcb, 0xa5, 0x0b, 0x6a, 0xbf, 0xa1, 0xd8, 0x31, 0xba, 0x29, 0xe0, 0x5c, 0xf0, 0x28, 0xef, 0xa5, 0x77, 0x09, 0x68, + 0x8e, 0xed, 0x29, 0xb2, 0x01, 0x9c, 0xde, 0x76, 0x21, 0xc0, 0xf6, 0x59, 0xb3, 0x8d, 0x3f, 0x59, 0xdf, 0x44, 0x53, + 0xaf, 0xe4, 0x33, 0xdd, 0x45, 0x89, 0xdb, 0x45, 0xb1, 0xec, 0xa2, 0x6d, 0x03, 0xc1, 0x8e, 0x6b, 0x3f, 0x00, 0xde, + 0xd0, 0xa8, 0xdf, 0x2f, 0x5b, 0x3d, 0x7b, 0xf6, 0xb5, 0xd3, 0x9e, 0xcd, 0x7c, 0x56, 0x9a, 0x9e, 0xfd, 0x35, 0x75, + 0x7b, 0x56, 0x4e, 0xf6, 0xa2, 0x73, 0xb2, 0x4f, 0x67, 0xf3, 0x40, 0x70, 0xb9, 0x73, 0x5f, 0x56, 0x53, 0x3d, 0xed, + 0x72, 0x3f, 0x68, 0x0d, 0x91, 0xf9, 0xc2, 0xa7, 0xbc, 0x7b, 0x5d, 0xc1, 0x02, 0x96, 0xe0, 0x6e, 0xbd, 0x34, 0xff, + 0x15, 0xbb, 0xbf, 0x17, 0xf4, 0xd2, 0xfc, 0x37, 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, 0xc7, 0x63, 0xd4, 0x31, 0xec, 0xa0, 0x8e, 0x80, 0x61, 0x0f, 0xa1, 0xb1, 0x05, + 0x9e, 0x8f, 0x9f, 0x32, 0x7e, 0x10, 0xa0, 0x36, 0x42, 0x78, 0xbc, 0x5a, 0x94, 0x21, 0xb6, 0xec, 0x0d, 0x52, 0x49, + 0xfd, 0x2c, 0x10, 0x65, 0xb4, 0x0a, 0x68, 0xab, 0x3d, 0x65, 0x69, 0xbc, 0x85, 0x50, 0xb1, 0xd4, 0xc7, 0x10, 0x1a, + 0x38, 0xfc, 0x8e, 0x47, 0x90, 0xe0, 0x4b, 0xae, 0xc9, 0xe6, 0xde, 0xe4, 0xf7, 0xb4, 0xcf, 0x1f, 0x8f, 0x97, 0xd7, + 0x08, 0x4a, 0x97, 0xc2, 0x47, 0x2a, 0x11, 0xd5, 0x53, 0xdc, 0x94, 0x90, 0xcd, 0x92, 0x95, 0x7e, 0xf0, 0xab, 0xfa, + 0x05, 0x00, 0xb2, 0x10, 0x68, 0x13, 0x99, 0xfd, 0xe9, 0x42, 0x45, 0x17, 0x00, 0x87, 0xf8, 0xe3, 0x27, 0x88, 0xbe, + 0xa1, 0x65, 0x5a, 0x3e, 0x4e, 0x78, 0x08, 0x5a, 0x5b, 0xd2, 0x49, 0xc4, 0x4a, 0x81, 0x0d, 0x91, 0xf0, 0xfd, 0xfe, + 0x65, 0x2c, 0xe9, 0x40, 0xa3, 0x56, 0xf7, 0xc6, 0xad, 0xee, 0x95, 0xaf, 0xeb, 0x4e, 0x6e, 0x7c, 0x50, 0xb4, 0xcf, + 0xe6, 0x8d, 0xca, 0xf7, 0x6d, 0x9d, 0xb3, 0x3f, 0xdf, 0x3b, 0x72, 0x4e, 0x7c, 0x7b, 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, 0x9f, 0xf4, 0xea, 0x65, 0xdc, 0x03, 0x72, 0x74, 0x80, + 0x70, 0x5e, 0xf7, 0x1b, 0xb6, 0xff, 0xe6, 0x97, 0xf7, 0x27, 0x2f, 0x03, 0x9b, 0x14, 0x89, 0x6d, 0x25, 0x9f, 0xf5, + 0x40, 0xe1, 0xd7, 0x63, 0xbd, 0xba, 0xd8, 0xf4, 0x58, 0x0f, 0xb5, 0x80, 0xe8, 0x61, 0x01, 0xea, 0xbf, 0x9e, 0x7d, + 0x1a, 0x0a, 0x07, 0xd9, 0x38, 0x55, 0xa0, 0xc8, 0x82, 0x3f, 0x17, 0xa3, 0x4d, 0x41, 0x80, 0xc8, 0x96, 0x90, 0x96, + 0x9f, 0xcd, 0x1e, 0x97, 0x5a, 0x92, 0xc1, 0x37, 0x01, 0x99, 0x1d, 0x58, 0x39, 0x41, 0xe9, 0xb8, 0x33, 0xe0, 0xca, + 0x16, 0x8f, 0x76, 0xfb, 0xd3, 0x20, 0x3b, 0x6b, 0x4e, 0x1a, 0xed, 0xc3, 0x3e, 0x05, 0x4e, 0x1a, 0x10, 0x7b, 0x44, + 0xa0, 0xef, 0xb6, 0xb9, 0xf4, 0xd1, 0xe1, 0x9c, 0x17, 0xf2, 0xcf, 0xa9, 0xc4, 0xe0, 0x1e, 0x4a, 0xac, 0x81, 0x40, + 0xe5, 0x19, 0xaa, 0x1c, 0x36, 0xc8, 0xf1, 0xcf, 0x8e, 0x64, 0x26, 0x31, 0x59, 0xe4, 0x6e, 0xcd, 0x54, 0xf8, 0x81, + 0x00, 0xfe, 0x73, 0x0e, 0x5c, 0x60, 0xb3, 0xb9, 0xaf, 0xa6, 0xb8, 0xb8, 0x01, 0x7f, 0x4c, 0xe1, 0xe7, 0x3c, 0x85, + 0x9d, 0xf6, 0xb0, 0x29, 0xaa, 0x14, 0x75, 0x1b, 0x85, 0x45, 0x25, 0x0b, 0xa6, 0x35, 0xa4, 0x89, 0x0e, 0xa3, 0x3f, + 0xc8, 0x19, 0x28, 0x08, 0xf9, 0x75, 0xd3, 0x00, 0x23, 0x95, 0x5c, 0x1e, 0x54, 0x49, 0xe0, 0x05, 0xd8, 0x05, 0x39, + 0xdb, 0x14, 0x10, 0x64, 0x9b, 0x14, 0x65, 0xfa, 0xa5, 0xc8, 0xeb, 0x30, 0x0b, 0xf2, 0x51, 0x5a, 0xfd, 0x55, 0xff, + 0x04, 0xe6, 0x6d, 0x2a, 0x46, 0xb5, 0x8a, 0xc9, 0x6f, 0xf4, 0xfb, 0xc5, 0xa0, 0xf5, 0x21, 0x83, 0x8f, 0x5e, 0x9b, + 0x06, 0x7f, 0x74, 0x1a, 0xec, 0x30, 0xd1, 0x08, 0x80, 0x64, 0x4e, 0x2d, 0x79, 0x28, 0xfa, 0x23, 0xa8, 0xb0, 0x46, + 0xb9, 0x53, 0x30, 0x58, 0xff, 0xf1, 0x68, 0x07, 0xa6, 0x5e, 0x1c, 0x6d, 0xc9, 0x0e, 0x9a, 0xfb, 0x06, 0xb8, 0x5f, + 0x23, 0x5b, 0xcc, 0x2a, 0x80, 0x66, 0xaf, 0x11, 0x19, 0x9f, 0xbc, 0x00, 0xc6, 0x6c, 0x93, 0x85, 0x91, 0x88, 0x83, + 0xb1, 0x6a, 0xcc, 0x98, 0x81, 0x81, 0x0b, 0x74, 0x2d, 0x93, 0x92, 0x34, 0xa4, 0x83, 0x01, 0x2b, 0x65, 0x0b, 0x07, + 0xbc, 0x68, 0x4e, 0xdb, 0xf1, 0xba, 0x45, 0xe3, 0x81, 0xed, 0x62, 0x87, 0xfb, 0x1f, 0x8a, 0xdd, 0xdb, 0x70, 0x47, + 0x7a, 0x85, 0x8a, 0x25, 0xf4, 0xf3, 0xaf, 0xb2, 0xcf, 0x1a, 0x4e, 0x4e, 0x85, 0x66, 0x68, 0x29, 0x12, 0x4a, 0xf1, + 0x4e, 0x4f, 0x0a, 0x8c, 0x65, 0x2c, 0xfc, 0x03, 0x70, 0x4e, 0x17, 0x8a, 0xc8, 0x1d, 0x38, 0x8e, 0x6f, 0xa1, 0x82, + 0x51, 0xc3, 0xc1, 0xcb, 0x18, 0xb6, 0x45, 0x31, 0x0b, 0x09, 0xa7, 0x10, 0x2e, 0x56, 0x59, 0xbf, 0x2f, 0x7f, 0x51, + 0x17, 0x5d, 0x65, 0xb2, 0xee, 0x93, 0x70, 0x64, 0xc6, 0x72, 0xea, 0x85, 0xe4, 0x79, 0xcf, 0x93, 0x69, 0xf2, 0xb4, + 0x0a, 0x22, 0x80, 0x7c, 0x0e, 0xef, 0xc3, 0x34, 0x03, 0xab, 0x34, 0x29, 0x3f, 0x42, 0xe9, 0x8b, 0xcf, 0x73, 0x3f, + 0xd0, 0xd9, 0x2b, 0x93, 0x0c, 0x6f, 0xe6, 0xad, 0x37, 0xa9, 0x75, 0x5d, 0x3c, 0xe0, 0xef, 0x9c, 0xc1, 0xc6, 0xb9, + 0xce, 0x04, 0x07, 0x5e, 0x24, 0xb5, 0x5e, 0x33, 0xfe, 0x3c, 0xc3, 0x75, 0xa9, 0xda, 0xe8, 0xa3, 0x10, 0x5d, 0x41, + 0xa6, 0x02, 0x14, 0x8a, 0xb4, 0x7f, 0x50, 0x6a, 0x6e, 0x52, 0x69, 0x23, 0x01, 0x74, 0x0f, 0x93, 0x06, 0x5b, 0x0c, + 0x65, 0x2c, 0x4d, 0xa2, 0xdc, 0x69, 0x10, 0x57, 0xf6, 0xe7, 0x5c, 0xe2, 0xd0, 0xb2, 0x48, 0xfe, 0xbd, 0xef, 0xe9, + 0x2b, 0xa4, 0xee, 0x64, 0x81, 0xcc, 0x18, 0x2f, 0xf2, 0xf8, 0x13, 0x10, 0x66, 0x83, 0x36, 0x2a, 0x0a, 0x21, 0x64, + 0x83, 0x18, 0x34, 0x5e, 0xe4, 0xf1, 0x0f, 0x8a, 0xc6, 0x43, 0x3e, 0x8a, 0x7c, 0xf5, 0x57, 0xa9, 0xff, 0x0a, 0x7d, + 0x66, 0x82, 0x47, 0xa8, 0x26, 0xfa, 0x77, 0xcf, 0x67, 0xf7, 0xa0, 0x36, 0x8c, 0xc2, 0xcc, 0x94, 0x9f, 0xfb, 0xa6, + 0x38, 0x7b, 0xfd, 0x15, 0x5d, 0x65, 0x5b, 0xf7, 0xa3, 0x8f, 0x27, 0x04, 0xd6, 0xc6, 0xe8, 0x8a, 0x1b, 0x03, 0xc8, + 0x61, 0xf2, 0x7e, 0x45, 0x69, 0x15, 0xa4, 0x41, 0xe8, 0xa0, 0x21, 0xe8, 0x95, 0x44, 0x1f, 0x48, 0x2c, 0x62, 0x0c, + 0x2f, 0xc4, 0x33, 0x52, 0x93, 0x89, 0x86, 0x78, 0x45, 0xec, 0x87, 0x68, 0xc9, 0xa9, 0x89, 0x6e, 0x84, 0x29, 0x06, + 0x12, 0x3b, 0x83, 0xe4, 0x24, 0xa9, 0x95, 0x5f, 0x3c, 0x93, 0x84, 0x25, 0x76, 0x1e, 0x62, 0x30, 0xa9, 0xa5, 0x3b, + 0xbd, 0xa9, 0xd2, 0x97, 0x13, 0x2d, 0x07, 0xed, 0x03, 0xb0, 0x4b, 0x49, 0xef, 0x9f, 0x14, 0x8a, 0xf8, 0x10, 0xc6, + 0x31, 0x84, 0x6f, 0x11, 0xd5, 0x15, 0x38, 0xd7, 0x0a, 0x34, 0x56, 0x03, 0x0f, 0xcd, 0x2c, 0x9f, 0x0f, 0x39, 0xfd, + 0x54, 0x5a, 0xfe, 0x18, 0xd1, 0xd8, 0x68, 0xdd, 0x1c, 0x8f, 0x07, 0x5a, 0xf5, 0xd2, 0x39, 0xe8, 0xba, 0x99, 0xc4, + 0xc4, 0x0d, 0xa4, 0xeb, 0x47, 0xbf, 0x99, 0xb0, 0x17, 0x51, 0x21, 0x97, 0x42, 0x50, 0xd0, 0xea, 0x40, 0xe0, 0x50, + 0x78, 0x8b, 0x32, 0x5f, 0xc5, 0xb4, 0x81, 0x30, 0xf8, 0xfc, 0x40, 0x7e, 0xbe, 0x29, 0x48, 0xc5, 0x8e, 0x75, 0xed, + 0xf7, 0xb7, 0xa5, 0x07, 0x78, 0x72, 0x26, 0xc9, 0xd3, 0x66, 0x08, 0x2b, 0x02, 0x68, 0xcc, 0x6a, 0xb2, 0x38, 0xe1, + 0xca, 0x1c, 0x7e, 0xcc, 0xbd, 0x92, 0xa5, 0x4c, 0x9d, 0xa7, 0x7a, 0x01, 0x44, 0x1d, 0x6f, 0xd0, 0x8a, 0xd4, 0xaf, + 0xd0, 0xd9, 0x6b, 0x56, 0x42, 0xc6, 0xc3, 0x4b, 0xce, 0xd3, 0xd1, 0x03, 0x4b, 0x78, 0x84, 0x7f, 0x25, 0x13, 0x7d, + 0xf8, 0x3d, 0x70, 0xb8, 0x19, 0x27, 0x3c, 0x72, 0x9b, 0x7d, 0xa8, 0xc2, 0x35, 0xdc, 0x4c, 0x0b, 0x40, 0x72, 0x0b, + 0x92, 0x26, 0xa0, 0x84, 0x44, 0x26, 0x64, 0xd6, 0x94, 0xfc, 0xdc, 0xd2, 0x36, 0x58, 0xc3, 0xa4, 0xf3, 0x80, 0x17, + 0xad, 0x3e, 0x5a, 0x4d, 0xb4, 0xcb, 0xac, 0x9a, 0x0f, 0x71, 0x86, 0x6a, 0x8e, 0xbb, 0x0b, 0xf8, 0x39, 0xe0, 0x39, + 0xcb, 0x9b, 0x74, 0xb4, 0x1f, 0x70, 0xe1, 0xc9, 0x75, 0x9e, 0x8e, 0x76, 0xf8, 0x4b, 0xee, 0x0f, 0x00, 0x1d, 0x4c, + 0x5d, 0x02, 0x7f, 0xaa, 0xb6, 0x9a, 0x4a, 0xfd, 0xd2, 0xda, 0xaf, 0xeb, 0xce, 0x6a, 0xc1, 0x19, 0xa2, 0x2f, 0x43, + 0x07, 0x64, 0xc8, 0x19, 0x33, 0xe0, 0xcf, 0x19, 0x4b, 0xfe, 0x9c, 0xb1, 0xe2, 0xcf, 0x19, 0x37, 0x46, 0x06, 0x50, + 0x82, 0x7b, 0xc9, 0x9f, 0x1f, 0x10, 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, 0xa9, 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, 0x3c, 0xd6, 0xa3, 0x07, 0xbd, 0xd3, 0xc3, 0xaf, 0x84, 0xfd, 0x92, 0x79, 0xf6, + 0x09, 0x02, 0x98, 0x24, 0xf2, 0x4c, 0xc2, 0xd1, 0x8f, 0xe5, 0xe8, 0x6f, 0x1b, 0xfe, 0x25, 0x43, 0x75, 0x77, 0x08, + 0x4c, 0x6c, 0xd9, 0x91, 0x43, 0x70, 0xba, 0xaa, 0x44, 0x02, 0x0e, 0x36, 0x5b, 0x16, 0xe9, 0x3d, 0x1e, 0xe2, 0x7c, + 0x50, 0xf8, 0x08, 0x0d, 0x33, 0x7a, 0xbf, 0xbf, 0x15, 0x5e, 0x2e, 0x5b, 0x79, 0x3c, 0x26, 0xd6, 0x5d, 0xd8, 0xd1, + 0xc7, 0xd1, 0x1e, 0x25, 0xd4, 0x7e, 0x54, 0xeb, 0x4d, 0xa5, 0x1e, 0x54, 0x66, 0x17, 0x12, 0x83, 0x9c, 0xa5, 0xfa, + 0xf4, 0x4a, 0xf5, 0xa1, 0x66, 0x9d, 0xdf, 0xf9, 0x69, 0x9f, 0x8a, 0xd1, 0x46, 0x4e, 0x08, 0x70, 0x1d, 0x24, 0x1a, + 0x1d, 0x00, 0xe3, 0x6c, 0xb3, 0xe5, 0xa5, 0xb6, 0x4e, 0x94, 0x8e, 0xe3, 0x4a, 0x1f, 0xc7, 0xc7, 0xa3, 0x14, 0x33, + 0xae, 0x4f, 0xc4, 0x8c, 0xeb, 0x06, 0xe0, 0xcd, 0x3a, 0x0f, 0xea, 0xe3, 0xf1, 0x9a, 0x2e, 0x45, 0xa6, 0xb3, 0x8d, + 0xf2, 0xb3, 0x1e, 0x3d, 0x3c, 0x4d, 0xd0, 0xdc, 0x5b, 0x61, 0xef, 0x45, 0xb2, 0x3d, 0x93, 0x4d, 0xea, 0x65, 0xe4, + 0xd3, 0x0b, 0xf7, 0xec, 0x92, 0xab, 0x1f, 0x56, 0x5f, 0x4f, 0x7f, 0x15, 0x5e, 0xc4, 0x72, 0xda, 0xad, 0x4b, 0x26, + 0xec, 0x2d, 0x25, 0x97, 0x56, 0x79, 0xf9, 0x74, 0xeb, 0x07, 0x98, 0x99, 0xf6, 0xf4, 0x41, 0x36, 0xa2, 0xfa, 0xb3, + 0x12, 0xb5, 0x32, 0x4c, 0x16, 0xce, 0x4b, 0xa6, 0x9e, 0x0c, 0x78, 0xcc, 0x4a, 0x1e, 0xc9, 0x4e, 0x6f, 0x0c, 0x82, + 0x00, 0xd6, 0x39, 0x69, 0xd5, 0x19, 0x47, 0xa3, 0x55, 0xe5, 0xe2, 0x7c, 0x95, 0x0b, 0x0c, 0xb7, 0xdb, 0xb0, 0xad, + 0xaa, 0xb3, 0xdc, 0xd4, 0x72, 0xe5, 0x3b, 0x80, 0x8f, 0x65, 0x95, 0x0b, 0x3a, 0xa6, 0x4c, 0x9d, 0xb7, 0x10, 0x8c, + 0xad, 0x6a, 0x5c, 0x38, 0x35, 0x2e, 0x78, 0x44, 0xed, 0x6e, 0x9a, 0x7a, 0xb4, 0x03, 0x96, 0xd2, 0xd1, 0x9e, 0x97, + 0xa8, 0x52, 0xf8, 0x9b, 0xe0, 0x87, 0x30, 0x8e, 0x7f, 0x28, 0x76, 0xea, 0x40, 0xbc, 0x2b, 0x76, 0x48, 0xfb, 0x22, + 0xff, 0x42, 0x1c, 0xf0, 0x5a, 0xd7, 0x94, 0xd7, 0xd6, 0x9c, 0x06, 0xb6, 0x86, 0x91, 0x92, 0xc2, 0xb9, 0xf9, 0xf3, + 0x78, 0xa4, 0x95, 0x5d, 0xab, 0xbb, 0x42, 0xad, 0xc7, 0x1c, 0x36, 0xec, 0x45, 0x16, 0xee, 0x45, 0x09, 0x7e, 0x13, + 0xf2, 0xaf, 0xe3, 0x51, 0xab, 0x2c, 0xd5, 0x91, 0x3e, 0x3b, 0x7c, 0x09, 0xc6, 0x0c, 0x5d, 0x9a, 0x80, 0x65, 0x63, + 0x24, 0xff, 0x6a, 0x9a, 0x79, 0xc3, 0x64, 0xcd, 0x14, 0x8e, 0x43, 0xc3, 0x08, 0x69, 0x40, 0xb7, 0x41, 0x6d, 0x78, + 0x32, 0xdf, 0x54, 0xe5, 0x57, 0x77, 0xa4, 0xda, 0x0f, 0x86, 0xd7, 0x13, 0x71, 0x49, 0x97, 0x24, 0xf5, 0x54, 0x42, + 0x49, 0x08, 0x76, 0xed, 0x03, 0x39, 0xb1, 0x02, 0xb2, 0x96, 0xb1, 0xfc, 0x56, 0x0f, 0x08, 0xfd, 0xa7, 0xdd, 0x7a, + 0xa1, 0xff, 0x34, 0xcd, 0x16, 0xea, 0xfa, 0xc3, 0xe4, 0xbe, 0xa3, 0xd7, 0x1f, 0x1c, 0xde, 0xa9, 0xab, 0x8a, 0xcb, + 0x79, 0x58, 0x1b, 0x26, 0xb9, 0x51, 0x16, 0xee, 0x8b, 0x6d, 0xad, 0x96, 0xa7, 0xe3, 0x30, 0x02, 0x33, 0x82, 0x02, + 0x64, 0x5d, 0xb7, 0x11, 0x31, 0xcc, 0xe5, 0x32, 0x21, 0x9f, 0x10, 0x90, 0x45, 0xa9, 0x71, 0x3e, 0x6e, 0x81, 0x4a, + 0x04, 0x83, 0xd3, 0xd0, 0x5a, 0x75, 0x93, 0xbf, 0xaa, 0x6c, 0x6c, 0x05, 0xe4, 0x90, 0x64, 0xb2, 0x58, 0x8d, 0xee, + 0xc4, 0xb2, 0x28, 0xc5, 0xcf, 0x58, 0x0f, 0xd7, 0x6c, 0xe1, 0x3e, 0x03, 0x42, 0xfb, 0x89, 0xd2, 0xde, 0x44, 0x9a, + 0xa0, 0x7b, 0xc5, 0xd6, 0x00, 0x32, 0x80, 0xa2, 0xae, 0x76, 0xeb, 0x73, 0x7e, 0x8e, 0xa4, 0x19, 0x0e, 0xa3, 0xdb, + 0xa7, 0xab, 0x60, 0x35, 0xb8, 0x46, 0xad, 0xf4, 0x35, 0x8b, 0x5b, 0x18, 0x54, 0x07, 0xb3, 0x84, 0x83, 0x9a, 0x59, + 0x6b, 0x23, 0x10, 0x4c, 0xf6, 0x50, 0x90, 0x33, 0x57, 0xb0, 0x0f, 0x0a, 0xd6, 0x92, 0xd7, 0xc1, 0xe1, 0xd6, 0xbe, + 0xac, 0x14, 0x57, 0xcf, 0xae, 0x92, 0xd6, 0x85, 0xa5, 0xbc, 0x7a, 0xd6, 0x80, 0xc1, 0xe5, 0x04, 0x9b, 0x2a, 0xf7, + 0x27, 0x5b, 0x00, 0xdd, 0x0a, 0x29, 0xe2, 0x45, 0x29, 0x6c, 0x5b, 0xf9, 0xcc, 0x09, 0x1b, 0x6c, 0xd9, 0x03, 0xdc, + 0x2b, 0x83, 0x92, 0xc1, 0x85, 0x18, 0xb7, 0x9b, 0x7d, 0x80, 0x2b, 0x18, 0x0a, 0x63, 0x1b, 0xfe, 0x3a, 0xf3, 0x22, + 0x25, 0xe0, 0x66, 0x88, 0xf2, 0xb5, 0x85, 0x93, 0x49, 0x4f, 0xae, 0x25, 0x8b, 0x01, 0x0b, 0x1a, 0x7c, 0x47, 0xad, + 0xbf, 0x33, 0xf9, 0x37, 0x9e, 0x1e, 0xfa, 0xc1, 0xe7, 0xcc, 0x5b, 0xfa, 0xec, 0x75, 0x2e, 0xa3, 0x35, 0x49, 0x94, + 0x57, 0x0f, 0x97, 0x20, 0x37, 0x2c, 0x47, 0x0f, 0x6c, 0x09, 0xe2, 0xc4, 0x72, 0x94, 0x50, 0x46, 0x57, 0xb8, 0x57, + 0x99, 0x2d, 0x13, 0x81, 0x14, 0x07, 0x96, 0x52, 0xee, 0x2d, 0x36, 0xc1, 0x12, 0xf7, 0x27, 0x92, 0x0b, 0x28, 0x79, + 0x00, 0xe5, 0x4a, 0x01, 0x01, 0x9f, 0x0e, 0xa0, 0x7c, 0x29, 0x2f, 0xc2, 0x9f, 0x38, 0x51, 0x83, 0xe5, 0xe8, 0xa1, + 0x61, 0x7f, 0xf5, 0x42, 0xcb, 0xfe, 0xb0, 0xd2, 0x9a, 0x86, 0x35, 0x5f, 0xc1, 0xb4, 0x98, 0xb8, 0x7d, 0xb9, 0xb6, + 0xab, 0xe2, 0xb3, 0xb5, 0x3a, 0xbb, 0xa9, 0x21, 0x09, 0xfb, 0x8a, 0xac, 0x02, 0x1c, 0xac, 0x8a, 0xb8, 0x67, 0x59, + 0x1e, 0xc2, 0xe8, 0xcf, 0x6d, 0x5a, 0x0a, 0x0b, 0x55, 0xd2, 0x3f, 0x34, 0xa5, 0x40, 0x2a, 0x13, 0x9d, 0x68, 0x21, + 0xb8, 0x02, 0x83, 0xc0, 0xbd, 0xc8, 0x6b, 0x00, 0x8c, 0x01, 0x97, 0x02, 0x65, 0xd9, 0x96, 0x10, 0x52, 0xdd, 0xcf, + 0x40, 0x6d, 0x27, 0xee, 0xd3, 0x88, 0xac, 0x85, 0xe8, 0xab, 0x60, 0xcc, 0x9c, 0xd7, 0xd2, 0x2d, 0x36, 0x5d, 0x6f, + 0xd7, 0xb7, 0xe8, 0x5c, 0xda, 0x72, 0xf3, 0x13, 0xb6, 0x58, 0x2b, 0x50, 0x36, 0x21, 0x69, 0xbb, 0xe2, 0x15, 0xca, + 0x26, 0xb4, 0xb4, 0x0f, 0xd4, 0xa3, 0x42, 0x75, 0xb2, 0xf5, 0x52, 0x3e, 0xb5, 0x08, 0xab, 0xc5, 0x55, 0xee, 0x07, + 0xa0, 0x9b, 0x4a, 0xab, 0x17, 0x75, 0x8d, 0xa6, 0x50, 0xab, 0x85, 0xe3, 0x46, 0x3b, 0x9b, 0x2e, 0xd3, 0x15, 0xe2, + 0xac, 0x4a, 0x3b, 0xf4, 0x0f, 0x99, 0x76, 0xbd, 0xec, 0xe8, 0x37, 0xe3, 0xea, 0x02, 0x17, 0x62, 0x03, 0x3e, 0xe7, + 0xfe, 0xf2, 0x7a, 0xcf, 0xe2, 0x1e, 0x44, 0x3c, 0x03, 0x7b, 0x52, 0xfb, 0x43, 0xf5, 0xa9, 0x2b, 0x18, 0xb2, 0x30, + 0x4a, 0xfd, 0x45, 0xca, 0x7b, 0x4f, 0x70, 0xdc, 0x3f, 0x57, 0x3d, 0xf6, 0xd7, 0x8c, 0x1f, 0xea, 0x62, 0x1b, 0x25, + 0x14, 0xd5, 0xd0, 0x5b, 0x17, 0xdb, 0x4a, 0xc4, 0xc5, 0x43, 0xde, 0x63, 0x98, 0x0c, 0x63, 0x21, 0x53, 0xe1, 0x4f, + 0x99, 0x0a, 0x1e, 0x21, 0x94, 0xb8, 0xdd, 0xf4, 0x48, 0xbb, 0x09, 0x71, 0x4a, 0xb5, 0x28, 0x65, 0x32, 0xfe, 0xad, + 0x9f, 0x40, 0x79, 0x4e, 0xd1, 0x32, 0xfd, 0xa4, 0x70, 0x99, 0xbe, 0xdd, 0x9c, 0x96, 0x9e, 0x89, 0x50, 0x67, 0x2e, + 0xb6, 0xb5, 0x4e, 0xc7, 0xd8, 0x29, 0x9d, 0xda, 0xb0, 0x77, 0xb9, 0xe2, 0xb2, 0xa2, 0xf0, 0x6f, 0x24, 0xb2, 0xea, + 0x19, 0x71, 0xfc, 0x9f, 0x59, 0xfb, 0x0c, 0xab, 0xc0, 0x2f, 0x03, 0x79, 0xbf, 0x00, 0xf8, 0xb8, 0xae, 0xcb, 0xf4, + 0x6e, 0x0b, 0xb4, 0x21, 0x34, 0xfc, 0x3d, 0x1f, 0x19, 0x30, 0xdd, 0x47, 0x38, 0x43, 0x7a, 0xa8, 0x73, 0x4e, 0x67, + 0x65, 0x3a, 0xe7, 0x2a, 0xac, 0x25, 0x38, 0xc8, 0x49, 0x53, 0xc9, 0x75, 0x09, 0x6a, 0x26, 0x70, 0xfb, 0xd0, 0x1e, + 0x11, 0x42, 0x6d, 0xca, 0x6a, 0x7a, 0x09, 0x35, 0xef, 0xe4, 0xb4, 0xa3, 0x49, 0x09, 0xae, 0x1a, 0x3a, 0x2b, 0xd7, + 0x7f, 0x1d, 0x8f, 0xbd, 0xbb, 0xac, 0x88, 0xfe, 0xe8, 0xa1, 0xbf, 0xe3, 0xee, 0x36, 0xfd, 0x02, 0xd1, 0x32, 0xd6, + 0xdf, 0x90, 0x01, 0x1d, 0x4f, 0x86, 0x77, 0xc5, 0xae, 0xc7, 0xde, 0xe5, 0x78, 0x81, 0x55, 0xd7, 0x8f, 0x3f, 0x40, + 0x42, 0xd5, 0xb5, 0x2f, 0x2c, 0x9e, 0x30, 0x4f, 0x89, 0xb6, 0x85, 0x0f, 0x61, 0xa1, 0xef, 0x72, 0xd8, 0x84, 0xa1, + 0x79, 0xd4, 0x3d, 0x4a, 0xda, 0x85, 0xbe, 0xf4, 0xb5, 0xec, 0x2b, 0xdf, 0xb9, 0x02, 0x58, 0xd9, 0x67, 0x36, 0xdc, + 0x93, 0xfe, 0x94, 0xea, 0xc3, 0xf6, 0xb7, 0x64, 0x01, 0x85, 0x16, 0xd6, 0x53, 0x39, 0x3b, 0x37, 0x25, 0x4f, 0xb3, + 0xe9, 0x61, 0x03, 0x7b, 0xd4, 0x3d, 0x7a, 0x4d, 0x05, 0x97, 0xd7, 0x66, 0xf4, 0xfe, 0x61, 0x28, 0x54, 0x47, 0x9d, + 0x3b, 0xc8, 0xa6, 0xb4, 0x2e, 0x39, 0xbf, 0x59, 0xb9, 0xa3, 0x30, 0xbf, 0x0f, 0xc1, 0x33, 0xac, 0x7b, 0x77, 0x71, + 0xde, 0xfb, 0xb3, 0x35, 0x47, 0xfe, 0x9a, 0xcd, 0x52, 0xc4, 0x22, 0x99, 0x83, 0xd5, 0x0f, 0xfd, 0x3c, 0x0e, 0xbb, + 0xa0, 0x82, 0xe3, 0xa6, 0x01, 0x1d, 0x36, 0x64, 0xd6, 0xbe, 0x44, 0xe0, 0x54, 0x23, 0x48, 0x53, 0x13, 0xd4, 0x2c, + 0x0f, 0x91, 0xd8, 0x2e, 0x65, 0xbb, 0xc0, 0xd0, 0xb6, 0x4d, 0x89, 0xf6, 0x0c, 0xde, 0x37, 0x69, 0x39, 0x51, 0xa1, + 0x59, 0xa4, 0xad, 0x92, 0xf1, 0xef, 0x44, 0x9b, 0x29, 0xd9, 0x63, 0x6b, 0xe0, 0xbd, 0x04, 0xe5, 0x64, 0x98, 0x62, + 0xf8, 0x8e, 0xaf, 0x77, 0x1e, 0x73, 0xcf, 0x39, 0x65, 0x9b, 0x94, 0x1d, 0xc1, 0x72, 0x22, 0x1b, 0xdf, 0x52, 0xbc, + 0xe1, 0xfb, 0xbb, 0x4a, 0x94, 0x00, 0x7a, 0x59, 0xf0, 0xe7, 0xd2, 0xe6, 0x0a, 0xdd, 0xee, 0xde, 0x51, 0x0a, 0xbf, + 0xe4, 0xe5, 0xf1, 0xb8, 0x4b, 0xbd, 0x10, 0x3a, 0x5f, 0xc4, 0xef, 0xc0, 0x1c, 0xc6, 0x10, 0x9b, 0x11, 0x20, 0xcc, + 0xf1, 0x01, 0x75, 0xb0, 0x7e, 0x04, 0xa0, 0x71, 0x02, 0x05, 0x18, 0x7d, 0xb5, 0x2d, 0xe8, 0x5b, 0x5e, 0x5c, 0x44, + 0x88, 0x1a, 0x05, 0x98, 0x28, 0x69, 0x16, 0xc3, 0x70, 0xa0, 0xf3, 0xfb, 0xf6, 0xae, 0x2e, 0x05, 0x0e, 0xbd, 0x63, + 0x19, 0xfe, 0xdb, 0xff, 0x58, 0x5b, 0x5a, 0x55, 0xb6, 0x5b, 0xe3, 0x34, 0xf3, 0xbf, 0xdd, 0x16, 0xfa, 0xfe, 0x4b, + 0xa1, 0x78, 0xde, 0xf1, 0xba, 0xfd, 0x05, 0xa2, 0xf7, 0x75, 0x2b, 0x57, 0xa5, 0x76, 0xc3, 0x4c, 0xf9, 0x43, 0x9a, + 0xc7, 0xc5, 0xc3, 0x28, 0x6e, 0x1d, 0x79, 0x93, 0xf4, 0x92, 0xf3, 0x2f, 0xe0, 0x6c, 0xfd, 0x05, 0xc8, 0x78, 0x5f, + 0x0a, 0xe3, 0x88, 0x49, 0x1c, 0x7c, 0x07, 0x31, 0x8a, 0xb6, 0x25, 0x6c, 0xc8, 0xed, 0xd3, 0x12, 0x34, 0x33, 0xfd, + 0x3e, 0x4a, 0x94, 0xd6, 0x7c, 0xff, 0x8b, 0x9c, 0xef, 0x2f, 0x85, 0xbc, 0x59, 0xc9, 0x0f, 0x9f, 0xac, 0x30, 0xf0, + 0x3d, 0x4e, 0xbf, 0x88, 0x1e, 0x5b, 0x95, 0x3e, 0x7c, 0x57, 0x5a, 0xfa, 0xac, 0xa2, 0xfe, 0x8e, 0x8a, 0x9a, 0x97, + 0x62, 0x44, 0xc4, 0x83, 0xa0, 0x9d, 0x6d, 0x97, 0xda, 0xb5, 0x04, 0xed, 0x82, 0x4d, 0x61, 0xff, 0x7a, 0x6c, 0xc8, + 0xab, 0x7e, 0xff, 0xe7, 0xca, 0x6b, 0xf1, 0xba, 0xeb, 0xd0, 0x94, 0x9f, 0x0a, 0x0f, 0x21, 0x80, 0xb5, 0x0c, 0x94, + 0xf1, 0x1c, 0x60, 0xd2, 0x45, 0x5e, 0xa3, 0x6c, 0x3a, 0x11, 0xf8, 0x98, 0x65, 0x37, 0x4e, 0x32, 0x0d, 0x30, 0xa3, + 0x9a, 0x62, 0xcc, 0x8a, 0x78, 0xb8, 0xf8, 0x88, 0x75, 0xd3, 0xd3, 0x2a, 0xb4, 0x7c, 0x0d, 0xc1, 0xba, 0xc8, 0x32, + 0x8e, 0x62, 0x26, 0x00, 0xd8, 0x7c, 0x04, 0xf9, 0x8a, 0xae, 0x0e, 0x49, 0x2b, 0x55, 0xde, 0xaf, 0x33, 0x22, 0xa3, + 0x49, 0x88, 0xe6, 0xb7, 0xf0, 0xc0, 0xbe, 0x6d, 0x66, 0x54, 0xa9, 0x67, 0x54, 0xee, 0x33, 0x1c, 0x96, 0xc2, 0x31, + 0xe2, 0xff, 0x2d, 0x55, 0x3d, 0x22, 0xd0, 0xab, 0x32, 0xad, 0xa2, 0x22, 0xcf, 0x45, 0x84, 0x08, 0xd5, 0xd2, 0x39, + 0x1c, 0xfa, 0xb1, 0xdf, 0xc7, 0x81, 0x30, 0x2f, 0xfe, 0xf4, 0x58, 0x57, 0xfe, 0x54, 0xe0, 0x5a, 0x49, 0x81, 0x53, + 0x51, 0x23, 0x44, 0x08, 0xef, 0x4f, 0xe0, 0x59, 0x4d, 0x7d, 0xbf, 0xb1, 0x4c, 0x74, 0xff, 0xc8, 0x80, 0xf2, 0x07, + 0xe4, 0xeb, 0x5c, 0x8a, 0x33, 0x75, 0xf2, 0x98, 0x38, 0xe3, 0x00, 0xc4, 0x7c, 0x5d, 0xa2, 0xd1, 0xd8, 0xff, 0x80, + 0x04, 0x43, 0xf5, 0x83, 0x9d, 0x6e, 0xea, 0xfd, 0x33, 0x93, 0x38, 0x8a, 0x3e, 0x6d, 0x93, 0xa7, 0x92, 0xa5, 0xd1, + 0xc2, 0xd1, 0x7b, 0xc4, 0x30, 0x0e, 0xa7, 0xf3, 0x29, 0xc9, 0x36, 0x26, 0xab, 0x00, 0xd2, 0xc9, 0x4c, 0x1d, 0x53, + 0xea, 0x68, 0x9c, 0xeb, 0x05, 0x55, 0xe8, 0xb1, 0x2e, 0x79, 0x05, 0xd6, 0x93, 0x1f, 0xbd, 0xd2, 0x9f, 0x0a, 0x39, + 0x87, 0x8d, 0x44, 0x50, 0xf8, 0x01, 0xae, 0x06, 0x2b, 0x05, 0x0c, 0xa6, 0xbe, 0x85, 0xaf, 0x89, 0xe7, 0x28, 0x78, + 0x14, 0x76, 0x31, 0xb6, 0xe6, 0xbe, 0xf3, 0x49, 0x41, 0xb9, 0x67, 0xc5, 0x9c, 0xe7, 0xc0, 0xb9, 0x0c, 0x0a, 0x61, + 0x3a, 0x9e, 0xe5, 0xff, 0x4c, 0xf2, 0x7a, 0x62, 0x43, 0x80, 0x0c, 0xfe, 0x9c, 0x38, 0x2d, 0xdd, 0xa1, 0x3b, 0x0f, + 0x3d, 0x8b, 0x38, 0x6c, 0xf4, 0x64, 0x53, 0x16, 0xbb, 0x14, 0xf5, 0x12, 0xe6, 0x07, 0xf2, 0xf3, 0x96, 0xfc, 0x10, + 0xa2, 0x78, 0x1b, 0xfc, 0x9a, 0xb1, 0x58, 0xe0, 0x5f, 0x7f, 0xcb, 0x18, 0x4d, 0xb4, 0xe0, 0x5f, 0x59, 0x83, 0x44, + 0xc5, 0x3f, 0x65, 0x93, 0x1c, 0xb8, 0x4c, 0xd5, 0x87, 0xcf, 0x89, 0xf1, 0xd6, 0x6c, 0x78, 0xe4, 0x9b, 0x39, 0xe8, + 0xd4, 0xe7, 0xee, 0xca, 0xf6, 0x54, 0x35, 0xfe, 0x96, 0xea, 0x6a, 0xa4, 0xaa, 0x1a, 0x7f, 0x4b, 0xa9, 0x1a, 0xbf, + 0x65, 0x14, 0xbf, 0x93, 0xfb, 0x0c, 0x99, 0x93, 0x4d, 0x4c, 0xd2, 0xf9, 0x7b, 0xc3, 0x99, 0x5d, 0xf6, 0xab, 0xb7, + 0x89, 0xcc, 0x44, 0x0a, 0xb9, 0x37, 0x00, 0x35, 0x13, 0x7f, 0xae, 0x0c, 0xa7, 0xc4, 0xe5, 0xa5, 0x87, 0x2b, 0x36, + 0xad, 0x5e, 0xd2, 0x82, 0x05, 0x36, 0x2f, 0xb3, 0x3c, 0x45, 0x02, 0xdb, 0xa6, 0xcc, 0xfa, 0xa9, 0xf2, 0x00, 0x82, + 0x99, 0xd4, 0x04, 0x80, 0xb4, 0x10, 0x95, 0x42, 0xe4, 0x2f, 0x71, 0x56, 0x5f, 0xf2, 0xde, 0x36, 0x8f, 0x89, 0xb4, + 0xba, 0xd7, 0xef, 0xa7, 0x17, 0x69, 0x4e, 0x41, 0x0d, 0xa7, 0x59, 0xa7, 0x3f, 0x64, 0x41, 0x9d, 0xc8, 0x55, 0xfa, + 0x77, 0x37, 0xc8, 0xcb, 0xf8, 0xbe, 0xee, 0x7a, 0xfe, 0x44, 0xfd, 0xbd, 0xb7, 0xfe, 0xb6, 0x40, 0x70, 0x27, 0xa7, + 0x7e, 0xb2, 0x2a, 0xe5, 0x89, 0x71, 0x69, 0xef, 0xf9, 0x4d, 0x5d, 0x14, 0x59, 0x9d, 0x6e, 0x3e, 0x48, 0x3d, 0x8d, + 0xee, 0x8b, 0x03, 0x18, 0x83, 0xf7, 0x00, 0x78, 0xa6, 0x43, 0x03, 0xa4, 0xef, 0x19, 0x79, 0xb8, 0xcf, 0x2d, 0xf9, + 0x49, 0x65, 0x6d, 0x92, 0xb0, 0xa2, 0xd8, 0x0c, 0x63, 0x84, 0x92, 0x71, 0x1a, 0x3b, 0xbf, 0xdf, 0x57, 0x7f, 0xef, + 0x31, 0x8a, 0x8a, 0x8a, 0x3b, 0x45, 0xa3, 0xb2, 0xaa, 0x47, 0xdb, 0xc1, 0xf1, 0x78, 0x59, 0xd9, 0x38, 0xda, 0x7a, + 0x05, 0x1c, 0xac, 0x50, 0x29, 0x7b, 0x25, 0xc2, 0xf2, 0xc3, 0x95, 0xdf, 0xef, 0xc3, 0xbf, 0x32, 0xd2, 0xc2, 0xf3, + 0xa7, 0xf8, 0x6b, 0x51, 0x17, 0x18, 0x9e, 0x41, 0x6b, 0x34, 0x87, 0x60, 0x82, 0xbf, 0x77, 0xa0, 0x5e, 0x5a, 0x69, + 0x1f, 0x41, 0xb7, 0x02, 0x3d, 0xa8, 0x87, 0x3e, 0x4d, 0xda, 0x17, 0x12, 0x75, 0x7b, 0xab, 0xd3, 0xe8, 0x8f, 0x0a, + 0x2e, 0xa7, 0x30, 0x39, 0xdc, 0xd0, 0xa7, 0x75, 0xb8, 0xfb, 0x04, 0x4f, 0x7f, 0x06, 0xca, 0xad, 0xe3, 0x11, 0xc5, + 0x16, 0x70, 0xf3, 0x58, 0x87, 0x9f, 0x8b, 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, 0xf9, 0x5c, 0xad, 0xb4, + 0x8b, 0x32, 0xf1, 0x6a, 0x96, 0x81, 0xb3, 0xc0, 0x55, 0xee, 0xb3, 0x4c, 0xab, 0x9e, 0xf2, 0x04, 0x7d, 0x5e, 0xc9, + 0x09, 0xae, 0x04, 0x27, 0x1b, 0x90, 0x5f, 0x80, 0x24, 0x4d, 0x29, 0x6b, 0xca, 0xe7, 0xd7, 0x74, 0x43, 0x46, 0xcf, + 0x79, 0xcf, 0x8b, 0x86, 0xa1, 0x7f, 0xe5, 0x95, 0x10, 0xbe, 0x89, 0xdb, 0x36, 0x4a, 0x61, 0x7f, 0x11, 0x58, 0x7c, + 0xc2, 0x7e, 0xf4, 0x96, 0xfe, 0x74, 0x1c, 0x84, 0x43, 0xe4, 0x86, 0x8a, 0x39, 0xb0, 0xa7, 0x01, 0x8b, 0x4d, 0x7c, + 0xb3, 0x9d, 0xc4, 0x83, 0x81, 0xaf, 0x33, 0x16, 0xb3, 0x18, 0x68, 0x90, 0xe3, 0xc1, 0xf5, 0x5c, 0x9f, 0x10, 0xfa, + 0x61, 0x44, 0xe5, 0xa8, 0x40, 0xe7, 0x20, 0x1a, 0x2c, 0x01, 0x4f, 0xbd, 0x95, 0x0d, 0x92, 0x8c, 0x49, 0x26, 0x71, + 0xad, 0x49, 0xaa, 0xc3, 0x09, 0xad, 0x03, 0x1d, 0x57, 0x17, 0xd0, 0xf9, 0xb8, 0xee, 0x7d, 0xbc, 0x1a, 0x2e, 0xa8, + 0xf4, 0x2b, 0x31, 0xf0, 0xea, 0xe9, 0x38, 0xb8, 0xa6, 0x5b, 0xe1, 0x62, 0x1d, 0xee, 0x7e, 0x96, 0x0f, 0x1c, 0x77, + 0x54, 0xd2, 0x10, 0x18, 0xbc, 0x3d, 0x74, 0x37, 0x33, 0x34, 0xd4, 0x49, 0xfb, 0x30, 0x0e, 0xe5, 0x10, 0xab, 0x56, + 0x5c, 0x49, 0x6f, 0x04, 0xdf, 0x2e, 0x14, 0x63, 0xd9, 0xd8, 0xb5, 0xa1, 0x28, 0xfc, 0x15, 0xc0, 0x0e, 0xb5, 0xbf, + 0x52, 0xc9, 0xc7, 0xc8, 0xa8, 0xa6, 0x81, 0x8e, 0x01, 0x58, 0xb2, 0x34, 0x91, 0x54, 0x91, 0x46, 0xe2, 0x8f, 0xcc, + 0x58, 0x47, 0x4d, 0xd7, 0x17, 0x4c, 0x55, 0x8b, 0xa4, 0xdb, 0x99, 0xc4, 0x72, 0x22, 0x49, 0x6d, 0xf7, 0x11, 0x31, + 0x18, 0xf8, 0x60, 0x23, 0xa6, 0x99, 0x08, 0x47, 0x3c, 0x2a, 0x91, 0x45, 0x97, 0xdf, 0x46, 0x99, 0xb4, 0x7d, 0x59, + 0x91, 0x2d, 0x08, 0xa6, 0x27, 0xd1, 0x07, 0x49, 0xd0, 0xba, 0x48, 0xa4, 0x19, 0x21, 0xc0, 0x8f, 0x27, 0xe5, 0x8d, + 0xfe, 0x1c, 0x34, 0xad, 0x04, 0x2f, 0x19, 0x24, 0x8f, 0xc4, 0xcf, 0xa4, 0x60, 0x16, 0x63, 0xf9, 0x60, 0x80, 0xe5, + 0xe4, 0x4f, 0x1d, 0x93, 0xf4, 0x5f, 0x3a, 0x9d, 0xb0, 0x5f, 0x78, 0x95, 0xad, 0xe5, 0x4d, 0x73, 0xef, 0x85, 0x97, + 0xb3, 0x54, 0xc3, 0x32, 0xe8, 0xbf, 0x26, 0xda, 0x05, 0x5b, 0x5b, 0xc6, 0x84, 0x55, 0x3f, 0x80, 0xb4, 0x47, 0xba, + 0xbc, 0x7c, 0x58, 0x31, 0xc1, 0xa3, 0x2b, 0x6b, 0x1e, 0x44, 0x57, 0xc2, 0x47, 0x2e, 0xbb, 0x49, 0x72, 0x33, 0x9e, + 0xf8, 0xe1, 0x60, 0xa0, 0x00, 0x68, 0x69, 0x9d, 0x14, 0x83, 0xf0, 0xa9, 0x90, 0x03, 0x69, 0x74, 0x54, 0x05, 0x58, + 0x2c, 0xb3, 0x9b, 0x72, 0x92, 0x0d, 0x06, 0x3e, 0x88, 0x8d, 0x89, 0xdd, 0xd0, 0x6c, 0xee, 0xb3, 0x33, 0x05, 0x59, + 0x6d, 0x0e, 0x5b, 0x33, 0xdd, 0x02, 0x03, 0x80, 0x41, 0x44, 0xb0, 0xdc, 0x67, 0x46, 0x3e, 0xa2, 0x4e, 0x4f, 0x61, + 0x04, 0x04, 0xbf, 0x9e, 0x08, 0x44, 0x2e, 0x12, 0xa8, 0x07, 0x98, 0x09, 0x30, 0xa3, 0x8a, 0xe1, 0x35, 0xb0, 0x8b, + 0x57, 0xe6, 0x15, 0x83, 0xfe, 0x45, 0x93, 0x2c, 0xd1, 0x54, 0xe2, 0x68, 0x8c, 0x9c, 0x4a, 0x63, 0x64, 0x40, 0xec, + 0xe2, 0xf8, 0xf7, 0x94, 0x1e, 0x05, 0x29, 0xfb, 0x9c, 0x1b, 0xe2, 0x70, 0x14, 0x5f, 0xc1, 0xaa, 0x71, 0x3c, 0xd6, + 0xe6, 0xf5, 0x74, 0x56, 0xcf, 0x07, 0x22, 0x80, 0xff, 0x86, 0x82, 0xfd, 0xa2, 0xa9, 0xc8, 0x0d, 0x52, 0xe7, 0xf1, + 0x98, 0x82, 0x7c, 0xaa, 0x9b, 0xfc, 0x43, 0xee, 0xee, 0xa7, 0xb3, 0xb9, 0x35, 0x47, 0xaf, 0x6a, 0x5c, 0xb7, 0x56, + 0x37, 0x14, 0x12, 0xad, 0x69, 0x52, 0xdc, 0xe4, 0x93, 0x62, 0xc0, 0x2b, 0x5f, 0xa8, 0x2e, 0xb6, 0x46, 0xb0, 0xf0, + 0xe7, 0x16, 0x08, 0x93, 0x71, 0x2f, 0x3e, 0x59, 0xc8, 0x29, 0xed, 0xda, 0x6a, 0xb7, 0xb5, 0x49, 0xd3, 0x58, 0x35, + 0xbc, 0x86, 0x5d, 0x3a, 0x45, 0xb4, 0xed, 0x92, 0xe0, 0x0b, 0xd0, 0xb2, 0xba, 0x10, 0x79, 0x4c, 0xbf, 0x42, 0x7e, + 0x2d, 0x86, 0xff, 0x29, 0xdd, 0x9b, 0x53, 0x1b, 0xe4, 0x00, 0xb6, 0x7b, 0x0f, 0xb7, 0x63, 0xf4, 0x40, 0x06, 0x6f, + 0x04, 0x10, 0x8d, 0xaf, 0xa7, 0xd6, 0x8c, 0x89, 0x86, 0x05, 0x2b, 0x87, 0x91, 0x1f, 0x20, 0xe3, 0xe5, 0x14, 0x58, + 0xd9, 0x4f, 0x8a, 0xb8, 0xf6, 0x87, 0x91, 0x7f, 0xf5, 0x2c, 0xc8, 0xb8, 0x17, 0x0d, 0x3b, 0xbe, 0x00, 0x7b, 0xf5, + 0xd5, 0x33, 0x16, 0x0d, 0x78, 0x7e, 0x53, 0x4f, 0xb3, 0x60, 0x98, 0xb1, 0xe8, 0xa6, 0x18, 0x82, 0x0f, 0xed, 0xf3, + 0x72, 0x10, 0xfa, 0xbe, 0xd9, 0x39, 0x74, 0x37, 0x24, 0xf2, 0x08, 0xfb, 0x2b, 0xb8, 0xed, 0x6a, 0x89, 0x19, 0xe0, + 0x06, 0x56, 0x11, 0x33, 0xd8, 0xf2, 0x57, 0xcf, 0x0c, 0x97, 0x50, 0xfe, 0x5c, 0x6a, 0x36, 0x0a, 0x34, 0x27, 0xe7, + 0x68, 0x4e, 0x56, 0x42, 0x2d, 0xf9, 0xa4, 0xc2, 0xa9, 0x3a, 0x9f, 0x68, 0xbb, 0xd1, 0x18, 0x03, 0x17, 0xed, 0xb9, + 0x2d, 0x8c, 0xcc, 0x74, 0x91, 0xa2, 0x01, 0x0b, 0xcf, 0xc4, 0x29, 0x8d, 0x01, 0xed, 0xcb, 0x81, 0xa5, 0x0d, 0xf9, + 0xab, 0x9c, 0x19, 0x68, 0x1b, 0x52, 0x1a, 0x35, 0x03, 0x7f, 0xa6, 0x26, 0xcc, 0xaf, 0x60, 0x25, 0x82, 0xa8, 0x2e, + 0xc0, 0x24, 0xa9, 0xc8, 0x68, 0xa4, 0xac, 0x44, 0x72, 0x0e, 0x78, 0x1f, 0xc1, 0x93, 0x45, 0xec, 0x6a, 0x7f, 0x4a, + 0xff, 0xab, 0xc3, 0xe7, 0xda, 0x7f, 0x2a, 0x80, 0x85, 0x5c, 0x1a, 0x44, 0x06, 0x0a, 0x87, 0xd4, 0x54, 0x22, 0x4e, + 0x1c, 0xcf, 0xc0, 0xd7, 0x70, 0x81, 0xa6, 0x80, 0xfe, 0xa0, 0x66, 0x14, 0x91, 0x85, 0xbf, 0x7a, 0x76, 0x53, 0xb7, + 0x7a, 0x9e, 0x39, 0xaf, 0x41, 0x33, 0x03, 0x21, 0x3d, 0x4e, 0xd5, 0xdb, 0x90, 0xe8, 0xbc, 0xbc, 0xd4, 0x2f, 0x13, + 0x22, 0x59, 0x11, 0x79, 0xfa, 0x3e, 0x07, 0xf3, 0x88, 0x22, 0x74, 0x70, 0x65, 0x1e, 0x8f, 0x97, 0x82, 0xc2, 0x77, + 0x94, 0xe7, 0x03, 0x4e, 0xb3, 0x28, 0x01, 0x6d, 0x20, 0xab, 0x4c, 0x99, 0x9b, 0xa4, 0x65, 0xea, 0x3e, 0x80, 0x95, + 0x20, 0x47, 0x37, 0xa7, 0xa0, 0x50, 0x46, 0x82, 0x52, 0x5a, 0x0d, 0x42, 0xa9, 0x0e, 0x8b, 0x20, 0x72, 0xc8, 0x42, + 0xc0, 0xcd, 0x54, 0x34, 0x5a, 0xd2, 0xf0, 0x08, 0xe7, 0x06, 0x0a, 0x01, 0x48, 0xec, 0xa9, 0xa2, 0x8c, 0xcb, 0x61, + 0xce, 0xd6, 0x3c, 0x1c, 0xe2, 0xac, 0x49, 0x5b, 0x9e, 0x83, 0x38, 0x96, 0x4b, 0xbe, 0xc9, 0x11, 0x0c, 0x22, 0xf4, + 0x19, 0xf2, 0x27, 0xcb, 0xf9, 0x77, 0xe7, 0x30, 0xed, 0x08, 0x1f, 0x76, 0xb5, 0x05, 0x17, 0xb3, 0xbb, 0xf9, 0x04, + 0xe2, 0x5b, 0xee, 0xe6, 0xa7, 0x18, 0x22, 0x0b, 0x7f, 0xb0, 0x1a, 0x4a, 0xae, 0x28, 0x74, 0x59, 0x8f, 0x48, 0x91, + 0x3d, 0xdd, 0x70, 0x04, 0xc1, 0x81, 0x56, 0x0d, 0x32, 0x34, 0x12, 0x5f, 0x3d, 0x83, 0xac, 0xc1, 0x86, 0x7f, 0xce, + 0xc9, 0x59, 0xdd, 0x9f, 0x6c, 0xa1, 0x9a, 0x64, 0xb2, 0x56, 0x54, 0xce, 0x5f, 0xaf, 0xca, 0xf2, 0x6c, 0x55, 0x86, + 0xeb, 0x41, 0x57, 0x55, 0x96, 0x1c, 0xa9, 0x0d, 0xd0, 0x9a, 0xae, 0x10, 0x43, 0x21, 0x6b, 0xb0, 0xb4, 0xaa, 0xb2, + 0xa1, 0x3e, 0x81, 0x40, 0x1f, 0x60, 0x19, 0x35, 0xfb, 0xe9, 0xf0, 0x9f, 0xc1, 0x3f, 0x55, 0xc8, 0x52, 0x9d, 0xd6, + 0x99, 0xf8, 0x35, 0x58, 0x32, 0xfc, 0xe3, 0xb7, 0x60, 0x03, 0x58, 0x02, 0x64, 0xb9, 0xdb, 0xda, 0x68, 0xbd, 0xf2, + 0x0a, 0xf1, 0xae, 0xd6, 0x17, 0xfd, 0xd6, 0x6d, 0xa2, 0x56, 0x80, 0x11, 0x0a, 0x2d, 0x02, 0x6c, 0xf5, 0xc0, 0x3d, + 0x05, 0x3f, 0x10, 0xc3, 0xb9, 0x26, 0xad, 0xa9, 0x13, 0x5e, 0x67, 0xe3, 0x48, 0x44, 0xf5, 0x0e, 0x2e, 0xee, 0xf5, + 0xce, 0xe2, 0x6f, 0x54, 0x20, 0x00, 0xb2, 0x98, 0x62, 0xe3, 0xbc, 0x21, 0xbd, 0x32, 0xec, 0x24, 0xf4, 0xde, 0xb0, + 0x13, 0xc8, 0x8b, 0xc3, 0x4e, 0xa1, 0x4b, 0xb4, 0x9d, 0x22, 0x35, 0xd1, 0x76, 0xd2, 0x62, 0x1d, 0x96, 0x10, 0xfc, + 0xaa, 0xbd, 0x75, 0x94, 0xed, 0x8b, 0x2c, 0x61, 0xda, 0x02, 0x46, 0xb9, 0x55, 0x9f, 0x39, 0x45, 0xac, 0x95, 0xbd, + 0xd3, 0x49, 0x95, 0xbb, 0xc8, 0xa7, 0x56, 0x53, 0x64, 0xf2, 0x0f, 0xa7, 0x2d, 0x92, 0x4f, 0x7e, 0x6e, 0x37, 0x4c, + 0xa6, 0x7f, 0x3c, 0xf9, 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, 0x4f, 0x62, 0x57, 0xbf, 0x84, 0x2b, 0x36, 0x3f, 0x34, 0x8a, + 0x9e, 0x5c, 0x9f, 0xa2, 0x6e, 0xc5, 0xd5, 0x69, 0xda, 0x6a, 0x8e, 0x1d, 0x67, 0xc8, 0xc1, 0xb3, 0x82, 0x60, 0x3b, + 0x2a, 0x51, 0xbe, 0x6d, 0x37, 0x1d, 0x13, 0x5b, 0xfd, 0xb3, 0xa8, 0xb6, 0x2b, 0xa8, 0x88, 0x88, 0x4f, 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, 0xe7, 0x77, 0xf5, 0x82, 0x93, 0xc9, 0x7e, 0x72, 0x9a, + 0xbb, 0x5d, 0xa4, 0xfd, 0xc4, 0xb7, 0x61, 0xfe, 0xf5, 0x0d, 0x62, 0x25, 0xea, 0x7f, 0x54, 0x00, 0x34, 0xb8, 0xcd, + 0x63, 0x89, 0x52, 0x7f, 0x50, 0xd5, 0x0f, 0x6a, 0xa6, 0x6a, 0x1a, 0x08, 0xe6, 0x54, 0x0a, 0xf8, 0xc3, 0xed, 0xc2, + 0x15, 0x8f, 0xb8, 0x61, 0x61, 0xfc, 0xd3, 0xab, 0xd9, 0xb9, 0xa0, 0x32, 0x70, 0x33, 0xfe, 0xd3, 0x13, 0xec, 0x1c, + 0xd6, 0x0a, 0xc8, 0x0a, 0x7f, 0x7a, 0xd5, 0x23, 0xef, 0xe7, 0xfc, 0x4f, 0x2f, 0x7f, 0xe4, 0x7d, 0xc4, 0x79, 0xf9, + 0x13, 0x49, 0x9d, 0x10, 0xd5, 0xe5, 0x4f, 0xc2, 0x14, 0x5b, 0xa7, 0xf9, 0x2b, 0x52, 0xf8, 0x04, 0x9f, 0x81, 0xef, + 0x70, 0x1d, 0xee, 0xcc, 0x6f, 0xf0, 0xd8, 0xb1, 0xd8, 0x76, 0xa9, 0x2f, 0xa0, 0x1c, 0x81, 0x45, 0x54, 0xf6, 0xdb, + 0xb9, 0xfd, 0x6a, 0x61, 0x94, 0x31, 0x76, 0x5f, 0xb2, 0x12, 0xa5, 0xb3, 0x7e, 0xbf, 0x90, 0x82, 0x91, 0x5d, 0x58, + 0xa3, 0x3d, 0x4a, 0xd5, 0xab, 0x6f, 0xc3, 0x3a, 0x4a, 0xd2, 0x7c, 0x25, 0xa3, 0x8f, 0x64, 0xd8, 0x91, 0xbe, 0x92, + 0x12, 0xed, 0xb5, 0x0a, 0xcb, 0xd1, 0xec, 0xd7, 0x25, 0x07, 0xca, 0xeb, 0x56, 0x50, 0xbe, 0x6a, 0x02, 0xe8, 0x95, + 0x6a, 0x9f, 0x01, 0x23, 0xa7, 0xb0, 0x54, 0x1e, 0xac, 0xc4, 0xb9, 0xe8, 0xb3, 0xe2, 0x78, 0xf4, 0x2c, 0x34, 0xf3, + 0x0a, 0x1e, 0x84, 0x3b, 0x0b, 0x23, 0x15, 0x2e, 0x84, 0xe2, 0x79, 0x85, 0xb1, 0x15, 0x15, 0x70, 0x20, 0xc3, 0x0f, + 0x08, 0xbc, 0x97, 0xfd, 0x2b, 0x18, 0x0c, 0x13, 0xdc, 0xc8, 0xa8, 0x93, 0x2b, 0xf6, 0x27, 0x06, 0x66, 0x50, 0x4f, + 0x6a, 0xf7, 0xd9, 0x83, 0x0a, 0xec, 0x85, 0x33, 0xa0, 0xbd, 0x1b, 0xa3, 0x9f, 0x55, 0xb1, 0x71, 0xd2, 0x3f, 0x15, + 0x1b, 0x48, 0xa6, 0xc3, 0xe2, 0x64, 0x9b, 0x86, 0x47, 0xf2, 0xe4, 0x38, 0xdd, 0xf4, 0x8f, 0xc7, 0x31, 0x7e, 0x1c, + 0xe5, 0xd7, 0x16, 0xf0, 0x2a, 0x6e, 0x21, 0x8d, 0x45, 0x8a, 0xde, 0x81, 0x98, 0x43, 0xd1, 0x4b, 0xf6, 0x5b, 0xc6, + 0xcb, 0x89, 0xa0, 0x94, 0x24, 0x36, 0xbc, 0x23, 0x3d, 0x4d, 0xeb, 0xd1, 0x4e, 0x06, 0xec, 0xd7, 0xa3, 0x3d, 0xfd, + 0x05, 0x8a, 0x47, 0x0b, 0x7f, 0x49, 0x7f, 0x17, 0x77, 0x73, 0xcf, 0xf9, 0xa6, 0xf1, 0x1d, 0x71, 0x81, 0x62, 0xcd, + 0xee, 0xaf, 0x69, 0xe9, 0xac, 0x03, 0xc1, 0x01, 0x6f, 0xb1, 0x8b, 0xf6, 0xfd, 0xc6, 0x75, 0x7a, 0x3a, 0x7c, 0xeb, + 0xd6, 0x28, 0xdf, 0xfb, 0x87, 0x44, 0x39, 0x38, 0xbc, 0x72, 0xd1, 0xfc, 0xed, 0xa7, 0x0c, 0x49, 0x85, 0xe6, 0x06, + 0xdb, 0xc9, 0x16, 0x61, 0x6d, 0x8c, 0x83, 0x9c, 0xad, 0xca, 0x30, 0x02, 0x06, 0x75, 0xec, 0x7f, 0xf4, 0xd9, 0xb4, + 0x21, 0xfb, 0x00, 0x50, 0xb9, 0x0a, 0x01, 0x7b, 0x00, 0x4e, 0x34, 0xc2, 0x0d, 0x70, 0xab, 0xd1, 0x92, 0x0e, 0xea, + 0xb6, 0x60, 0x20, 0x5a, 0xc2, 0xc6, 0x09, 0x5d, 0xdf, 0x57, 0x84, 0x8f, 0xca, 0xb7, 0x0f, 0xe5, 0xaf, 0x9e, 0xb3, + 0xff, 0xde, 0x61, 0x4d, 0x4d, 0xb9, 0x05, 0xcc, 0x9c, 0xb5, 0xc8, 0x2b, 0x84, 0x4e, 0x91, 0xdf, 0xab, 0xba, 0x12, + 0xc3, 0x65, 0x2d, 0xca, 0xce, 0xec, 0xd6, 0x89, 0xde, 0x39, 0x05, 0xb5, 0x54, 0x36, 0x20, 0x01, 0x6e, 0x20, 0xc5, + 0xb6, 0xc0, 0x92, 0xce, 0x06, 0x28, 0xfe, 0x0d, 0x2a, 0xed, 0xfe, 0xdf, 0x39, 0x13, 0xd4, 0x6c, 0xa3, 0xba, 0xbf, + 0xd2, 0x4f, 0x55, 0x4d, 0x62, 0x01, 0x2e, 0x27, 0x69, 0xde, 0xf1, 0x08, 0xab, 0x7f, 0x9a, 0x2c, 0x45, 0xa0, 0x57, + 0x11, 0xed, 0x4a, 0x40, 0x82, 0x76, 0x76, 0x16, 0x2a, 0x02, 0x05, 0xfa, 0xfa, 0x0f, 0xdb, 0x34, 0x8b, 0xe5, 0x6a, + 0xb6, 0x87, 0x89, 0xb2, 0x58, 0x0f, 0x11, 0xe4, 0xcc, 0xd4, 0xc1, 0x7e, 0x4f, 0x33, 0x9a, 0x85, 0x37, 0xa6, 0x04, + 0x97, 0xe2, 0x2a, 0x2a, 0x72, 0xf0, 0x39, 0xc4, 0x17, 0x3e, 0x15, 0x72, 0x83, 0x88, 0xa6, 0x3f, 0xe4, 0x9e, 0x79, + 0x83, 0x85, 0x92, 0x9f, 0x10, 0x7f, 0xc9, 0xda, 0x18, 0xf7, 0x4b, 0xa7, 0xda, 0x2f, 0x15, 0x82, 0xfb, 0xcf, 0xb6, + 0xd8, 0xa8, 0xf2, 0x44, 0x8f, 0x3e, 0xc5, 0xfa, 0x9f, 0x2d, 0xa0, 0x54, 0xf7, 0x6d, 0x70, 0x2a, 0x1e, 0x85, 0xdb, + 0xba, 0xb8, 0x45, 0x68, 0x81, 0x72, 0x54, 0x15, 0xdb, 0x32, 0x22, 0x4e, 0xd8, 0x6d, 0x5d, 0xf4, 0x34, 0x07, 0x3a, + 0x75, 0x58, 0x9a, 0xc8, 0x13, 0xa1, 0xdd, 0x82, 0xee, 0x69, 0x8e, 0x95, 0x78, 0x21, 0x4b, 0x07, 0x59, 0x27, 0xd2, + 0x84, 0xca, 0x5d, 0x5d, 0x75, 0x52, 0x2a, 0x75, 0xc3, 0xeb, 0x54, 0x33, 0xfe, 0x2e, 0xcd, 0x9f, 0x58, 0xf6, 0xeb, + 0xd6, 0x6f, 0xb5, 0xda, 0x1b, 0xab, 0x47, 0x25, 0x6b, 0x8e, 0xb3, 0x09, 0x49, 0xe9, 0x13, 0xb6, 0x9b, 0x49, 0xd7, + 0x3a, 0xf0, 0x24, 0xb8, 0x1c, 0x7a, 0x02, 0x2a, 0x06, 0x4d, 0xbc, 0xdd, 0x05, 0xea, 0x11, 0x78, 0x06, 0xaa, 0x19, + 0x24, 0xd7, 0x01, 0xbf, 0xac, 0xb5, 0x3c, 0x65, 0x84, 0x61, 0xb5, 0xb3, 0x68, 0x39, 0x58, 0x49, 0x78, 0xae, 0x08, + 0x5c, 0xbb, 0x12, 0x78, 0x35, 0x54, 0xef, 0x85, 0x80, 0xe1, 0xfe, 0xa9, 0x50, 0xd9, 0xec, 0x66, 0x38, 0x8f, 0x1a, + 0xa7, 0x07, 0xda, 0xdb, 0xae, 0xf5, 0x50, 0xef, 0xba, 0x9d, 0xdb, 0x4a, 0xf7, 0x7e, 0xed, 0x64, 0xd2, 0x05, 0xb4, + 0x36, 0x9f, 0x43, 0x67, 0x57, 0x5a, 0x37, 0x3d, 0x67, 0x0f, 0xb6, 0x6e, 0x89, 0xce, 0x05, 0xd1, 0xe4, 0xf7, 0x03, + 0xcf, 0xda, 0x76, 0xf4, 0xdb, 0xb4, 0x63, 0x9b, 0x7b, 0xa8, 0x7b, 0x05, 0xb5, 0xde, 0xd0, 0xbc, 0x7f, 0xe6, 0xda, + 0x76, 0x7a, 0xf5, 0xeb, 0xba, 0xc3, 0x75, 0xde, 0x04, 0xc7, 0x4d, 0xd7, 0xb6, 0xda, 0xd9, 0xcf, 0xdd, 0xbd, 0xb5, + 0x88, 0xc2, 0x2c, 0xfb, 0x6b, 0x51, 0xfc, 0x51, 0xe9, 0x3b, 0x02, 0x1d, 0xdd, 0x79, 0x51, 0xa7, 0xcb, 0xfd, 0x07, + 0xc2, 0x78, 0xf2, 0xea, 0x13, 0xa2, 0x5b, 0xdf, 0x67, 0xee, 0x57, 0x80, 0x1b, 0xc1, 0x1d, 0x44, 0x7b, 0xb7, 0xd4, + 0x27, 0xb5, 0xfa, 0x5a, 0xaf, 0x9d, 0xa7, 0xe7, 0x37, 0x9d, 0xdb, 0xef, 0xa1, 0x39, 0xd9, 0x7a, 0x4f, 0x0b, 0x6b, + 0x65, 0xe9, 0xa9, 0x2a, 0xd8, 0x9b, 0xe5, 0xb9, 0x2a, 0x98, 0x3c, 0xf0, 0x9a, 0xfd, 0x82, 0x06, 0x57, 0x3a, 0xd9, + 0x78, 0xcf, 0xd4, 0xc0, 0x2d, 0x0a, 0x4b, 0x87, 0x5f, 0x72, 0x33, 0x79, 0x89, 0xfb, 0x4b, 0x45, 0x2e, 0xf6, 0x9d, + 0x33, 0xba, 0x33, 0xb3, 0xee, 0x55, 0x85, 0xab, 0x05, 0xb9, 0x3a, 0xb0, 0xb5, 0xec, 0xe2, 0x70, 0xc3, 0x22, 0x0a, + 0x10, 0x88, 0xe9, 0x95, 0x5a, 0xfb, 0x13, 0x1a, 0x84, 0x6a, 0x30, 0xf0, 0x0b, 0x0c, 0x56, 0x05, 0x0a, 0x1f, 0x28, + 0x92, 0xbf, 0xf2, 0x04, 0xec, 0xe2, 0x19, 0xa0, 0x5b, 0xb1, 0x59, 0x31, 0x42, 0x84, 0x4c, 0x56, 0xb1, 0x9a, 0xce, + 0x20, 0x9f, 0xfa, 0xe2, 0x1b, 0x5b, 0x75, 0x3e, 0x6f, 0x6b, 0xaa, 0x9c, 0x3b, 0x14, 0xba, 0xbb, 0xa9, 0x3b, 0xb7, + 0x2e, 0xf2, 0xdc, 0x21, 0xe4, 0x4a, 0xc5, 0x4a, 0x4c, 0x43, 0xcd, 0x93, 0x34, 0xa3, 0xfe, 0x62, 0x9f, 0x8a, 0x1a, + 0x85, 0x53, 0xfe, 0x74, 0x0c, 0xaa, 0x70, 0x55, 0x43, 0x1c, 0x4b, 0x55, 0x3c, 0xb2, 0x41, 0xa0, 0x79, 0x75, 0xa7, + 0x92, 0x26, 0x64, 0x72, 0x23, 0x7c, 0x6a, 0x52, 0xca, 0xd3, 0xb4, 0x49, 0x2b, 0x45, 0xea, 0xe0, 0x83, 0x3a, 0xd5, + 0x78, 0x6e, 0xe6, 0xcf, 0x01, 0xcc, 0xb8, 0xba, 0xe1, 0xd7, 0x8a, 0xcb, 0xa8, 0xad, 0xcc, 0xa4, 0xfd, 0xc9, 0xd1, + 0xd8, 0x28, 0x56, 0xe3, 0x46, 0x19, 0x61, 0xa5, 0x34, 0x27, 0xc5, 0x72, 0x3c, 0xff, 0x80, 0xc1, 0x9a, 0x27, 0xb0, + 0x83, 0x89, 0x4a, 0x79, 0x1f, 0x01, 0xf1, 0x75, 0x92, 0xae, 0x12, 0x48, 0x91, 0xfe, 0xa5, 0x4b, 0xee, 0x32, 0x36, + 0x10, 0x63, 0x56, 0xcc, 0x8c, 0xfe, 0x07, 0x77, 0x49, 0x7f, 0x12, 0x02, 0xe0, 0x26, 0x9a, 0x42, 0xa7, 0xce, 0x93, + 0xab, 0x2a, 0x58, 0x5e, 0x79, 0x68, 0xc5, 0x88, 0x07, 0xff, 0xf9, 0x3c, 0x44, 0x10, 0x73, 0x4c, 0xf1, 0xf4, 0x0b, + 0xa3, 0xff, 0x08, 0xae, 0x31, 0x82, 0xd0, 0xdd, 0x3b, 0x87, 0x21, 0xdc, 0xec, 0x41, 0x06, 0xf5, 0x87, 0x3a, 0x24, + 0x6a, 0xf8, 0xd7, 0xdc, 0x83, 0xfe, 0xaf, 0x33, 0x61, 0xa9, 0xfd, 0xf4, 0x74, 0x00, 0x15, 0xbc, 0xaf, 0x78, 0x1b, + 0x11, 0xdf, 0x27, 0x7e, 0x1a, 0x0f, 0xb6, 0x4f, 0xb7, 0x60, 0xad, 0xfb, 0x50, 0x19, 0xeb, 0x2a, 0x61, 0x03, 0x01, + 0x5f, 0xa3, 0xa8, 0x3d, 0xaf, 0xdd, 0xee, 0xc1, 0x7f, 0xfa, 0x57, 0x21, 0x03, 0x26, 0x4e, 0xdf, 0x67, 0x4e, 0xd6, + 0xe8, 0x2a, 0x93, 0xe9, 0x43, 0x27, 0x7d, 0xab, 0xd3, 0x7d, 0x27, 0xfc, 0x23, 0x67, 0x16, 0x1f, 0x6e, 0xe9, 0x2b, + 0x4d, 0x8a, 0x3b, 0x60, 0x65, 0xf3, 0xa8, 0x20, 0xd4, 0xb9, 0x88, 0xbe, 0x32, 0xe5, 0x5b, 0x42, 0xcd, 0xa1, 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, 0x53, 0x91, 0x5e, 0x18, 0xaf, 0x42, 0x29, + 0x28, 0x0c, 0xe8, 0x0a, 0xa4, 0x80, 0x47, 0xed, 0x89, 0xce, 0xc2, 0x2e, 0xe1, 0x1e, 0xdd, 0x04, 0x8c, 0xf5, 0xf9, + 0xaf, 0xb9, 0x97, 0x33, 0xe1, 0x0e, 0x2f, 0x06, 0xa8, 0x4d, 0xbd, 0xba, 0xfb, 0xb8, 0x56, 0xe7, 0x70, 0x08, 0x0e, + 0x56, 0x83, 0x08, 0x4e, 0xe7, 0x73, 0x47, 0xb3, 0x2c, 0x40, 0xe5, 0x64, 0x95, 0x91, 0x37, 0x4f, 0x16, 0xbd, 0xba, + 0xef, 0x2d, 0xd3, 0xb2, 0xaa, 0x83, 0x8c, 0x65, 0x61, 0x05, 0xb8, 0x3a, 0xb4, 0x7e, 0x10, 0x2e, 0x0b, 0xe7, 0x0f, + 0x84, 0x20, 0x76, 0xaf, 0xb6, 0x25, 0xd7, 0x47, 0xf5, 0xd3, 0x67, 0x6c, 0xc3, 0x25, 0xea, 0xa4, 0x33, 0x11, 0x80, + 0xd8, 0x53, 0xb3, 0x8a, 0x6e, 0x80, 0xa4, 0x4e, 0xb3, 0x8a, 0x6e, 0xa8, 0xd9, 0xc6, 0x38, 0x00, 0xca, 0x58, 0xc0, + 0xbe, 0x9b, 0x8e, 0x83, 0xf5, 0xd3, 0x58, 0x5e, 0x87, 0x56, 0x4f, 0xb7, 0xca, 0x67, 0x50, 0xb7, 0xda, 0x18, 0x13, + 0xdb, 0xcd, 0x97, 0x73, 0xfd, 0x6e, 0xb0, 0xf4, 0xed, 0xa0, 0x39, 0xa7, 0xec, 0x95, 0x2e, 0x7b, 0x6d, 0x97, 0x4d, + 0x3d, 0x77, 0x52, 0xb4, 0x1a, 0x03, 0x7a, 0x03, 0x0b, 0xd6, 0xe7, 0x22, 0xcd, 0x56, 0xa5, 0x2a, 0x01, 0x2f, 0x8c, + 0x35, 0x5b, 0xf9, 0x8d, 0xcc, 0x90, 0x84, 0x79, 0x9c, 0x89, 0xb7, 0x74, 0xaf, 0x85, 0xc9, 0x71, 0x2a, 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, 0x89, 0x18, 0x67, 0xbe, 0x28, 0x99, 0x01, 0xdd, 0xf2, 0xeb, 0x62, 0xd3, 0x4a, + 0x91, 0x11, 0xe6, 0xcd, 0x69, 0x7b, 0x5d, 0x1f, 0x02, 0xb9, 0x5a, 0x0e, 0x28, 0x1a, 0x07, 0x85, 0x0e, 0x97, 0x2a, + 0x01, 0xf6, 0x45, 0xe2, 0x67, 0x84, 0x2d, 0xed, 0x81, 0xdc, 0x1e, 0x9d, 0x09, 0x73, 0xc9, 0x49, 0x59, 0x76, 0x29, + 0xcd, 0xe0, 0x72, 0xe2, 0x4a, 0x70, 0x91, 0xde, 0xae, 0xa7, 0x49, 0x4b, 0xdb, 0xc7, 0x86, 0x73, 0x34, 0xb4, 0x0d, + 0xba, 0x63, 0x7f, 0x68, 0x2e, 0x16, 0xb1, 0x75, 0xb1, 0x18, 0x76, 0x66, 0x3f, 0x59, 0x2c, 0x40, 0x0e, 0x00, 0x47, + 0xdd, 0x96, 0x8f, 0xd9, 0x12, 0x38, 0xad, 0xa6, 0xd9, 0xd4, 0xdb, 0xf2, 0xfc, 0xa9, 0xea, 0xe9, 0x25, 0xaf, 0x9e, + 0x0a, 0x33, 0x16, 0x5b, 0x5e, 0x3d, 0xb5, 0x8e, 0x9c, 0xfc, 0xa9, 0x50, 0xa2, 0x75, 0x01, 0xcd, 0xc0, 0x6b, 0x0a, + 0x18, 0xb1, 0x64, 0x32, 0xa5, 0x8a, 0x3c, 0xee, 0x4d, 0xb7, 0x6a, 0xf0, 0x82, 0xc2, 0x21, 0x90, 0xd2, 0xe9, 0x57, + 0xcf, 0x98, 0x7e, 0xef, 0xea, 0x59, 0x87, 0xac, 0x6d, 0x98, 0x2e, 0xb7, 0xc3, 0x64, 0x50, 0xfa, 0x4f, 0xcd, 0xc4, + 0xb8, 0xb2, 0x26, 0x09, 0x20, 0xfe, 0x8d, 0xfd, 0x0e, 0x29, 0xdc, 0xbc, 0xbf, 0x1c, 0xc6, 0x8f, 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, 0x72, 0x67, 0x48, 0x06, 0x65, 0xd7, 0x0d, + 0x21, 0x1e, 0x94, 0x4d, 0xd3, 0x5e, 0x6f, 0x07, 0x67, 0x1e, 0xab, 0xeb, 0xb4, 0xb3, 0xb8, 0x5a, 0x64, 0x90, 0x56, + 0x1f, 0xb2, 0xd3, 0xcc, 0x3e, 0x3b, 0x59, 0x2a, 0xdd, 0xef, 0x43, 0x44, 0xdc, 0x49, 0xd6, 0xf6, 0xdb, 0x2d, 0xb8, + 0x86, 0x93, 0x41, 0xe8, 0xca, 0xde, 0x2e, 0xa3, 0x8d, 0x0b, 0x71, 0xda, 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, 0x5d, 0xaf, 0x49, 0xab, 0xd7, 0x32, + 0x0a, 0xb1, 0xa5, 0xed, 0xd5, 0xa7, 0xa3, 0x7c, 0xdc, 0x9c, 0x51, 0x5c, 0xc8, 0x51, 0x7e, 0xf4, 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, 0x55, 0xc3, 0x21, 0x13, 0xc3, 0xa1, 0x84, 0x4c, 0x7d, 0xec, 0xde, 0xd0, 0xb4, 0xcf, 0x4f, 0x5b, + 0x3f, 0x62, 0xa9, 0x71, 0x14, 0x1b, 0xde, 0xf9, 0x3b, 0x8f, 0xad, 0x71, 0x25, 0x5f, 0x06, 0xb3, 0x5d, 0x41, 0xb5, + 0x35, 0xde, 0xb0, 0x57, 0xf1, 0x1f, 0x72, 0xa9, 0xe4, 0x6f, 0x7f, 0x86, 0x6b, 0x78, 0x6b, 0x4b, 0x07, 0x4d, 0x35, + 0xab, 0x98, 0xb9, 0x17, 0x9c, 0x7e, 0xdc, 0xbd, 0x22, 0x18, 0xfc, 0x9e, 0x8e, 0x82, 0x5c, 0x2c, 0xd5, 0x1a, 0x50, + 0x90, 0x4e, 0xec, 0x98, 0xca, 0x02, 0xc3, 0x00, 0xde, 0x90, 0x01, 0xf2, 0x98, 0xc2, 0xdd, 0x50, 0xe1, 0x85, 0xbf, + 0xe4, 0x64, 0x97, 0xc0, 0xb6, 0x66, 0x7c, 0xcc, 0x70, 0x07, 0x21, 0xff, 0x08, 0xb6, 0x62, 0x6b, 0x76, 0xc7, 0x16, + 0x0c, 0xc9, 0xc6, 0x71, 0x18, 0x63, 0x3e, 0x9e, 0xc4, 0x37, 0x62, 0x12, 0x0f, 0x78, 0x84, 0x8e, 0x11, 0x1b, 0x5e, + 0xcf, 0x62, 0x39, 0x80, 0x6c, 0xc5, 0x95, 0x0e, 0x08, 0xa1, 0xb1, 0xa1, 0x25, 0xaf, 0x0b, 0x83, 0x8b, 0x1d, 0xfb, + 0x2c, 0x47, 0x91, 0x8c, 0x43, 0xb0, 0x68, 0x55, 0x03, 0x0b, 0x13, 0xbb, 0xe3, 0xc5, 0x6c, 0x3d, 0xc7, 0x7f, 0x8e, + 0x47, 0x04, 0xc0, 0x0e, 0x0e, 0x0d, 0x5b, 0x45, 0x88, 0xf4, 0xb6, 0xe0, 0x2b, 0xcb, 0xd3, 0x85, 0xdd, 0xf3, 0xb7, + 0x7c, 0xcc, 0x2e, 0x7f, 0xf4, 0x20, 0x72, 0xf6, 0xf2, 0x23, 0xa0, 0x21, 0xde, 0xf3, 0xbb, 0xd4, 0xcb, 0xd9, 0x1d, + 0x51, 0x10, 0xde, 0x81, 0x33, 0xd0, 0x3d, 0x44, 0xc0, 0xbe, 0xe5, 0x0b, 0x8c, 0x15, 0xbb, 0x48, 0x97, 0x1e, 0x66, + 0x84, 0xda, 0xd3, 0xf9, 0xb2, 0x51, 0x93, 0x70, 0x7b, 0xb3, 0x9c, 0x0c, 0x06, 0x5b, 0x7f, 0xcf, 0x37, 0xc0, 0x07, + 0x73, 0xf9, 0xa3, 0xb7, 0xa7, 0x72, 0xe1, 0x3f, 0xaf, 0xb3, 0xe4, 0xbd, 0xcf, 0xde, 0x0e, 0xf8, 0x02, 0xf0, 0x96, + 0xd0, 0x81, 0xeb, 0xde, 0x67, 0x12, 0xaf, 0xed, 0xad, 0xbe, 0x46, 0x20, 0x91, 0x2f, 0x00, 0x23, 0x26, 0xe6, 0xf7, + 0x5b, 0x88, 0xc0, 0x48, 0xc0, 0xb7, 0x55, 0x7b, 0xc4, 0xef, 0xb8, 0x01, 0xfc, 0xca, 0x7c, 0xf6, 0xc0, 0x43, 0xfd, + 0x33, 0xf1, 0xd9, 0x2d, 0x7f, 0xcf, 0x9f, 0x7b, 0x52, 0x92, 0x2e, 0x67, 0xef, 0xe7, 0x70, 0x3d, 0x94, 0xf2, 0x74, + 0x48, 0x3f, 0x1b, 0x83, 0x01, 0x84, 0x42, 0xe6, 0xad, 0x07, 0xac, 0x49, 0x21, 0xfe, 0x05, 0x7c, 0x3b, 0x4a, 0xd8, + 0xbc, 0xf5, 0x76, 0xbe, 0x96, 0x37, 0x6f, 0xbd, 0x07, 0x9f, 0xa2, 0x00, 0xab, 0xa0, 0x94, 0x05, 0x56, 0x41, 0xd8, + 0x68, 0x23, 0x8c, 0x81, 0xab, 0x77, 0x8d, 0xa1, 0xae, 0xe7, 0x88, 0x6d, 0x2b, 0x7d, 0x17, 0xbe, 0x83, 0x0c, 0xf8, + 0xe0, 0x75, 0x51, 0x12, 0x7d, 0x4e, 0x4d, 0x91, 0xb4, 0xee, 0xb9, 0xdf, 0x5a, 0x77, 0xb4, 0xa6, 0xd4, 0x47, 0x6e, + 0xc6, 0xc7, 0x63, 0xfd, 0x5c, 0x68, 0x91, 0x60, 0x0a, 0x1a, 0xd7, 0xa0, 0x2d, 0x40, 0xd0, 0xe7, 0x01, 0xb2, 0x96, + 0x14, 0x0b, 0xbe, 0xfd, 0x15, 0x62, 0xf0, 0xca, 0xf4, 0xce, 0xe5, 0x2a, 0x23, 0x61, 0x7b, 0xe1, 0xd7, 0xc3, 0xda, + 0x9f, 0x38, 0xb5, 0xb0, 0xb4, 0x9a, 0x83, 0xfa, 0xa9, 0x2d, 0xc7, 0xa9, 0xaa, 0xfd, 0x4b, 0x92, 0x54, 0xbb, 0x4a, + 0xcb, 0xe9, 0xbd, 0x7d, 0xd3, 0x65, 0x82, 0x8d, 0xfd, 0x80, 0xaa, 0x23, 0xab, 0x61, 0xf7, 0x85, 0xfa, 0xa2, 0xa7, + 0x64, 0x42, 0xf3, 0x51, 0x45, 0xf3, 0xec, 0x7e, 0xb3, 0xa3, 0xfe, 0xd3, 0xeb, 0xa1, 0x08, 0x90, 0xac, 0xd2, 0x62, + 0x29, 0x72, 0x36, 0xf6, 0xd3, 0x61, 0x92, 0xa9, 0xf0, 0x82, 0x74, 0x74, 0xf7, 0x1b, 0xf7, 0xb7, 0xdc, 0x40, 0xd6, + 0x68, 0xd5, 0x06, 0x63, 0xa5, 0x68, 0x19, 0xac, 0x6f, 0xc6, 0xfd, 0xbe, 0xb8, 0x19, 0x4f, 0x45, 0x50, 0x03, 0x71, + 0x91, 0x78, 0x3e, 0x9e, 0xd6, 0xc4, 0x92, 0xda, 0x15, 0x18, 0xa3, 0xc7, 0x55, 0x51, 0xfb, 0xd4, 0xcf, 0x21, 0x14, + 0xa9, 0xd6, 0xcc, 0xb1, 0xc6, 0x8d, 0x11, 0x71, 0x87, 0x95, 0x6b, 0xa7, 0xf6, 0x3a, 0x00, 0xcb, 0xab, 0x71, 0x41, + 0xd8, 0x26, 0xa7, 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, 0xce, 0x5c, 0xc0, 0x2a, + 0xc3, 0x53, 0xab, 0x6f, 0x62, 0x70, 0x22, 0xe4, 0xad, 0x74, 0xbc, 0xf6, 0x23, 0xee, 0x57, 0xf7, 0x75, 0xaf, 0x04, + 0x3f, 0x09, 0x79, 0xfd, 0x96, 0x77, 0x00, 0x58, 0xf1, 0x21, 0x2f, 0xa6, 0x85, 0xa3, 0x75, 0x19, 0x94, 0x01, 0x22, + 0x34, 0x03, 0xa0, 0x93, 0xab, 0x83, 0x28, 0x0d, 0x5c, 0x71, 0x87, 0x08, 0x3f, 0x8d, 0x9e, 0x56, 0xcf, 0xc3, 0xa7, + 0xf9, 0x34, 0xbc, 0xaa, 0x82, 0xe8, 0x2a, 0x0f, 0xa2, 0xa7, 0xf9, 0x4d, 0xf8, 0xb4, 0x9a, 0x46, 0x57, 0x55, 0x10, + 0x5e, 0xe5, 0x8d, 0x7d, 0xd7, 0xee, 0xee, 0x09, 0x79, 0xdb, 0xd5, 0x1f, 0xb9, 0x54, 0xf6, 0x94, 0xe9, 0xe5, 0x65, + 0xad, 0x57, 0x6a, 0xb7, 0xb9, 0x5e, 0xa3, 0x66, 0xea, 0xa3, 0xec, 0x2f, 0xb6, 0xb1, 0xf0, 0x64, 0x0e, 0xa1, 0xcf, + 0x48, 0x8b, 0xb9, 0xc7, 0xb9, 0xde, 0x1c, 0x48, 0x61, 0x60, 0xc4, 0xa4, 0x92, 0x91, 0xd3, 0x0b, 0x5c, 0x84, 0x72, + 0xc4, 0xb0, 0x96, 0xae, 0xf6, 0x59, 0x97, 0xde, 0x40, 0x5d, 0x53, 0xec, 0x6b, 0xc8, 0xc0, 0x8b, 0xa6, 0xd7, 0xc1, + 0x18, 0x90, 0x23, 0xf0, 0x8e, 0xcf, 0x96, 0x70, 0x60, 0x6e, 0x00, 0xfa, 0xe6, 0x51, 0x5f, 0x97, 0x15, 0xdf, 0xa8, + 0xbe, 0x99, 0x6e, 0x46, 0x4a, 0xf9, 0xb1, 0xe6, 0xab, 0xab, 0x67, 0xec, 0x8e, 0x6b, 0x54, 0x94, 0x5f, 0xf4, 0x62, + 0xbd, 0x07, 0xae, 0xba, 0x5f, 0xe0, 0x36, 0x8b, 0xc7, 0xae, 0x3c, 0x60, 0xd9, 0x8e, 0x3d, 0xb0, 0x5b, 0xf6, 0x9e, + 0x3d, 0x61, 0x6f, 0xd8, 0x17, 0xf6, 0x13, 0xaa, 0x36, 0x94, 0x90, 0xe7, 0x2f, 0xf8, 0x9d, 0x34, 0x3d, 0x4a, 0x54, + 0xb2, 0x07, 0xdb, 0x4c, 0x33, 0xdc, 0xb2, 0xf7, 0x7c, 0x31, 0x5c, 0xb3, 0x37, 0x90, 0x0d, 0x65, 0xe2, 0xc1, 0x9a, + 0xfd, 0xc4, 0x15, 0x88, 0x99, 0x3e, 0x0b, 0x4b, 0x4b, 0x54, 0x34, 0x65, 0xa2, 0x0c, 0xfd, 0x86, 0xe3, 0x8b, 0xec, + 0x27, 0x2c, 0x42, 0x7e, 0x66, 0xb8, 0x66, 0x0f, 0x7c, 0x31, 0x58, 0xb3, 0xf7, 0xda, 0x40, 0x34, 0xd8, 0xba, 0xa5, + 0x11, 0x92, 0x95, 0x2e, 0x4b, 0x4a, 0xd3, 0x3b, 0xfb, 0x1a, 0xb8, 0x65, 0xb7, 0x58, 0xbb, 0x27, 0x58, 0x34, 0x0a, + 0xfc, 0x83, 0x35, 0xfb, 0xc2, 0x25, 0x80, 0x9a, 0x5b, 0x9e, 0xf4, 0x0a, 0xd5, 0x05, 0xd2, 0xfd, 0xe0, 0x09, 0xa7, + 0x17, 0xd9, 0x17, 0x2c, 0x83, 0xbe, 0x32, 0x5c, 0xb3, 0x1d, 0xd6, 0xee, 0xd6, 0x58, 0xb6, 0xac, 0xea, 0x49, 0x44, + 0x60, 0x14, 0x54, 0x4a, 0xcb, 0xbf, 0x11, 0xcb, 0xa6, 0x6e, 0x1a, 0xd4, 0x86, 0xfe, 0x7c, 0x30, 0xfa, 0x0f, 0x5f, + 0xbf, 0xfb, 0xc1, 0x2b, 0xf5, 0xb5, 0xf7, 0x17, 0xc7, 0xb5, 0xb2, 0x44, 0xd7, 0xca, 0x5f, 0x79, 0x39, 0xfb, 0x65, + 0x3e, 0xd1, 0xb5, 0xa4, 0x1d, 0x86, 0x7c, 0x4d, 0x67, 0xbf, 0x74, 0x38, 0x5b, 0xfe, 0xea, 0xfb, 0x8d, 0xe9, 0x62, + 0xf5, 0x59, 0xdd, 0xbb, 0x0f, 0x83, 0x6d, 0xe3, 0xd4, 0x7b, 0x7f, 0xbe, 0xde, 0xd8, 0xcc, 0x5a, 0x7b, 0x66, 0xfe, + 0x0f, 0x57, 0x7a, 0x87, 0x43, 0x77, 0xcb, 0x77, 0xc3, 0xad, 0x3d, 0x0a, 0xf2, 0xfb, 0x52, 0x69, 0x9c, 0xd5, 0xfc, + 0x85, 0x97, 0x77, 0x49, 0xb1, 0x80, 0x68, 0xf4, 0xc9, 0x48, 0x42, 0xd7, 0xcc, 0xc4, 0x33, 0xc4, 0x57, 0x19, 0x20, + 0x73, 0x81, 0x68, 0x76, 0xcf, 0xc7, 0x93, 0xfb, 0x9b, 0x78, 0x72, 0x3f, 0xe0, 0x9f, 0x4c, 0x0b, 0xda, 0x8b, 0xed, + 0xde, 0x67, 0xbf, 0xf2, 0xc2, 0x5e, 0x8e, 0xbf, 0xf8, 0xec, 0x9d, 0x70, 0x57, 0xe8, 0x2f, 0x3e, 0xfb, 0x22, 0xf8, + 0xaf, 0x23, 0x4d, 0x94, 0xc1, 0xbe, 0xd4, 0xfc, 0xd7, 0x11, 0x32, 0x7e, 0xb0, 0xcf, 0x82, 0xbf, 0x03, 0xdf, 0xef, + 0x2a, 0x41, 0xab, 0xf8, 0xe7, 0x5a, 0xfd, 0x7c, 0x2f, 0xe3, 0x72, 0xe0, 0x4d, 0x68, 0x05, 0xbd, 0x79, 0x57, 0xcb, + 0x9f, 0xc4, 0xc3, 0x91, 0xaa, 0xa7, 0x86, 0x7f, 0x16, 0x8b, 0x59, 0xd4, 0x27, 0xe9, 0x54, 0xde, 0xe4, 0x2d, 0xcf, + 0xa4, 0x75, 0xf9, 0x1e, 0x42, 0x81, 0xdf, 0xda, 0x10, 0x05, 0x7b, 0x8e, 0x1b, 0xc1, 0x5b, 0x06, 0xf0, 0x91, 0xd9, + 0x74, 0xc7, 0x6f, 0xf9, 0x13, 0xfe, 0x85, 0xef, 0x83, 0x07, 0xfe, 0x9e, 0xbf, 0xe1, 0x3f, 0xf1, 0x3d, 0x5b, 0x4a, + 0xb4, 0xd3, 0x7a, 0x77, 0x1d, 0xec, 0x58, 0xbd, 0xbf, 0x0e, 0x1e, 0x58, 0xbd, 0x7b, 0x16, 0xdc, 0xb2, 0x7a, 0xff, + 0x2c, 0x78, 0xcf, 0x76, 0xd7, 0xc1, 0x13, 0xb6, 0xbf, 0x0e, 0xde, 0xb0, 0xdd, 0xb3, 0xe0, 0x0b, 0xdb, 0x3f, 0x0b, + 0x7e, 0x92, 0x18, 0x0f, 0x5f, 0x84, 0xe4, 0x38, 0xf9, 0x52, 0x33, 0xc3, 0xa7, 0x1b, 0x7c, 0x16, 0xd6, 0x2f, 0xaa, + 0x63, 0xf0, 0xb9, 0x66, 0xba, 0xc5, 0x81, 0x10, 0x4c, 0xb7, 0x37, 0xb8, 0xa3, 0x27, 0xa6, 0x55, 0x41, 0x2a, 0x58, + 0x57, 0x3b, 0x83, 0x45, 0xdd, 0xb4, 0xce, 0x64, 0xc7, 0x2f, 0x31, 0xee, 0xf0, 0x4b, 0x5c, 0xb0, 0x65, 0xd3, 0xe9, + 0xa4, 0x73, 0xfe, 0x24, 0xd0, 0x9b, 0xbf, 0xde, 0xf5, 0x73, 0xe9, 0x3b, 0x53, 0x34, 0x5c, 0x6b, 0x8d, 0x5b, 0x3b, + 0x7d, 0x68, 0xed, 0xf4, 0x4c, 0xaa, 0xd0, 0x22, 0x16, 0x95, 0x45, 0x55, 0x21, 0x93, 0x78, 0x90, 0x69, 0x7d, 0x5a, + 0xc2, 0x48, 0x91, 0x09, 0x68, 0xf4, 0x05, 0x1d, 0x03, 0x15, 0x59, 0x14, 0xd8, 0x92, 0x6f, 0x07, 0x09, 0xdb, 0xf0, + 0x78, 0x3a, 0x4c, 0x82, 0x25, 0x5b, 0xf1, 0x61, 0xb7, 0x40, 0xb0, 0x56, 0x01, 0x4c, 0xfa, 0xe2, 0xd4, 0xde, 0xd7, + 0x79, 0x6f, 0x9d, 0xc6, 0x71, 0x26, 0x50, 0xd9, 0x96, 0xeb, 0x0d, 0x7e, 0xe7, 0xec, 0xe7, 0x1b, 0xb5, 0xbf, 0x83, + 0xa4, 0xf0, 0x2b, 0x30, 0xec, 0x10, 0xe1, 0x1d, 0x54, 0x18, 0x79, 0x96, 0xcc, 0xa2, 0xcf, 0xed, 0x2d, 0x7d, 0x67, + 0xb6, 0xe9, 0x7f, 0xb7, 0x08, 0xda, 0xc7, 0x65, 0xe7, 0x7f, 0x32, 0xaf, 0xfe, 0xd6, 0xf1, 0xea, 0xd6, 0x9f, 0x3c, + 0xf0, 0x4f, 0x18, 0x96, 0x80, 0x89, 0x6c, 0xcf, 0x3f, 0x8d, 0x76, 0x8d, 0x53, 0x9e, 0xdc, 0xc7, 0xff, 0xaf, 0x14, + 0x68, 0xef, 0xe4, 0xb9, 0xbd, 0x23, 0xee, 0x78, 0xc7, 0x3e, 0xbe, 0xb4, 0x36, 0x44, 0x03, 0x4d, 0xf2, 0x89, 0xbb, + 0xd1, 0xd0, 0xb0, 0x21, 0xfe, 0xc2, 0xf3, 0xd9, 0xa7, 0xf9, 0x64, 0xc7, 0x4f, 0xb7, 0xc3, 0x4f, 0x1d, 0xdb, 0xe1, + 0x2f, 0xfe, 0x60, 0xd9, 0x7c, 0xad, 0x57, 0x3b, 0xb7, 0x71, 0xa7, 0xd2, 0x7b, 0x7e, 0xba, 0x89, 0x0f, 0xff, 0xed, + 0x4a, 0xef, 0xbf, 0xb9, 0xd2, 0x76, 0x95, 0xbb, 0x3b, 0xdf, 0x74, 0x7c, 0x23, 0x6b, 0x8d, 0x71, 0x66, 0x46, 0xb3, + 0xf8, 0x13, 0xcd, 0xd2, 0x20, 0xb2, 0x14, 0x8a, 0x3f, 0x99, 0x69, 0xa7, 0xee, 0x54, 0x59, 0xdd, 0x2d, 0xdf, 0xe2, + 0x1e, 0x7f, 0xc7, 0xc7, 0x6c, 0x61, 0x3c, 0x35, 0xef, 0x6e, 0x16, 0x93, 0xc1, 0xe0, 0xce, 0x3f, 0xdc, 0xf3, 0x70, + 0x76, 0x37, 0x67, 0x6f, 0xf9, 0x3d, 0x2d, 0xa6, 0x89, 0x6a, 0x7a, 0xf1, 0x98, 0xe0, 0x75, 0xe7, 0xfb, 0x13, 0x8b, + 0xff, 0xd5, 0xbe, 0x68, 0xde, 0xf9, 0x03, 0x69, 0x8d, 0x96, 0xbb, 0xfa, 0xfb, 0xc7, 0x15, 0x13, 0x77, 0x20, 0x5e, + 0xbc, 0xb7, 0x35, 0x0d, 0x6f, 0xf8, 0x47, 0xef, 0xad, 0x3f, 0x7d, 0xab, 0x63, 0x6e, 0x26, 0xea, 0x48, 0x7a, 0x73, + 0xf5, 0x8c, 0xfd, 0xca, 0x3f, 0xc9, 0xe3, 0xe4, 0x9d, 0x90, 0x93, 0xf6, 0x16, 0xb9, 0x9b, 0xe8, 0x94, 0xf8, 0xe2, + 0x26, 0x12, 0x16, 0x04, 0xc2, 0x70, 0xd4, 0xfc, 0x61, 0x52, 0x4e, 0xbd, 0x3d, 0x70, 0xbb, 0x72, 0x5b, 0xff, 0x7c, + 0xc7, 0x39, 0x5f, 0x0c, 0xaf, 0xa7, 0x5f, 0xba, 0x5d, 0x7a, 0x54, 0x34, 0x9b, 0x0a, 0x74, 0xbb, 0xc3, 0xd8, 0xab, + 0xb3, 0x99, 0x65, 0x2e, 0xf9, 0xd2, 0x97, 0xda, 0xcc, 0x3c, 0xa6, 0xf7, 0x9b, 0x69, 0x86, 0x44, 0xbe, 0x40, 0xc8, + 0x74, 0x3c, 0xae, 0x2e, 0xb1, 0x3c, 0x3e, 0x7c, 0xf3, 0xf4, 0xc9, 0xe0, 0x09, 0x46, 0x6e, 0x59, 0xd1, 0x20, 0x5f, + 0xf8, 0x30, 0xab, 0x5b, 0xb7, 0x8d, 0xab, 0x67, 0xc3, 0x5f, 0x20, 0x6f, 0xd0, 0xf5, 0xd0, 0x14, 0xd1, 0x2a, 0xbf, + 0xa3, 0xe8, 0x33, 0x25, 0x07, 0x1d, 0x4f, 0xa0, 0x76, 0x48, 0x81, 0xfb, 0xe5, 0x29, 0x07, 0xfd, 0x06, 0x96, 0xda, + 0xef, 0x5f, 0x7e, 0x22, 0x1e, 0x69, 0x18, 0xef, 0x1f, 0xc2, 0xe8, 0x8f, 0xb8, 0x2c, 0x36, 0x70, 0xba, 0x0e, 0xe0, + 0x73, 0x4f, 0xf5, 0xed, 0x6b, 0xe5, 0xfb, 0x7e, 0xe0, 0xed, 0xf8, 0x2d, 0xfb, 0xc2, 0xbd, 0xeb, 0xe1, 0x1b, 0xff, + 0xe9, 0x13, 0x10, 0x9d, 0x60, 0x5c, 0x3e, 0x63, 0x24, 0x6c, 0x47, 0x31, 0x6a, 0x15, 0x7e, 0xae, 0x21, 0x44, 0xeb, + 0x13, 0x32, 0x76, 0x41, 0xfa, 0x07, 0x05, 0xe8, 0x27, 0x04, 0x56, 0x93, 0xd4, 0x28, 0x30, 0x89, 0xef, 0x6a, 0x48, + 0x20, 0x05, 0x0b, 0x84, 0xde, 0x40, 0xf1, 0xa9, 0xe0, 0x5f, 0x86, 0x9f, 0x49, 0xf2, 0x5b, 0xd4, 0x7c, 0x0c, 0x7f, + 0xc3, 0xd0, 0x4c, 0xaa, 0x87, 0xb4, 0x8e, 0x12, 0xef, 0x27, 0xff, 0x10, 0x85, 0x95, 0x50, 0xc7, 0x42, 0x90, 0x8a, + 0x21, 0x17, 0xe2, 0xea, 0xd9, 0xe4, 0xae, 0x14, 0xe1, 0x1f, 0x13, 0x7c, 0x26, 0x17, 0x9a, 0x7c, 0x46, 0x4f, 0x1a, + 0xf9, 0xfe, 0x83, 0x7c, 0x5f, 0x76, 0x6a, 0xb0, 0xa8, 0x87, 0xfc, 0xae, 0x76, 0xdf, 0x97, 0x53, 0x82, 0x1e, 0xd9, + 0x0f, 0x68, 0x0a, 0x06, 0x6a, 0x02, 0x52, 0x86, 0xe0, 0x0e, 0xae, 0xfa, 0x9e, 0x2a, 0xc8, 0x97, 0xdf, 0xfb, 0x2c, + 0x64, 0xb8, 0xca, 0x82, 0x90, 0xe4, 0x52, 0x21, 0x85, 0x8d, 0xbb, 0x7a, 0xf0, 0x59, 0x63, 0x92, 0x48, 0xc8, 0x29, + 0x01, 0x49, 0xd2, 0xde, 0x40, 0x92, 0x88, 0xe9, 0x3f, 0x5c, 0x27, 0x4d, 0xb3, 0x96, 0xd2, 0x0d, 0x71, 0xaa, 0xbe, + 0x45, 0x9a, 0xb3, 0xe0, 0x3d, 0x83, 0xa5, 0x23, 0xc5, 0x8a, 0x2f, 0xc6, 0x60, 0xac, 0x83, 0x85, 0x56, 0xb2, 0xb8, + 0x5f, 0x25, 0x61, 0x1a, 0x89, 0x2a, 0xef, 0x84, 0xfc, 0xf9, 0x4f, 0x25, 0xfe, 0xe8, 0x2d, 0x0d, 0x44, 0x20, 0xf8, + 0x01, 0x5a, 0x0f, 0x58, 0xe3, 0xc1, 0x4f, 0xac, 0x2e, 0xc3, 0xbc, 0xca, 0xa8, 0xbc, 0xd9, 0x9e, 0xed, 0xe6, 0x4c, + 0x55, 0x2d, 0xf8, 0x2c, 0x0c, 0x2d, 0xda, 0xc5, 0xba, 0x39, 0xbb, 0xcd, 0x1b, 0x7c, 0x67, 0x92, 0x44, 0x6a, 0x29, + 0x89, 0xb4, 0xd5, 0xf5, 0xe9, 0xd2, 0xeb, 0x16, 0x15, 0x34, 0x46, 0x80, 0x5e, 0x92, 0xee, 0x2a, 0x9f, 0x50, 0xbc, + 0xb2, 0x1a, 0x56, 0xc3, 0x4b, 0x87, 0x22, 0x8c, 0xb5, 0x37, 0xe7, 0xf2, 0xec, 0x0e, 0xac, 0x47, 0x68, 0xed, 0xca, + 0xd5, 0x21, 0x6c, 0x3f, 0xd1, 0x7b, 0x4e, 0xae, 0xfe, 0x06, 0x54, 0x81, 0x73, 0x47, 0x43, 0x7d, 0xd2, 0x4e, 0x21, + 0xdb, 0x79, 0xb0, 0x24, 0xa8, 0x4a, 0xc9, 0x4d, 0xb9, 0x16, 0xa5, 0x94, 0x29, 0x5f, 0xcb, 0x6c, 0x65, 0xf7, 0xc9, + 0x00, 0xe2, 0xd9, 0xa0, 0x40, 0x72, 0x51, 0x5b, 0xcd, 0x41, 0xfa, 0x68, 0x96, 0x38, 0xd6, 0x0e, 0x0a, 0x2f, 0xcb, + 0xc1, 0xcc, 0x65, 0x2e, 0x97, 0x83, 0x82, 0x55, 0x7a, 0xab, 0x99, 0x66, 0xaa, 0x2f, 0x2a, 0x7b, 0x9b, 0xf1, 0x32, + 0xfd, 0x37, 0x4b, 0x06, 0x3c, 0xba, 0x7a, 0xe6, 0x07, 0x90, 0x26, 0x79, 0x1d, 0x20, 0x09, 0x36, 0x07, 0xbb, 0xd8, + 0x61, 0xd8, 0x2a, 0x56, 0xf6, 0xe4, 0xf9, 0x72, 0x87, 0xa6, 0x5c, 0xc2, 0x48, 0x4e, 0xcc, 0xa5, 0xd4, 0xf7, 0x25, + 0xd5, 0x0d, 0x05, 0x27, 0x9b, 0x26, 0xa0, 0x14, 0xd0, 0x6e, 0xc1, 0x7f, 0xe1, 0x53, 0x43, 0xa7, 0x05, 0x58, 0x6a, + 0xbb, 0x01, 0xff, 0x85, 0x7e, 0xb1, 0x7d, 0x44, 0xfd, 0xc0, 0x3c, 0x38, 0x98, 0xb5, 0x95, 0x31, 0x20, 0x22, 0x71, + 0x05, 0x79, 0x24, 0xf8, 0x41, 0xb1, 0xa7, 0xcb, 0xc4, 0x81, 0x33, 0xc5, 0xc5, 0x52, 0x6a, 0x33, 0xf3, 0xda, 0x6f, + 0xa9, 0x89, 0x37, 0x51, 0x12, 0x15, 0xb6, 0x43, 0x1a, 0xbd, 0xa4, 0x8c, 0xa9, 0x82, 0x0d, 0xd1, 0x7d, 0xdd, 0x04, + 0x53, 0xe0, 0x4d, 0x55, 0x05, 0x44, 0xa8, 0xbd, 0xc8, 0xf2, 0xfc, 0xa6, 0x0b, 0xac, 0x2e, 0xf8, 0xd4, 0x98, 0x66, + 0x17, 0xac, 0xe4, 0x6a, 0x26, 0x7d, 0xe6, 0xed, 0x40, 0x0b, 0x79, 0x97, 0x97, 0x45, 0x2b, 0x74, 0x3d, 0x88, 0x16, + 0xfe, 0x41, 0x73, 0x3c, 0x7a, 0xb6, 0xad, 0xa6, 0x36, 0xfb, 0x5a, 0x8b, 0x05, 0x32, 0x10, 0x0d, 0x7d, 0xa1, 0x62, + 0x14, 0xee, 0x2a, 0xcd, 0xd5, 0x6a, 0x5f, 0x95, 0x41, 0x02, 0x13, 0x41, 0xd6, 0xb2, 0xf0, 0x1e, 0xdd, 0xab, 0x47, + 0x9a, 0x57, 0x12, 0x3c, 0x73, 0xf1, 0x17, 0x00, 0x42, 0x79, 0x92, 0x90, 0x03, 0x72, 0x00, 0x7f, 0x4b, 0x51, 0x2a, + 0x0d, 0xf0, 0xcf, 0xea, 0x72, 0x6c, 0xeb, 0xfb, 0x3b, 0xad, 0x62, 0x70, 0xfd, 0xf9, 0xba, 0xeb, 0x59, 0x3b, 0xc4, + 0x39, 0xb7, 0xd5, 0x6b, 0xcb, 0x34, 0x8f, 0x91, 0xba, 0x06, 0xe0, 0x4e, 0xa4, 0x47, 0x20, 0x92, 0x99, 0x68, 0x90, + 0xb3, 0xe7, 0x7c, 0x3c, 0x15, 0x8f, 0x49, 0x7b, 0xb9, 0xef, 0x9b, 0x0b, 0x7d, 0x30, 0xc6, 0xbe, 0x05, 0x0d, 0xe2, + 0xa3, 0xd5, 0xd6, 0x0a, 0xc4, 0x7a, 0xa7, 0xd4, 0x87, 0x6e, 0x8c, 0x82, 0x0e, 0x1e, 0x71, 0x23, 0x17, 0x1c, 0xdb, + 0x5d, 0x5b, 0x4f, 0xe9, 0x2b, 0x00, 0x73, 0x1d, 0xa8, 0x64, 0x18, 0xa4, 0x2e, 0x13, 0x85, 0x49, 0x7e, 0x99, 0x90, + 0x84, 0x88, 0xea, 0x6c, 0x39, 0x4a, 0x95, 0x69, 0x01, 0x97, 0x19, 0x19, 0x60, 0x36, 0x69, 0xd6, 0x4f, 0x2e, 0x5f, + 0x62, 0x18, 0x01, 0xf1, 0x33, 0xfa, 0xb1, 0x56, 0x89, 0x97, 0x8c, 0xee, 0x1c, 0x75, 0x83, 0x2a, 0xc9, 0x5c, 0xbf, + 0xb9, 0x9d, 0x45, 0xca, 0xbc, 0x60, 0xb8, 0x5d, 0xa5, 0xf9, 0x87, 0xb0, 0x4e, 0xf0, 0xdb, 0x00, 0x95, 0xf4, 0xa9, + 0xf0, 0xa2, 0x11, 0x40, 0xa8, 0xef, 0x55, 0x19, 0x9f, 0x0a, 0x2f, 0x1b, 0xed, 0x58, 0x46, 0x29, 0x54, 0x17, 0xcc, + 0x6e, 0x4d, 0x17, 0xa2, 0x5b, 0x55, 0x03, 0x6d, 0xe0, 0xda, 0x15, 0x52, 0xba, 0x81, 0x6a, 0x57, 0x6e, 0x58, 0x80, + 0x68, 0x33, 0x11, 0x18, 0x2e, 0xff, 0x3e, 0x7f, 0xa9, 0x62, 0x78, 0xfa, 0xfd, 0xd0, 0x3b, 0xec, 0x82, 0x68, 0xb4, + 0xbb, 0x66, 0xfb, 0x20, 0x1a, 0xed, 0xaf, 0x1b, 0x46, 0xbf, 0x9f, 0xd1, 0xef, 0x67, 0x0d, 0xe8, 0x48, 0x84, 0x09, + 0xb3, 0xd7, 0x6f, 0xd4, 0xf2, 0x95, 0x5a, 0xbf, 0x53, 0xcb, 0x97, 0x6a, 0x78, 0xeb, 0x40, 0x12, 0x41, 0x64, 0xa9, + 0x6a, 0x1e, 0x24, 0x45, 0xaa, 0xa5, 0xcb, 0x31, 0x5a, 0x8c, 0xa8, 0xa5, 0xac, 0x39, 0xd5, 0x89, 0xb4, 0x73, 0x50, + 0x32, 0xc0, 0xd1, 0xe2, 0xaa, 0xc6, 0x74, 0xb3, 0xa2, 0x25, 0x10, 0x23, 0xac, 0x6c, 0xcb, 0xc5, 0x4d, 0xea, 0xa3, + 0x2b, 0xf2, 0x6d, 0xcb, 0x95, 0x6f, 0x5b, 0xc1, 0xab, 0xaf, 0x28, 0x94, 0x4b, 0xae, 0x95, 0xed, 0xd3, 0x42, 0x29, + 0x94, 0x71, 0x0d, 0xb6, 0xf6, 0x4d, 0x60, 0xc8, 0x7c, 0xa4, 0xa8, 0xb1, 0xbd, 0x68, 0x94, 0x43, 0x90, 0xad, 0x83, + 0x51, 0xa7, 0x2c, 0x58, 0x7c, 0xbb, 0x43, 0x06, 0x32, 0xd0, 0x51, 0xd5, 0xc6, 0xab, 0x9d, 0x95, 0xfe, 0xb0, 0xbc, + 0x7a, 0xc6, 0x12, 0x2b, 0x9d, 0xfc, 0xa6, 0x42, 0x7f, 0x10, 0xa2, 0x6f, 0xca, 0x96, 0x83, 0x17, 0x5d, 0x6c, 0x65, + 0x40, 0xbc, 0x61, 0x7a, 0x6f, 0x6b, 0x25, 0xcb, 0x5d, 0x53, 0xbe, 0x98, 0xf1, 0x84, 0xe3, 0xe8, 0xcb, 0xd5, 0x22, + 0xac, 0xd5, 0x22, 0x3b, 0x01, 0x1e, 0x5a, 0xab, 0xa5, 0x90, 0xab, 0x45, 0x38, 0x33, 0x5d, 0xa8, 0x99, 0x9e, 0x81, + 0xe6, 0x51, 0xa8, 0x59, 0x9e, 0x00, 0x16, 0xbc, 0x30, 0x33, 0x5c, 0x98, 0x19, 0x8e, 0x43, 0x6a, 0x9c, 0x1e, 0xf4, + 0x5e, 0xe7, 0x9e, 0x5b, 0xee, 0x46, 0xa7, 0x61, 0xde, 0x4e, 0x36, 0x98, 0xd3, 0x83, 0x70, 0x02, 0xf1, 0x81, 0x25, + 0x02, 0xf4, 0x68, 0x58, 0x1d, 0x35, 0x54, 0x8e, 0xe2, 0xcb, 0x02, 0x90, 0x2c, 0x09, 0x40, 0xf2, 0xa0, 0xc6, 0xb9, + 0xb4, 0xfc, 0xba, 0x4a, 0x42, 0x8e, 0xc8, 0x78, 0x29, 0xed, 0xee, 0x09, 0x2f, 0x47, 0x46, 0x68, 0x9e, 0x2c, 0x52, + 0xaf, 0x62, 0x19, 0x1b, 0x23, 0x70, 0x51, 0xe8, 0x37, 0x79, 0xbf, 0x9f, 0x96, 0x5e, 0x45, 0xed, 0xfc, 0x04, 0xfe, + 0x96, 0xe7, 0xce, 0x22, 0x47, 0xc8, 0xab, 0x91, 0x49, 0x58, 0x5e, 0x2a, 0xf5, 0xf4, 0x25, 0xcc, 0xa0, 0xee, 0xde, + 0x28, 0x00, 0xd7, 0x42, 0x39, 0xd5, 0x96, 0x70, 0x65, 0xaa, 0x0c, 0xf6, 0x79, 0xc8, 0x65, 0x68, 0x87, 0x44, 0x1e, + 0x29, 0xac, 0xfb, 0xf6, 0xd5, 0xb3, 0x89, 0xeb, 0xc3, 0x62, 0xa3, 0x11, 0x1c, 0x8f, 0x00, 0x73, 0x30, 0xf5, 0xa2, + 0x01, 0x2f, 0xd5, 0x9c, 0xf9, 0xe8, 0x55, 0x84, 0x8d, 0x01, 0x6a, 0x8a, 0x81, 0x53, 0xd6, 0x53, 0xf9, 0xc8, 0xf8, + 0x96, 0xf9, 0x7e, 0x80, 0xef, 0xd6, 0x85, 0x84, 0x7c, 0x50, 0xa8, 0x04, 0x99, 0x42, 0x25, 0x48, 0x0c, 0x2a, 0x41, + 0x6c, 0x50, 0x09, 0xb6, 0x0d, 0xdf, 0x48, 0xe5, 0x6d, 0x04, 0x1c, 0x11, 0x3e, 0xf4, 0x2c, 0x6c, 0xac, 0x50, 0x3c, + 0x1b, 0xb3, 0x31, 0x2b, 0xd4, 0xce, 0x53, 0xc9, 0xa9, 0xd8, 0x59, 0x8c, 0x75, 0x13, 0x59, 0x26, 0x5e, 0x48, 0xd0, + 0x71, 0xce, 0x85, 0x44, 0x5d, 0xfd, 0xdc, 0x7b, 0x49, 0xc6, 0x92, 0x79, 0x43, 0xa3, 0x06, 0xf3, 0xb2, 0xeb, 0x00, + 0xa6, 0x25, 0xdf, 0x16, 0x34, 0x98, 0x4e, 0x95, 0x47, 0xa4, 0x49, 0x50, 0x3b, 0x97, 0x49, 0x91, 0x13, 0xc2, 0x24, + 0xe8, 0x95, 0xe0, 0x37, 0x12, 0xda, 0xff, 0xab, 0x9e, 0xef, 0x80, 0xc1, 0x44, 0xab, 0xe4, 0x0b, 0x58, 0x2d, 0x2b, + 0xfe, 0x42, 0x7a, 0x62, 0x23, 0xfe, 0x62, 0x99, 0xc6, 0xa3, 0x2f, 0x6c, 0x88, 0x78, 0x56, 0x2f, 0xd0, 0xb4, 0x04, + 0x75, 0x80, 0x47, 0xf4, 0xd7, 0xe8, 0x8b, 0xe1, 0x4d, 0xe9, 0x6a, 0xa4, 0xae, 0xd9, 0x25, 0xe7, 0xef, 0x6a, 0x43, + 0x84, 0x8c, 0x69, 0x53, 0x20, 0x19, 0x10, 0x48, 0x32, 0x10, 0x00, 0x98, 0x9a, 0xce, 0xec, 0x15, 0x40, 0x34, 0x10, + 0xc0, 0xe3, 0xaa, 0xe3, 0xf1, 0x23, 0xfd, 0x55, 0x9c, 0xf6, 0x4e, 0xd3, 0xb0, 0xc3, 0x17, 0xa0, 0x29, 0x86, 0x72, + 0x3c, 0xdf, 0x29, 0x48, 0xf6, 0x28, 0x65, 0xe9, 0xaa, 0x89, 0xec, 0x50, 0xac, 0x4f, 0x73, 0xce, 0x42, 0xda, 0x96, + 0x63, 0xb4, 0xc5, 0xfa, 0x31, 0xf2, 0xde, 0xca, 0xa8, 0xc8, 0x07, 0x3d, 0xb8, 0xbd, 0xbd, 0x79, 0xd5, 0x63, 0x36, + 0xc9, 0x8a, 0x45, 0xae, 0x22, 0xce, 0x9c, 0xd6, 0x21, 0x07, 0x0c, 0xc8, 0x49, 0x08, 0x4c, 0x63, 0x5c, 0x2a, 0xd0, + 0x41, 0xc9, 0x72, 0x59, 0x03, 0xb5, 0x2c, 0x22, 0x6b, 0x80, 0xa8, 0xa6, 0xf9, 0x57, 0x0d, 0xf9, 0x49, 0xde, 0x9c, + 0x53, 0xa8, 0x7d, 0xc5, 0xc3, 0xea, 0xfc, 0x89, 0x55, 0x9b, 0x18, 0xeb, 0x5f, 0x6b, 0x4f, 0xd0, 0x56, 0xd2, 0x40, + 0x7c, 0xe7, 0xab, 0x74, 0x45, 0xa1, 0x3b, 0xce, 0x4c, 0x3c, 0x57, 0x81, 0xb1, 0x6f, 0xed, 0x08, 0x0a, 0x87, 0xa6, + 0xeb, 0x80, 0xc3, 0x34, 0x3a, 0x61, 0xf1, 0x4f, 0xe9, 0x38, 0x79, 0x55, 0x2b, 0x44, 0x92, 0xbf, 0x0b, 0x17, 0x86, + 0xc4, 0x82, 0xbc, 0x24, 0xd4, 0x11, 0x19, 0xb1, 0x1a, 0x15, 0x1b, 0xa1, 0xa2, 0xe2, 0x14, 0x8f, 0xb7, 0x0a, 0x8a, + 0x4b, 0x51, 0xaa, 0x94, 0x8a, 0xdc, 0xa8, 0x14, 0x10, 0xcb, 0x06, 0xde, 0x2d, 0xe0, 0x00, 0x08, 0x3a, 0xcb, 0xfd, + 0xc6, 0x76, 0xb7, 0x91, 0xf9, 0xcc, 0x34, 0x4f, 0xab, 0x0f, 0xea, 0xef, 0xf7, 0x4b, 0x8c, 0xad, 0xf1, 0xf4, 0xf7, + 0x6d, 0x5a, 0x70, 0xf3, 0x37, 0x0c, 0xd1, 0x0a, 0x10, 0x31, 0x4b, 0x7b, 0x28, 0x64, 0xc1, 0x84, 0x65, 0xa8, 0xca, + 0x53, 0x8e, 0x7a, 0xd5, 0xe4, 0x0e, 0x20, 0xd4, 0xd0, 0xaf, 0x8d, 0x4e, 0x75, 0x55, 0x82, 0xf0, 0x7d, 0x57, 0xa8, + 0xc7, 0xe6, 0x80, 0x27, 0x03, 0xe0, 0xaf, 0xc8, 0x6b, 0x3d, 0xb6, 0x7f, 0xd0, 0x1b, 0xf5, 0x06, 0x08, 0xa2, 0x73, + 0x59, 0xf8, 0x27, 0x9c, 0xeb, 0xd4, 0x9f, 0x71, 0x21, 0x88, 0x6f, 0x3d, 0x09, 0xef, 0xc5, 0x45, 0x1a, 0x07, 0x17, + 0xbd, 0x81, 0xb9, 0x08, 0x14, 0x17, 0x69, 0x7e, 0x01, 0x61, 0xf9, 0x88, 0x89, 0x58, 0xb3, 0x15, 0xc0, 0x04, 0x96, + 0x3a, 0x0e, 0x59, 0x75, 0x6c, 0xbf, 0xff, 0x7a, 0x64, 0xc8, 0xd2, 0x11, 0x06, 0x46, 0xff, 0x06, 0x14, 0xa1, 0x12, + 0x96, 0x99, 0xed, 0xc1, 0xa4, 0xab, 0x3d, 0xab, 0xe7, 0xcd, 0x36, 0xef, 0xea, 0x1d, 0xab, 0x69, 0x15, 0x35, 0x2d, + 0xb7, 0x9a, 0x36, 0xa9, 0xa0, 0x66, 0xa2, 0xdf, 0xd7, 0xa0, 0xa8, 0xd5, 0x1c, 0xc0, 0xd8, 0x30, 0xf9, 0xf5, 0x2c, + 0x9f, 0xf7, 0xfb, 0x9e, 0x7c, 0x04, 0xbf, 0x90, 0xad, 0xcc, 0xad, 0xb1, 0x7c, 0xfa, 0x8a, 0x48, 0xcc, 0x0c, 0xcc, + 0xd1, 0xea, 0x04, 0xdf, 0xeb, 0x56, 0x78, 0x1d, 0x73, 0x85, 0xcd, 0xc4, 0xf4, 0x35, 0x0c, 0x9e, 0x27, 0x7c, 0x70, + 0x91, 0xa3, 0xbf, 0x91, 0xc3, 0x4c, 0x61, 0x41, 0xce, 0xfd, 0xc9, 0x6b, 0xc4, 0x4b, 0x46, 0x78, 0x07, 0x9d, 0x4e, + 0x78, 0x90, 0xfd, 0xfe, 0x1a, 0x3a, 0xb3, 0x95, 0x4a, 0xd9, 0xaa, 0xa8, 0x4c, 0x37, 0x75, 0x51, 0x56, 0xd0, 0xb1, + 0xf4, 0xf3, 0x4e, 0xc8, 0xcc, 0xfa, 0x99, 0x05, 0xf7, 0xb4, 0x96, 0x00, 0x53, 0xb6, 0x6d, 0xa2, 0x36, 0xf0, 0xb2, + 0x2e, 0x3e, 0x17, 0x78, 0x74, 0xd6, 0x5e, 0x6f, 0x84, 0xda, 0x67, 0xb8, 0xcf, 0x6f, 0x3c, 0xf0, 0x83, 0x99, 0xa5, + 0x73, 0x45, 0x9c, 0x51, 0xf9, 0xa3, 0xcf, 0x45, 0x9a, 0x53, 0x1e, 0xe0, 0x3e, 0x14, 0x73, 0xfb, 0x2d, 0x90, 0x7e, + 0xe8, 0x2d, 0x90, 0x7d, 0x74, 0xce, 0xc9, 0x6b, 0x40, 0xa4, 0x43, 0x18, 0xdc, 0x9c, 0x04, 0x1d, 0xab, 0x86, 0x77, + 0x16, 0xd8, 0x69, 0x2f, 0x8d, 0x7b, 0x69, 0x7e, 0x91, 0xf6, 0xfb, 0x06, 0x35, 0x33, 0x45, 0x38, 0x78, 0x9c, 0x91, + 0x8b, 0xa4, 0x05, 0x5b, 0x4a, 0xfb, 0xaf, 0x06, 0x8e, 0xa8, 0x10, 0xa0, 0xf9, 0xef, 0xc2, 0x7b, 0x02, 0x10, 0x9b, + 0xb4, 0x01, 0x57, 0x3d, 0xa6, 0xa3, 0xb1, 0x25, 0x51, 0xab, 0xce, 0x06, 0x48, 0x9c, 0x2a, 0xad, 0xa7, 0xdc, 0xac, + 0x29, 0x0c, 0x52, 0x65, 0xa1, 0x7e, 0x63, 0x3d, 0x99, 0xac, 0x72, 0x91, 0x11, 0x47, 0x65, 0x7a, 0x57, 0x33, 0x82, + 0xe9, 0xd2, 0xcf, 0x17, 0xb0, 0x64, 0xe3, 0x8f, 0x38, 0x79, 0x4b, 0xc0, 0xb1, 0x9d, 0xb5, 0xab, 0x6a, 0x97, 0xe3, + 0xd6, 0x6e, 0x0e, 0xf0, 0xbd, 0xde, 0x68, 0x34, 0xd2, 0xce, 0x71, 0x02, 0x86, 0xaa, 0xa7, 0x96, 0x42, 0x8f, 0xd5, + 0x0a, 0x50, 0xb7, 0x23, 0x97, 0x59, 0x32, 0x98, 0x2f, 0x8c, 0xe3, 0x97, 0xe6, 0xa3, 0x8f, 0x97, 0xca, 0xda, 0x75, + 0xc4, 0xd7, 0x7f, 0x94, 0xd5, 0xfa, 0x96, 0x77, 0x55, 0x13, 0xf0, 0x45, 0x15, 0x50, 0xfa, 0x0d, 0xef, 0xc9, 0xde, + 0xc5, 0xd7, 0x6e, 0xb1, 0x4b, 0xbe, 0xe5, 0x2d, 0xea, 0x3c, 0x5f, 0x39, 0xb8, 0x51, 0xa5, 0xdb, 0x7b, 0xc9, 0x02, + 0xd7, 0xde, 0x49, 0xd3, 0x58, 0xcf, 0xfc, 0xe8, 0x61, 0x11, 0xb2, 0x9d, 0x8f, 0xbd, 0xaf, 0x9a, 0xa7, 0x67, 0x0d, + 0xbd, 0x49, 0x0d, 0x7d, 0xec, 0x45, 0xd9, 0x3e, 0x35, 0x8d, 0xe8, 0x35, 0x6c, 0xe8, 0x63, 0x6f, 0xc9, 0xc9, 0x21, + 0x11, 0xe0, 0xd4, 0x98, 0x3f, 0x3e, 0x9c, 0xce, 0xf0, 0x77, 0x0c, 0xa8, 0x04, 0x62, 0x3e, 0x3f, 0xa6, 0x1d, 0x05, + 0x98, 0x51, 0xa5, 0xb7, 0xcf, 0x0f, 0x6c, 0xc7, 0xcb, 0x7a, 0x68, 0xe9, 0xdd, 0xb3, 0xa3, 0xdb, 0xf1, 0xaa, 0x1a, + 0x5f, 0xca, 0x21, 0xcf, 0xf3, 0xd9, 0x68, 0x34, 0x12, 0x06, 0x92, 0x3b, 0xd7, 0x1b, 0x58, 0x81, 0xb4, 0x2d, 0xaa, + 0x0f, 0xe5, 0xd2, 0xdb, 0xab, 0x43, 0x3b, 0xf7, 0x27, 0xd5, 0xf1, 0x58, 0x8c, 0xcc, 0x31, 0x9e, 0xfb, 0xc7, 0x63, + 0xa1, 0xe4, 0x28, 0x59, 0x4b, 0x10, 0x9d, 0xd2, 0x78, 0x2a, 0xeb, 0xb5, 0x13, 0x91, 0x57, 0x23, 0xce, 0x43, 0xf0, + 0x57, 0x2f, 0x67, 0xa5, 0xfe, 0x54, 0xf8, 0xe8, 0xa7, 0x4a, 0xe9, 0x05, 0xaf, 0x0a, 0x08, 0x11, 0xfb, 0xbb, 0x81, + 0x76, 0x50, 0x82, 0x43, 0x09, 0xf7, 0x1e, 0xaf, 0x93, 0xaf, 0xbc, 0x6a, 0x26, 0x63, 0x94, 0x7b, 0x83, 0x7c, 0xce, + 0x00, 0xa6, 0xd2, 0x67, 0xe0, 0x77, 0x09, 0x50, 0xa7, 0xf8, 0x14, 0x9d, 0xea, 0xcd, 0xc3, 0xa6, 0xeb, 0xd3, 0x12, + 0x45, 0x11, 0xdd, 0xf9, 0xf9, 0x18, 0x10, 0x3b, 0xbb, 0x36, 0x23, 0xed, 0xda, 0x6f, 0xd0, 0x60, 0x95, 0x26, 0xad, + 0x95, 0x53, 0xc2, 0x6e, 0x57, 0x23, 0x5b, 0xfa, 0x51, 0x0a, 0xc4, 0xca, 0x71, 0x22, 0x91, 0x3d, 0xd8, 0xc8, 0x09, + 0xdc, 0xa2, 0xbd, 0xa3, 0x03, 0x50, 0xb9, 0x51, 0x90, 0x5f, 0xcd, 0x89, 0xdc, 0xf1, 0x7d, 0xef, 0xfb, 0x41, 0x3d, + 0xf8, 0xbe, 0x77, 0x91, 0x92, 0xdc, 0x11, 0x5e, 0xa8, 0x29, 0x21, 0xe2, 0x8b, 0xef, 0x07, 0xd5, 0x00, 0xcf, 0x12, + 0x2d, 0xd2, 0x22, 0xa1, 0x5a, 0x5d, 0xe3, 0x26, 0xbc, 0x48, 0x24, 0xf7, 0xd0, 0xbe, 0xf3, 0x88, 0x58, 0x00, 0x32, + 0x16, 0x9f, 0xcd, 0x1b, 0x0a, 0x75, 0x37, 0x31, 0x5b, 0x74, 0x97, 0xc5, 0x7e, 0x7f, 0x93, 0xa7, 0x75, 0x4f, 0xc7, + 0xc7, 0xe0, 0x0b, 0x52, 0x4d, 0x80, 0x47, 0xfb, 0x2b, 0x73, 0xbc, 0x7a, 0xb5, 0x39, 0x52, 0x16, 0xaa, 0x44, 0xfd, + 0x16, 0xab, 0x59, 0x0f, 0x61, 0xb8, 0xb3, 0xcc, 0x58, 0xdb, 0x0b, 0x9e, 0xcb, 0x59, 0x15, 0xdb, 0xe5, 0xf8, 0x8a, + 0xa5, 0x36, 0x97, 0xa8, 0x1c, 0xad, 0xc7, 0xda, 0x14, 0x23, 0xbf, 0x52, 0x48, 0x94, 0x45, 0xc7, 0xd6, 0x42, 0x01, + 0xf1, 0x02, 0xf4, 0x25, 0x7b, 0xd3, 0x00, 0xeb, 0x8d, 0x5e, 0x45, 0x84, 0x96, 0x8f, 0x54, 0x78, 0x9b, 0x9b, 0x2a, + 0xb3, 0xb2, 0x59, 0xb4, 0xfb, 0x29, 0xe7, 0x39, 0x82, 0xd5, 0x1b, 0xb5, 0x47, 0x01, 0x6a, 0x0f, 0x2d, 0x94, 0x01, + 0xa4, 0x34, 0xcd, 0x00, 0x90, 0x01, 0x40, 0xa6, 0x8a, 0xf8, 0x4c, 0x80, 0x4a, 0x5b, 0xdd, 0x28, 0x70, 0x22, 0xbd, + 0x02, 0x9a, 0x05, 0x56, 0xfa, 0x48, 0x41, 0x06, 0x8b, 0x2d, 0x02, 0xb0, 0x72, 0xe4, 0x0c, 0xd3, 0x18, 0xb2, 0x8d, + 0x26, 0x2e, 0x49, 0xf3, 0xfb, 0x30, 0x4b, 0x25, 0x9e, 0xc4, 0x8f, 0xb2, 0xc6, 0x08, 0x00, 0xa4, 0xef, 0xd3, 0x8b, + 0x22, 0x8b, 0x09, 0x07, 0xce, 0x7a, 0xea, 0xa0, 0xa8, 0xc9, 0xb9, 0xd6, 0xb4, 0x7a, 0x56, 0x9b, 0x3c, 0x64, 0x81, + 0xce, 0x1e, 0x8c, 0x49, 0x2d, 0xdf, 0xf3, 0xc8, 0xfe, 0xca, 0xe9, 0x8c, 0xf0, 0x5d, 0x77, 0x70, 0xea, 0xbf, 0xdb, + 0x1a, 0x98, 0x98, 0x12, 0x80, 0x8d, 0xc1, 0xd1, 0x84, 0xf8, 0x9d, 0x8e, 0xc9, 0xd4, 0x26, 0x45, 0x20, 0xf0, 0x10, + 0xbc, 0x82, 0xeb, 0x0b, 0x19, 0x30, 0x20, 0x68, 0x3b, 0x8b, 0x3c, 0x4d, 0x00, 0x4e, 0xbc, 0xe0, 0x3b, 0x80, 0xe3, + 0xd4, 0xab, 0x42, 0xf6, 0xec, 0xa5, 0x98, 0xce, 0xe6, 0xc1, 0x43, 0x42, 0xfb, 0x17, 0x13, 0x7e, 0xd3, 0x5d, 0x25, + 0x57, 0xa6, 0xd6, 0xbd, 0x89, 0xae, 0x72, 0x95, 0xd3, 0xa7, 0x39, 0xc7, 0x30, 0x67, 0xb0, 0x0a, 0xc8, 0x39, 0x1b, + 0xf2, 0xe7, 0x97, 0x00, 0xd8, 0xb2, 0x16, 0x5e, 0xc4, 0x9f, 0x87, 0xb2, 0x5a, 0x00, 0xf7, 0xc8, 0x79, 0x64, 0x7e, + 0xf9, 0x6a, 0x3b, 0x94, 0x73, 0x8a, 0xc2, 0x58, 0xce, 0x4d, 0x4b, 0x8a, 0xd3, 0xa1, 0xa7, 0x60, 0x32, 0xb5, 0xe5, + 0xef, 0x5d, 0xe2, 0x32, 0x7b, 0x33, 0x09, 0xe7, 0xeb, 0xc8, 0xb6, 0xb5, 0xea, 0x1e, 0xba, 0x21, 0x18, 0xf4, 0x31, + 0x82, 0x96, 0xd5, 0x9b, 0x5f, 0x31, 0x18, 0x28, 0x6c, 0xdf, 0x9a, 0x6e, 0x5a, 0x74, 0x8a, 0x03, 0xce, 0xac, 0x75, + 0x8d, 0x4a, 0x55, 0x71, 0xe8, 0x25, 0xef, 0x96, 0x95, 0xdb, 0x65, 0xe9, 0x85, 0x20, 0x35, 0xea, 0x2a, 0x42, 0xa4, + 0x54, 0xec, 0xf0, 0x9e, 0xfc, 0x1a, 0x98, 0x78, 0x66, 0xe5, 0x28, 0x8d, 0xe7, 0x00, 0x13, 0xa4, 0xd0, 0x37, 0xe5, + 0x57, 0x80, 0x1b, 0xba, 0x88, 0xc2, 0xec, 0x4d, 0x5c, 0x05, 0xb5, 0xd5, 0xf4, 0x7b, 0x07, 0x27, 0xf6, 0xb2, 0xee, + 0xf7, 0x53, 0xa2, 0xf1, 0xc3, 0xd0, 0x0b, 0xfc, 0x7b, 0x3c, 0x3d, 0x34, 0x41, 0x6a, 0x5e, 0x79, 0x80, 0x57, 0x74, + 0xb9, 0xb5, 0x29, 0x57, 0x34, 0x2e, 0xe6, 0x35, 0x22, 0xc2, 0xa7, 0x8e, 0x62, 0xbb, 0xcd, 0x8f, 0x53, 0x1b, 0x83, + 0x41, 0x08, 0xf7, 0xad, 0x8c, 0xdf, 0x27, 0x5e, 0x35, 0x8b, 0xe6, 0xa0, 0x28, 0xcd, 0x34, 0x49, 0x48, 0x21, 0xbd, + 0x04, 0xe8, 0xa3, 0x41, 0xa8, 0xd5, 0x95, 0x7f, 0x24, 0x5e, 0xaa, 0xa6, 0xb5, 0x79, 0x8a, 0x35, 0x0a, 0xc4, 0x2c, + 0x9a, 0x37, 0x2c, 0xa3, 0x43, 0x52, 0x5d, 0x2e, 0x4d, 0x33, 0xfe, 0xb0, 0x9a, 0xa1, 0x5a, 0x71, 0xd2, 0x04, 0x35, + 0x4a, 0xb7, 0x70, 0x01, 0xfc, 0x1b, 0xdd, 0x71, 0x54, 0xa3, 0x48, 0xd1, 0x80, 0x4f, 0x20, 0xa6, 0xab, 0x30, 0x9b, + 0x27, 0xac, 0x35, 0x75, 0xcd, 0xe8, 0xf7, 0x65, 0x9c, 0x90, 0x49, 0x42, 0x72, 0x3e, 0x5c, 0xae, 0x1f, 0x49, 0x75, + 0x01, 0xa4, 0xca, 0x39, 0x9b, 0xf5, 0x7a, 0x73, 0xc0, 0xe8, 0x85, 0xf5, 0x0b, 0x1b, 0x57, 0x70, 0x79, 0x4d, 0x98, + 0xbb, 0xea, 0x47, 0x98, 0x65, 0x50, 0x05, 0xa4, 0xf9, 0xb1, 0xa0, 0xd3, 0x2b, 0x17, 0x88, 0xfa, 0xf5, 0x48, 0x5d, + 0x50, 0x66, 0xe9, 0xdc, 0x22, 0x02, 0x01, 0xaf, 0x61, 0xf5, 0x04, 0x92, 0x7d, 0xf9, 0xd8, 0xa7, 0x19, 0x05, 0xaa, + 0x23, 0x00, 0x65, 0xb3, 0x7e, 0x08, 0xfb, 0x07, 0x84, 0x13, 0xea, 0x6f, 0xde, 0xca, 0x59, 0x43, 0xf2, 0x40, 0xaa, + 0x09, 0x8f, 0xe1, 0xd4, 0x58, 0xe0, 0x4b, 0x8b, 0xde, 0x54, 0xf0, 0x9a, 0xe0, 0xb8, 0x17, 0x68, 0xed, 0x5b, 0xc0, + 0x11, 0x22, 0xb8, 0x0c, 0x4d, 0x9c, 0xf6, 0xf6, 0xbd, 0x00, 0x09, 0xcd, 0x2d, 0x9c, 0xeb, 0xb7, 0x2e, 0x68, 0x71, + 0x8a, 0x9c, 0x2c, 0xba, 0xc0, 0x40, 0x17, 0x64, 0xde, 0xf8, 0x67, 0x0e, 0x2b, 0x17, 0x20, 0x7b, 0xa9, 0x58, 0x49, + 0xc4, 0xb6, 0x57, 0x7f, 0x94, 0xca, 0x7e, 0x7b, 0x61, 0x4d, 0xe0, 0x97, 0x89, 0xfd, 0x12, 0x99, 0x7c, 0xd3, 0x53, + 0x93, 0xaf, 0x8c, 0x85, 0x4e, 0x2d, 0x83, 0x73, 0x7a, 0x62, 0x70, 0xee, 0xed, 0xad, 0xda, 0x94, 0x30, 0x14, 0x24, + 0x81, 0xa6, 0x4b, 0x0f, 0xeb, 0xa6, 0x3f, 0x3f, 0x69, 0xf1, 0x6b, 0xd5, 0xbe, 0x75, 0x3f, 0x0e, 0xb1, 0x8b, 0x5f, + 0x26, 0x9e, 0x61, 0x1f, 0xf5, 0x81, 0x03, 0x4c, 0x46, 0x4c, 0x5c, 0xf7, 0xfb, 0x50, 0xd8, 0x6c, 0x3c, 0x1f, 0xd5, + 0xc5, 0xcf, 0xc5, 0x03, 0x40, 0x39, 0x54, 0x60, 0x97, 0x43, 0x19, 0xca, 0x88, 0x4d, 0x6d, 0xb9, 0xe7, 0xf7, 0x97, + 0x61, 0x0e, 0xf2, 0x8e, 0xc6, 0xc4, 0xb9, 0x00, 0x31, 0x0c, 0xbe, 0xfe, 0xfd, 0x93, 0x43, 0xda, 0x7c, 0x7f, 0x01, + 0xdf, 0x1d, 0x5d, 0x7c, 0x40, 0x8e, 0x9b, 0x8b, 0x4d, 0x59, 0xdc, 0xa7, 0xb1, 0xb8, 0xf8, 0x1e, 0x52, 0xbf, 0xbf, + 0x28, 0xca, 0x8b, 0xef, 0x55, 0x65, 0xbe, 0xbf, 0xa0, 0x05, 0x37, 0xfa, 0xdd, 0x9a, 0x78, 0xff, 0xc8, 0x35, 0xed, + 0xd9, 0x12, 0xc2, 0xb1, 0xb4, 0xfa, 0x11, 0x94, 0x88, 0x8a, 0x14, 0x55, 0x86, 0xb2, 0x5a, 0x3b, 0xce, 0xfb, 0x44, + 0xc3, 0x63, 0xd3, 0x84, 0xc4, 0xd5, 0x12, 0xd6, 0xa1, 0x9e, 0x9d, 0x36, 0xc9, 0x8e, 0xf3, 0x40, 0x1d, 0x10, 0x15, + 0x7f, 0x5e, 0x8d, 0x76, 0xf4, 0x35, 0xf8, 0xd6, 0xf1, 0x58, 0x8d, 0xf6, 0xe6, 0xa7, 0x4f, 0xd6, 0x4a, 0x19, 0x6c, + 0xa4, 0x18, 0x85, 0x90, 0x28, 0x6e, 0xd7, 0x63, 0x00, 0xfc, 0xef, 0x1f, 0x8f, 0xf4, 0x7b, 0x2f, 0x7f, 0xab, 0xdd, + 0xd2, 0xaa, 0xe7, 0x87, 0x16, 0x61, 0xc6, 0xab, 0xda, 0xb0, 0xb3, 0x1d, 0x24, 0xa0, 0xf4, 0xa1, 0x69, 0x50, 0x53, + 0x44, 0x3f, 0x61, 0x35, 0xb1, 0x9c, 0xc3, 0x82, 0x94, 0x38, 0xc4, 0x70, 0x8c, 0x76, 0xe8, 0x71, 0xba, 0xa8, 0x79, + 0x2a, 0xdf, 0x21, 0xe3, 0xd6, 0xf7, 0x01, 0xc9, 0xa5, 0x70, 0xf9, 0xc1, 0x0b, 0x0d, 0x26, 0x7a, 0x91, 0x57, 0x45, + 0x26, 0x46, 0x82, 0x46, 0xf9, 0x0d, 0x89, 0x33, 0x17, 0x58, 0x8b, 0x0b, 0x85, 0x10, 0x16, 0x12, 0x2a, 0x77, 0x51, + 0x52, 0x7a, 0x70, 0xf1, 0xe4, 0x50, 0x36, 0xbf, 0x13, 0x26, 0xc4, 0x68, 0x01, 0x34, 0x38, 0xfb, 0x76, 0x79, 0x0f, + 0x61, 0x99, 0x7b, 0xbf, 0xbf, 0x59, 0xe5, 0x05, 0xc4, 0x65, 0x5e, 0x48, 0xc5, 0x6a, 0x79, 0x01, 0x34, 0x79, 0x22, + 0xbe, 0x08, 0x2b, 0x39, 0x0d, 0xaa, 0x8e, 0x62, 0xd5, 0x36, 0x5e, 0x56, 0x1e, 0xf0, 0x7a, 0xbf, 0x4f, 0x80, 0xc0, + 0xdd, 0x67, 0xaf, 0x95, 0x5b, 0x2a, 0xe9, 0x91, 0xe7, 0x18, 0x22, 0x99, 0x00, 0xaf, 0x33, 0x04, 0x47, 0x0a, 0xab, + 0xe7, 0x26, 0xc8, 0x3f, 0xbe, 0x39, 0xa3, 0xf8, 0xa2, 0x79, 0x14, 0x35, 0x2c, 0x64, 0x09, 0x1c, 0x0f, 0xc9, 0x2c, + 0x9b, 0x23, 0x35, 0x79, 0xda, 0x9e, 0x22, 0x1d, 0x9d, 0x58, 0xe2, 0xb7, 0x35, 0xa9, 0x5e, 0xa4, 0xc2, 0x2e, 0x69, + 0x67, 0x2b, 0x2a, 0x2f, 0x84, 0xa1, 0x4a, 0xb8, 0xf7, 0xaa, 0x9e, 0x85, 0x72, 0x53, 0xb4, 0x2a, 0x66, 0x0f, 0x53, + 0x62, 0x86, 0x29, 0xd6, 0x5f, 0xd8, 0xf2, 0xdb, 0xc4, 0x8b, 0xc1, 0x70, 0xbd, 0xe4, 0xe5, 0x6c, 0x6b, 0x16, 0xc2, + 0xf1, 0xb8, 0x9d, 0x14, 0xb3, 0x25, 0xc4, 0xb6, 0x2e, 0xe7, 0xc7, 0x63, 0x57, 0xcb, 0x36, 0xc2, 0x83, 0x87, 0xaa, + 0x85, 0xdb, 0x86, 0x55, 0xf0, 0x33, 0x99, 0xc5, 0xd8, 0xbe, 0xc6, 0x67, 0xf6, 0xe7, 0x8b, 0xee, 0x59, 0x82, 0x8c, + 0x1b, 0x1b, 0xe0, 0x1a, 0x9b, 0xb5, 0x3b, 0x5c, 0x8d, 0x80, 0xe4, 0x71, 0x37, 0xfa, 0xbb, 0xb2, 0x93, 0x9c, 0x04, + 0x09, 0xa3, 0x15, 0xc2, 0xef, 0xa1, 0xf1, 0x27, 0x5a, 0xec, 0x41, 0xbb, 0x8d, 0x2d, 0x21, 0xaa, 0x69, 0xcf, 0xe5, + 0x4a, 0xb1, 0x34, 0x6f, 0xa5, 0x0d, 0x99, 0x0f, 0xeb, 0xf3, 0xd0, 0xc8, 0x81, 0x82, 0x31, 0xe2, 0xa9, 0x75, 0x10, + 0xcd, 0xe6, 0xc0, 0x7d, 0x81, 0xe6, 0x11, 0x9e, 0x5a, 0x90, 0xa0, 0xcc, 0xda, 0xb0, 0x9f, 0x24, 0x67, 0xcb, 0xe3, + 0xf0, 0x2d, 0xfc, 0xcb, 0x67, 0xd8, 0x24, 0xa6, 0x28, 0x1e, 0x7f, 0xcb, 0x15, 0xff, 0x1d, 0x5b, 0x10, 0xc1, 0xda, + 0xad, 0xa8, 0x0d, 0x7f, 0xc3, 0xbf, 0x84, 0x7d, 0x84, 0xfd, 0x96, 0xe3, 0x4d, 0xd2, 0x10, 0x90, 0x09, 0xc4, 0x85, + 0x85, 0x20, 0xc1, 0xdf, 0x72, 0xc9, 0x3f, 0x27, 0x7c, 0xb6, 0x28, 0x81, 0xac, 0x0e, 0xa3, 0xf8, 0x84, 0x62, 0xa2, + 0x10, 0x86, 0x5b, 0x42, 0xef, 0xe8, 0xbf, 0x11, 0x25, 0xd9, 0xa4, 0xb2, 0x62, 0x3d, 0x90, 0x49, 0x12, 0x4c, 0xb0, + 0xf2, 0x42, 0xf9, 0xc2, 0xbd, 0x50, 0x6a, 0xad, 0x05, 0xad, 0x5f, 0xfe, 0x24, 0xf1, 0x0c, 0xe8, 0x1e, 0xc8, 0x18, + 0x74, 0x1b, 0x51, 0x4d, 0x72, 0x4c, 0x1f, 0xa5, 0xf3, 0x0c, 0x54, 0x40, 0x17, 0x9b, 0x2c, 0xac, 0x97, 0x45, 0xb9, + 0x6e, 0x85, 0x87, 0xca, 0xd2, 0x47, 0xea, 0x31, 0xe6, 0x85, 0x79, 0x72, 0x26, 0x1f, 0x3c, 0x02, 0x34, 0x3c, 0xca, + 0xd3, 0xaa, 0xa3, 0xb4, 0x7e, 0x60, 0x19, 0x30, 0x02, 0x67, 0xca, 0x80, 0x47, 0x58, 0x06, 0xe6, 0x69, 0x97, 0xa1, + 0x06, 0xb1, 0x46, 0xd5, 0x95, 0xda, 0x60, 0xce, 0x14, 0x25, 0x9f, 0x62, 0x69, 0x85, 0x31, 0x34, 0x75, 0xe5, 0x91, + 0xf5, 0x92, 0x13, 0xf6, 0x6c, 0x37, 0x90, 0x6e, 0x61, 0xab, 0xc0, 0x05, 0x5d, 0xcb, 0x12, 0xe5, 0xa2, 0x5b, 0x46, + 0x94, 0x89, 0x90, 0xfa, 0xd9, 0xc3, 0x99, 0x56, 0xfb, 0x8d, 0x9d, 0x74, 0x68, 0x8f, 0x14, 0xbd, 0x60, 0x20, 0x3e, + 0xed, 0x91, 0x52, 0xcf, 0x1a, 0xb9, 0x0c, 0x6c, 0xe9, 0x52, 0xd5, 0xf3, 0x5f, 0xa0, 0x7c, 0x07, 0x33, 0xe3, 0x6c, + 0xf6, 0xbb, 0xde, 0xdc, 0x9e, 0x1c, 0xea, 0xe6, 0x77, 0xd6, 0xeb, 0xc1, 0xd6, 0x20, 0x13, 0x5f, 0x28, 0xea, 0x29, + 0xab, 0x10, 0x2b, 0x32, 0xfb, 0x5f, 0xc2, 0xfb, 0x1d, 0xde, 0x1a, 0xa1, 0x59, 0x19, 0x0f, 0xf3, 0xd1, 0x93, 0x83, + 0x68, 0x7e, 0xef, 0x2c, 0xdb, 0xca, 0x55, 0xc9, 0x6c, 0xbf, 0x9f, 0x24, 0xcd, 0xd9, 0xe3, 0x35, 0x92, 0x3a, 0xc0, + 0xc7, 0xeb, 0x33, 0x7c, 0xa4, 0x12, 0x4a, 0x2d, 0xa8, 0x6a, 0xd0, 0xfa, 0xd8, 0xef, 0xad, 0xe7, 0xf4, 0xf1, 0x53, + 0x39, 0xdd, 0x92, 0x22, 0x8c, 0x1f, 0x18, 0x4c, 0xd9, 0x89, 0x53, 0x97, 0xaa, 0x19, 0xd2, 0xbb, 0x6e, 0x95, 0xd4, + 0x65, 0x8f, 0x12, 0x41, 0xa8, 0x83, 0xf5, 0x8b, 0xfd, 0x10, 0x66, 0xb6, 0xe8, 0x0f, 0x9b, 0xd5, 0x9c, 0x50, 0x10, + 0x01, 0xa2, 0x55, 0xde, 0x07, 0x4e, 0x49, 0xc2, 0xac, 0xb9, 0x21, 0xdd, 0x7a, 0x2b, 0xa5, 0xbd, 0x92, 0x02, 0xfa, + 0x65, 0x7e, 0x3c, 0xa2, 0xf9, 0xcd, 0xec, 0x5c, 0x95, 0xb4, 0xe5, 0x00, 0x22, 0x75, 0xde, 0xb4, 0x2f, 0x1d, 0x0e, + 0xfe, 0x83, 0xba, 0x12, 0xe5, 0x44, 0xd0, 0x51, 0xb4, 0x60, 0xb4, 0x5a, 0xb5, 0xab, 0xc8, 0xa6, 0x42, 0xb6, 0x24, + 0xc2, 0x89, 0x92, 0xbd, 0x12, 0xea, 0xa3, 0x5c, 0xed, 0x99, 0x86, 0xf8, 0x33, 0x01, 0x9b, 0x36, 0xf8, 0x5b, 0xe0, + 0x5e, 0x06, 0x67, 0xa6, 0x7d, 0x1a, 0x46, 0x40, 0x64, 0x0e, 0xc1, 0x7e, 0x7e, 0xd7, 0x83, 0x1c, 0x1e, 0x74, 0xa4, + 0xbf, 0xaa, 0x67, 0x05, 0x9e, 0xb9, 0x67, 0x9e, 0xbf, 0x3e, 0x93, 0x9e, 0x57, 0xf0, 0x40, 0x73, 0x1f, 0x66, 0xfc, + 0x45, 0x59, 0x86, 0xfb, 0xd1, 0xb2, 0x2c, 0xd6, 0x5e, 0xa4, 0xf7, 0xf1, 0x4c, 0x8a, 0x81, 0x44, 0x87, 0x99, 0xd1, + 0x55, 0xac, 0xe3, 0x1c, 0xc6, 0xbd, 0x3d, 0x09, 0x2b, 0xb4, 0x7f, 0x96, 0xd8, 0xeb, 0x02, 0x00, 0x1c, 0xb2, 0x06, + 0xad, 0xf0, 0x4e, 0xb7, 0xb7, 0x7b, 0x5c, 0x52, 0xa2, 0xb8, 0x51, 0xf3, 0xb3, 0x1a, 0x5a, 0x26, 0xa8, 0x65, 0xd6, + 0x9d, 0x4c, 0xa6, 0x48, 0x02, 0xdf, 0x86, 0xbd, 0x66, 0x79, 0x35, 0x6f, 0xe4, 0xf6, 0xf0, 0x2e, 0x5c, 0x8b, 0x58, + 0x5b, 0xd0, 0x49, 0x47, 0xc6, 0xe1, 0x5e, 0x68, 0x6e, 0xa4, 0x87, 0x27, 0x55, 0x12, 0x96, 0x22, 0x86, 0x5b, 0x20, + 0x3b, 0xa8, 0x6d, 0x25, 0x28, 0x81, 0x04, 0xf6, 0x43, 0x29, 0x96, 0xe9, 0x4e, 0x00, 0x98, 0x03, 0xff, 0x53, 0x1a, + 0xbb, 0xdd, 0x9d, 0x87, 0x78, 0xd5, 0xc8, 0xfb, 0x06, 0x21, 0xd8, 0x5f, 0x81, 0x9c, 0x06, 0x0c, 0x22, 0xc5, 0x48, + 0x16, 0x0c, 0x24, 0x00, 0x15, 0xdf, 0x80, 0x49, 0x6e, 0x5a, 0x79, 0x7e, 0x50, 0xe9, 0x0e, 0xa6, 0x7d, 0xd0, 0xbd, + 0xb8, 0xd6, 0x0c, 0x90, 0x7f, 0x27, 0x11, 0xff, 0x5b, 0xed, 0x95, 0xac, 0x62, 0x99, 0xdf, 0x98, 0x8b, 0x4e, 0x06, + 0x57, 0x0d, 0xe1, 0x17, 0xb3, 0x6c, 0xce, 0xa3, 0x59, 0xa6, 0x43, 0xfd, 0x8b, 0xe6, 0xa4, 0x14, 0xc0, 0x50, 0xc7, + 0x0b, 0xb0, 0xc6, 0xbb, 0xd2, 0x4d, 0x2b, 0x1e, 0x69, 0x8c, 0x51, 0x50, 0xa1, 0x83, 0xd0, 0xdf, 0x6a, 0x80, 0xd7, + 0x60, 0x92, 0x1b, 0x21, 0xf7, 0xc1, 0x05, 0xdd, 0xd0, 0x2d, 0xe7, 0x2e, 0x41, 0x4d, 0xaa, 0x96, 0x5f, 0x85, 0x50, + 0xef, 0x6a, 0xc9, 0xa5, 0xda, 0x7c, 0x6a, 0x94, 0x35, 0x82, 0x4c, 0x8e, 0xd2, 0xef, 0x53, 0x2e, 0xdc, 0xdc, 0x98, + 0xac, 0x8f, 0x47, 0xaf, 0xe0, 0xa6, 0xc6, 0x3f, 0x56, 0x92, 0x45, 0xd4, 0x1a, 0x12, 0x61, 0x6b, 0xb7, 0x42, 0xf7, + 0x1e, 0x37, 0x4a, 0xf3, 0x28, 0xdb, 0xc6, 0xa2, 0xf2, 0x7a, 0x09, 0x58, 0x8b, 0x7b, 0x40, 0x86, 0x4a, 0x4b, 0x3f, + 0x67, 0x05, 0x40, 0x06, 0x48, 0x61, 0xe3, 0x47, 0xa4, 0xbd, 0xfa, 0xe0, 0xa5, 0x7e, 0xbf, 0x6f, 0x4c, 0xf9, 0xef, + 0x1f, 0x72, 0x60, 0x26, 0x14, 0x65, 0xbd, 0x87, 0x09, 0x54, 0x79, 0xa9, 0x4f, 0xda, 0xb3, 0x9a, 0x3f, 0xdf, 0xd4, + 0x1e, 0x90, 0x5a, 0xf9, 0x16, 0x73, 0xd5, 0x2b, 0xfb, 0x62, 0x73, 0x48, 0xab, 0x5b, 0xa3, 0x71, 0x10, 0x2c, 0xad, + 0xde, 0x68, 0x95, 0x43, 0xd5, 0xf0, 0x1c, 0x44, 0x2a, 0xeb, 0xea, 0x9a, 0x3b, 0x57, 0xd7, 0x82, 0x23, 0x81, 0x6c, + 0xc9, 0x21, 0x2c, 0x8d, 0x85, 0xdc, 0x2b, 0x8f, 0xc7, 0xc2, 0xef, 0xf7, 0xd3, 0x59, 0x8e, 0x97, 0x16, 0xa0, 0x4c, + 0xdb, 0xd4, 0x5e, 0xe8, 0x1f, 0x8f, 0x3f, 0x82, 0xd7, 0x88, 0x7f, 0x3c, 0x96, 0xfd, 0xfe, 0x47, 0x73, 0x93, 0xb9, + 0x1c, 0x2b, 0xa5, 0xec, 0x35, 0x2a, 0xdd, 0xdf, 0x24, 0xbc, 0xf7, 0xbf, 0x47, 0xff, 0x7b, 0x74, 0xdd, 0x53, 0x01, + 0x60, 0x09, 0x9f, 0xe1, 0x0d, 0x9d, 0xa9, 0xcb, 0x39, 0x93, 0xee, 0xee, 0xca, 0x0f, 0xbd, 0xa7, 0xf1, 0xe1, 0x7b, + 0x73, 0xd3, 0xc6, 0x5f, 0xf3, 0x13, 0x4d, 0x42, 0xc7, 0x45, 0xff, 0x78, 0xfc, 0x94, 0x68, 0x7d, 0x5a, 0xaa, 0xf4, + 0x69, 0x0a, 0x3c, 0xc9, 0xb0, 0xe1, 0xba, 0x85, 0xe9, 0x68, 0x7e, 0xdc, 0x7c, 0x95, 0xbc, 0x38, 0x4b, 0xe1, 0xda, + 0x0b, 0x3a, 0x97, 0x29, 0x58, 0x57, 0x86, 0xe0, 0x66, 0x10, 0x40, 0xea, 0x10, 0xd2, 0xac, 0x69, 0xf8, 0x97, 0xdc, + 0x15, 0xbc, 0xb5, 0xc7, 0xbb, 0xc1, 0x88, 0x52, 0x47, 0xfa, 0xa4, 0x0d, 0xa1, 0x4b, 0x2a, 0xf9, 0x8f, 0x22, 0x8f, + 0x31, 0x66, 0xe3, 0x15, 0x91, 0x7d, 0x16, 0xf9, 0xcb, 0x02, 0x00, 0x8b, 0x00, 0x01, 0x39, 0x9d, 0x3b, 0x92, 0xf8, + 0xcf, 0xc9, 0xb7, 0x7f, 0x4c, 0x97, 0xf6, 0xa1, 0x2c, 0x56, 0xa5, 0xa8, 0xaa, 0x93, 0xd2, 0xf6, 0xb6, 0x5c, 0x0f, + 0xf4, 0xa1, 0xfd, 0xbe, 0xa4, 0x0f, 0x4d, 0x31, 0x14, 0x05, 0x6e, 0x8d, 0xbd, 0x69, 0xca, 0x15, 0x4d, 0xf5, 0xc8, + 0x58, 0x3f, 0x7f, 0xd8, 0xbf, 0x89, 0xbd, 0xd4, 0x0f, 0x52, 0x10, 0x84, 0x35, 0x7e, 0x52, 0x8a, 0x24, 0x70, 0x3e, + 0xc3, 0x54, 0xe2, 0xd3, 0xa5, 0x54, 0xf9, 0xc3, 0x48, 0xf3, 0x61, 0x0a, 0x7a, 0xd9, 0xbf, 0xe7, 0x30, 0xff, 0x75, + 0x7b, 0xb0, 0x3e, 0xad, 0xcb, 0x34, 0xaa, 0x88, 0x2a, 0x2f, 0x4c, 0xb5, 0x09, 0x44, 0xf0, 0xe7, 0xc2, 0x22, 0xf9, + 0xf5, 0xc9, 0x91, 0xa0, 0x31, 0x93, 0xe5, 0xe3, 0x89, 0xfb, 0x85, 0x7d, 0xe5, 0x3a, 0x9d, 0xff, 0x95, 0x99, 0xff, + 0x03, 0x8c, 0x49, 0x28, 0x9e, 0x73, 0xcb, 0x60, 0x81, 0xb3, 0x5f, 0xba, 0x7a, 0xc0, 0xdf, 0xcc, 0x13, 0xcf, 0x81, + 0x83, 0xf9, 0x39, 0xba, 0x2a, 0xa6, 0xb3, 0x62, 0x00, 0x04, 0xb6, 0x7e, 0x63, 0xcd, 0x89, 0xd7, 0x16, 0xcf, 0x95, + 0x5c, 0x10, 0xfa, 0xba, 0x0a, 0xb3, 0x71, 0x5d, 0x6c, 0x2b, 0x51, 0x6c, 0x21, 0x6c, 0x04, 0xd4, 0xb2, 0xd5, 0xb4, + 0xb6, 0x15, 0xb2, 0x3f, 0x89, 0x16, 0x6d, 0x97, 0xa1, 0x9a, 0x8c, 0xb2, 0x74, 0x33, 0x05, 0x52, 0xbd, 0x00, 0xce, + 0x22, 0xf3, 0xca, 0x3b, 0x67, 0x0f, 0xd8, 0xa1, 0xf1, 0x14, 0x18, 0x51, 0xe9, 0x8f, 0xaa, 0x31, 0x3a, 0x3d, 0xd1, + 0xef, 0x57, 0x53, 0x0a, 0xf9, 0xfa, 0x09, 0x30, 0xb9, 0x6a, 0xb9, 0x00, 0x7d, 0x19, 0xea, 0xa0, 0x12, 0xa5, 0x56, + 0x0c, 0x23, 0x16, 0x7e, 0x12, 0xc8, 0xde, 0x4c, 0x41, 0xcd, 0x2a, 0x4a, 0x42, 0x25, 0x2a, 0x25, 0x5b, 0x13, 0xd4, + 0xd2, 0xfb, 0xa2, 0xa8, 0x0f, 0x15, 0x38, 0x4a, 0x46, 0xda, 0x2c, 0xa7, 0xcc, 0xb8, 0x28, 0x73, 0xd1, 0x0f, 0xf6, + 0x2f, 0xc0, 0xf8, 0x92, 0xf9, 0x2c, 0xf7, 0x1d, 0x9d, 0xd3, 0x76, 0x5c, 0xa0, 0xcc, 0x2d, 0xa7, 0xad, 0x96, 0x3c, + 0x26, 0xef, 0x59, 0xb0, 0xed, 0xbf, 0x48, 0x90, 0x57, 0x11, 0xe6, 0x13, 0xaa, 0x6c, 0xfe, 0x9e, 0x7b, 0xc4, 0x3e, + 0xda, 0xe1, 0xc2, 0x44, 0xa4, 0xb7, 0x60, 0x49, 0x0c, 0xb3, 0x52, 0x84, 0xf1, 0x1e, 0xbc, 0x7f, 0xb6, 0x95, 0x18, + 0x5d, 0xa0, 0x93, 0xfb, 0xc5, 0x43, 0x5a, 0x27, 0x17, 0x6f, 0x5e, 0x5d, 0x7c, 0xdf, 0x1b, 0x14, 0xa3, 0x34, 0x1e, + 0xf4, 0xbe, 0xbf, 0x58, 0x6f, 0x01, 0x22, 0x53, 0x5c, 0xc4, 0x64, 0x4a, 0x13, 0xf1, 0x05, 0x19, 0x06, 0x2f, 0xea, + 0x44, 0x5c, 0xd0, 0xc4, 0x74, 0x5f, 0xa3, 0x34, 0xf9, 0x76, 0x14, 0xe6, 0xf0, 0x72, 0x29, 0xb6, 0x95, 0x88, 0xc1, + 0x4e, 0xa9, 0xe6, 0xd9, 0xc9, 0x59, 0x2c, 0x3d, 0x06, 0x5d, 0x59, 0xa5, 0x03, 0x74, 0x7b, 0x22, 0xed, 0xaa, 0x74, + 0x05, 0x84, 0x1e, 0xf0, 0xcc, 0xcf, 0xe3, 0x51, 0x24, 0x10, 0x6a, 0xc1, 0x9c, 0x4c, 0x23, 0xba, 0x21, 0xbd, 0xc4, + 0x3e, 0x03, 0xb3, 0x90, 0xd2, 0x3c, 0xb8, 0xb9, 0x5a, 0xb4, 0xdc, 0x39, 0x2b, 0x47, 0x61, 0xb5, 0x11, 0x51, 0x8d, + 0x54, 0xc7, 0xe0, 0xbc, 0x03, 0x11, 0x00, 0x8a, 0x11, 0x3c, 0xe3, 0x51, 0xbf, 0x1f, 0xa9, 0xa0, 0x9c, 0x84, 0x7e, + 0x51, 0xe8, 0x97, 0xc6, 0xa0, 0x8c, 0xf9, 0x97, 0x50, 0x13, 0x03, 0xd4, 0x3b, 0x1e, 0x2a, 0x8e, 0x00, 0x5c, 0xce, + 0x11, 0x33, 0xce, 0x7b, 0xdc, 0x45, 0xe3, 0x54, 0xbc, 0x13, 0xea, 0x3a, 0x58, 0x2a, 0xd4, 0x79, 0x53, 0x1f, 0xe9, + 0x39, 0x69, 0x12, 0x34, 0x88, 0x1b, 0x78, 0xbc, 0x1a, 0x02, 0xaa, 0xb5, 0x90, 0x7a, 0x0b, 0x9d, 0x52, 0xd5, 0x21, + 0xb0, 0x06, 0xb8, 0x44, 0x61, 0x3b, 0x61, 0x72, 0x44, 0xdb, 0xb2, 0x14, 0xf9, 0x09, 0x1b, 0xb4, 0x4b, 0x46, 0xa6, + 0x0e, 0x2e, 0x97, 0xcb, 0x89, 0xa8, 0x7f, 0xcd, 0xb7, 0x00, 0xce, 0x0b, 0xf9, 0xad, 0xdd, 0x6c, 0x99, 0x64, 0xbb, + 0xfe, 0x3f, 0xbc, 0x7d, 0x09, 0x77, 0xdb, 0x46, 0xb2, 0xee, 0x5f, 0x11, 0xf1, 0x1c, 0x06, 0x6d, 0x36, 0x29, 0x52, + 0xb1, 0x33, 0x09, 0xa8, 0x16, 0xaf, 0xe2, 0x25, 0x71, 0x26, 0x5e, 0x62, 0x39, 0x99, 0xcc, 0xf0, 0xf1, 0x2a, 0x10, + 0xd0, 0x12, 0x11, 0x43, 0x68, 0x06, 0x00, 0xb5, 0x84, 0xc4, 0x7f, 0x7f, 0xa7, 0xaa, 0x77, 0x10, 0x94, 0x3d, 0xf3, + 0xee, 0x7b, 0xc7, 0xe7, 0x58, 0x44, 0xa3, 0xd1, 0x7b, 0x57, 0x57, 0xd7, 0xf2, 0x55, 0x65, 0x63, 0x96, 0x94, 0xbc, + 0x5a, 0x89, 0xa2, 0xca, 0x6e, 0xf8, 0x4f, 0xe6, 0xa5, 0x1f, 0x40, 0x0a, 0xed, 0x48, 0x5f, 0xb7, 0xbb, 0xa3, 0xc4, + 0x38, 0xa6, 0x1c, 0xd7, 0x52, 0xe9, 0x5e, 0x8d, 0xaa, 0x13, 0x37, 0x5b, 0xe5, 0x5a, 0x66, 0x69, 0xca, 0x8b, 0x57, + 0x45, 0x9a, 0x25, 0x4e, 0x72, 0xac, 0x02, 0x54, 0xdb, 0xc8, 0x57, 0x36, 0x36, 0xf2, 0xf3, 0xac, 0xc2, 0x80, 0xc1, + 0x5e, 0xa3, 0x5a, 0xa1, 0xa6, 0x74, 0xe0, 0x0b, 0xf1, 0x1e, 0x23, 0x6e, 0xb3, 0x22, 0x01, 0x86, 0x1f, 0x13, 0xd5, + 0x25, 0x3d, 0x85, 0x28, 0x0f, 0x32, 0x1e, 0xf7, 0x73, 0x8e, 0x88, 0xd7, 0x46, 0x65, 0x0e, 0x4c, 0xb6, 0x52, 0x41, + 0x22, 0xd8, 0x5d, 0x36, 0x57, 0x8b, 0x68, 0x21, 0xef, 0x42, 0xbd, 0x78, 0xbb, 0xed, 0x25, 0x92, 0x0e, 0x58, 0xf9, + 0x69, 0xf0, 0x32, 0xce, 0x72, 0x9e, 0x1e, 0xd4, 0xe2, 0x40, 0x6e, 0xa8, 0x03, 0xe9, 0xcc, 0x01, 0x3b, 0xef, 0xcb, + 0xfa, 0x40, 0xad, 0xe9, 0x03, 0xd5, 0xce, 0x03, 0xb8, 0x60, 0xe0, 0xce, 0xbd, 0xca, 0x6e, 0x78, 0x71, 0x00, 0xca, + 0x40, 0x63, 0x3c, 0xd0, 0x54, 0xf5, 0x48, 0x4e, 0x8c, 0x0a, 0x5c, 0x9d, 0xa8, 0x83, 0x39, 0xa0, 0xdf, 0x03, 0x52, + 0x54, 0xeb, 0xed, 0x4a, 0x1d, 0xb4, 0x01, 0xfd, 0x69, 0xa9, 0xfb, 0xa0, 0xa2, 0xc5, 0xcb, 0x90, 0xc0, 0xde, 0x90, + 0x2a, 0xa4, 0x56, 0x2d, 0xab, 0x40, 0xf1, 0x86, 0xb3, 0x78, 0x77, 0xae, 0x25, 0x1b, 0xe7, 0x25, 0x02, 0x7b, 0x65, + 0x45, 0x1d, 0x67, 0xc5, 0xa9, 0x93, 0xca, 0x1b, 0xe5, 0x24, 0x53, 0x69, 0xdf, 0xb2, 0x82, 0xba, 0x3b, 0x44, 0xdf, + 0x22, 0xf5, 0x61, 0xf0, 0x22, 0xac, 0xc9, 0x8c, 0xf7, 0xfb, 0x62, 0x26, 0xa2, 0x62, 0x56, 0x1d, 0x16, 0x91, 0x44, + 0x68, 0xdb, 0x27, 0x02, 0x7a, 0x50, 0x02, 0xe4, 0x0a, 0x80, 0xea, 0x87, 0x84, 0x3f, 0x0f, 0x49, 0x7d, 0x3a, 0x85, + 0x3e, 0xa5, 0xb2, 0x5e, 0x71, 0x0a, 0xaa, 0x1b, 0x6f, 0x64, 0xbd, 0x0a, 0x5a, 0x3c, 0x96, 0x63, 0xb5, 0xa1, 0x6d, + 0x4e, 0x8d, 0x77, 0xbd, 0xde, 0x60, 0xd2, 0xe6, 0x42, 0xae, 0xc2, 0x90, 0x44, 0xb7, 0x85, 0x13, 0x3e, 0xc4, 0x60, + 0x65, 0xb5, 0x36, 0xbf, 0x8e, 0xfd, 0x91, 0x15, 0x29, 0xee, 0x67, 0x43, 0x9c, 0xbb, 0x78, 0x3c, 0xa7, 0xfa, 0x46, + 0x49, 0x8b, 0x74, 0x9b, 0xef, 0xd5, 0x65, 0x48, 0x51, 0x51, 0x4d, 0x1a, 0x55, 0x66, 0xd0, 0x7d, 0xdb, 0xbc, 0x55, + 0x3d, 0xc2, 0x04, 0x78, 0xa5, 0x32, 0xa8, 0x46, 0xe3, 0x81, 0x58, 0xd5, 0xa3, 0x72, 0x5d, 0x14, 0x88, 0x36, 0x0c, + 0x39, 0x66, 0x86, 0x90, 0x64, 0x7f, 0xf1, 0xef, 0x64, 0x70, 0x85, 0x32, 0xbe, 0xd5, 0x70, 0xde, 0xb5, 0xf1, 0xec, + 0x6e, 0x22, 0x37, 0x27, 0x16, 0xd6, 0xb8, 0x0f, 0xfe, 0x51, 0xab, 0x9d, 0x05, 0x94, 0x35, 0xad, 0x6a, 0x38, 0xdc, + 0xa3, 0x3a, 0x16, 0xa5, 0x06, 0x24, 0x76, 0xc8, 0x72, 0xd9, 0x3a, 0x66, 0xd0, 0x80, 0xfe, 0x2e, 0xbb, 0x5e, 0x5f, + 0x23, 0x6a, 0x5b, 0x81, 0xac, 0x93, 0x90, 0xfe, 0x25, 0xed, 0x51, 0x57, 0xf6, 0x54, 0xee, 0xb7, 0x6d, 0xaa, 0x1c, + 0x1a, 0x20, 0x79, 0xec, 0xe6, 0x2c, 0x90, 0x1d, 0x09, 0xa2, 0x40, 0x6e, 0xbd, 0x60, 0xea, 0x9c, 0x32, 0x65, 0x07, + 0xf2, 0x73, 0xa9, 0xcf, 0xb0, 0xcf, 0x38, 0x62, 0xf4, 0x52, 0x89, 0xc1, 0xd4, 0x47, 0x1b, 0xd5, 0xb4, 0x56, 0x80, + 0xaa, 0x9f, 0x6e, 0xe0, 0x4f, 0x54, 0x36, 0x68, 0xa8, 0x35, 0x12, 0x85, 0xa4, 0x89, 0x12, 0x3a, 0x96, 0x96, 0x2a, + 0x98, 0x42, 0x27, 0x91, 0x30, 0x04, 0x34, 0x4c, 0x88, 0x4a, 0x2a, 0xf1, 0xd6, 0x00, 0xce, 0x7c, 0xbc, 0xa8, 0xd6, + 0xa5, 0x32, 0x98, 0xfb, 0x21, 0xbe, 0xe1, 0xaf, 0x9e, 0x5b, 0xa3, 0xfa, 0x96, 0xb5, 0xbe, 0xa3, 0x05, 0xf9, 0x21, + 0xe4, 0x14, 0x1d, 0x98, 0xd8, 0xc9, 0x06, 0x0f, 0xe6, 0xa2, 0x51, 0xa1, 0x2e, 0xde, 0xaa, 0xf8, 0x2b, 0xca, 0x04, + 0xef, 0x01, 0x4f, 0x11, 0x65, 0x78, 0x58, 0x69, 0xab, 0x6a, 0x7c, 0x2a, 0x58, 0x4b, 0x0f, 0x56, 0xf2, 0x74, 0x9d, + 0xf0, 0x10, 0xf4, 0x48, 0x84, 0x9d, 0x84, 0xe5, 0x3c, 0x5e, 0xc0, 0x71, 0x52, 0x12, 0x50, 0x3b, 0xa8, 0x2b, 0xf8, + 0x7c, 0x81, 0xee, 0xaf, 0x02, 0x3d, 0xc0, 0xd0, 0x82, 0xd8, 0x0f, 0x7d, 0x3a, 0xba, 0x8e, 0x57, 0x9e, 0x8a, 0x84, + 0xcf, 0x4b, 0xb0, 0x1d, 0x92, 0xea, 0x29, 0xd0, 0x42, 0x25, 0x52, 0x3f, 0x0c, 0x7c, 0x87, 0x02, 0xbe, 0x56, 0x3a, + 0x40, 0x4d, 0x3f, 0x63, 0x9a, 0x1a, 0x67, 0xa8, 0x7c, 0xe6, 0xdc, 0x33, 0xa3, 0xe5, 0xcc, 0x80, 0x31, 0xa8, 0xdb, + 0x68, 0x8a, 0xe2, 0x9c, 0x7c, 0x16, 0x94, 0x71, 0x9a, 0xc5, 0x39, 0xf8, 0x6d, 0xc6, 0x25, 0x66, 0x4c, 0xe2, 0x9a, + 0x5f, 0x89, 0x12, 0xb4, 0xdd, 0xb9, 0x4c, 0x6d, 0x1a, 0x10, 0x90, 0xfd, 0x00, 0x56, 0x2f, 0x9e, 0x8e, 0xca, 0x7a, + 0x77, 0x29, 0x53, 0x88, 0xb2, 0x0a, 0xc1, 0xa6, 0x99, 0x2e, 0xd9, 0x69, 0x28, 0xb5, 0x39, 0x10, 0xdf, 0x08, 0x8d, + 0xfb, 0xa7, 0x61, 0x6c, 0x34, 0xc5, 0xc6, 0xee, 0x6d, 0xbb, 0xfd, 0xad, 0x70, 0xd2, 0x69, 0x4e, 0x7a, 0x8c, 0xfd, + 0x56, 0x84, 0xe5, 0xc8, 0x74, 0x84, 0xc0, 0x92, 0x73, 0x3e, 0x75, 0x5f, 0xd1, 0x62, 0x9e, 0x80, 0xe9, 0x88, 0x8a, + 0x90, 0x0b, 0x94, 0x1d, 0xa3, 0xb8, 0x03, 0x83, 0x0b, 0x66, 0x42, 0x10, 0x4b, 0x4f, 0x5d, 0x48, 0x96, 0x24, 0x65, + 0xf0, 0x3c, 0x75, 0x30, 0xe0, 0xd7, 0x4c, 0x9a, 0xbb, 0x48, 0xeb, 0xd3, 0x25, 0x99, 0xa6, 0xc8, 0x40, 0xac, 0xc3, + 0x4d, 0x96, 0x46, 0x89, 0x14, 0x91, 0x2d, 0xd1, 0x3f, 0x52, 0x53, 0x2c, 0x15, 0xae, 0x17, 0xa9, 0x12, 0xa1, 0xd5, + 0x3c, 0xc5, 0x93, 0x3a, 0x6d, 0xd2, 0x11, 0xc6, 0x9b, 0x04, 0xa5, 0x5c, 0x03, 0x03, 0x55, 0x50, 0xb5, 0x14, 0x36, + 0xe5, 0x76, 0xab, 0x2e, 0x56, 0xd5, 0x3c, 0x5e, 0xe0, 0xcb, 0x0a, 0x47, 0xf1, 0xef, 0xdc, 0x89, 0x35, 0x25, 0xb7, + 0x07, 0x35, 0x23, 0x4a, 0xe8, 0xdf, 0x39, 0x5c, 0x24, 0xbe, 0x13, 0x2a, 0xee, 0x1f, 0x5a, 0x84, 0x9c, 0xcb, 0x83, + 0x54, 0x73, 0x43, 0x3b, 0xc2, 0x7f, 0xcd, 0xf5, 0x69, 0x67, 0x74, 0x5f, 0xcd, 0xa8, 0xf0, 0x7b, 0x1d, 0x3c, 0x63, + 0xd4, 0x67, 0x03, 0x87, 0x15, 0xa2, 0xd0, 0x86, 0x9d, 0x14, 0x52, 0xb4, 0x30, 0x14, 0xf2, 0x2f, 0xa1, 0xd5, 0x09, + 0xb7, 0x66, 0x94, 0x05, 0xe3, 0xd3, 0xe2, 0xb8, 0x9a, 0x0e, 0x06, 0x05, 0xa9, 0xb5, 0x85, 0x1e, 0x5c, 0x0f, 0x1c, + 0xff, 0x1e, 0xb8, 0x85, 0x38, 0x70, 0xc8, 0xd5, 0x90, 0x6b, 0x70, 0xfc, 0x16, 0x27, 0x57, 0x8f, 0x2a, 0x19, 0xbc, + 0x9a, 0xc8, 0x16, 0xfc, 0xbd, 0x08, 0x03, 0xf4, 0x49, 0x0a, 0xc0, 0x64, 0x30, 0xe5, 0x77, 0x20, 0x51, 0x3a, 0x97, + 0x37, 0xa4, 0x5f, 0x8a, 0x92, 0x5f, 0xf2, 0x92, 0x17, 0x89, 0x2d, 0xc0, 0xf0, 0x0e, 0xa6, 0xd7, 0x51, 0x4d, 0x25, + 0x10, 0xaf, 0xee, 0x71, 0xc4, 0xb5, 0xf7, 0x9f, 0xee, 0xb1, 0x01, 0x6a, 0x35, 0x8e, 0x0d, 0x2e, 0x73, 0x0c, 0x2e, + 0xe8, 0x4a, 0x62, 0xab, 0xa9, 0x86, 0x11, 0x81, 0x81, 0x0b, 0x38, 0x08, 0x4b, 0x24, 0xc7, 0x56, 0xf1, 0x9a, 0x78, + 0x52, 0xda, 0x07, 0x86, 0xa3, 0x4d, 0x72, 0x5c, 0x9b, 0x65, 0x3b, 0x81, 0xf3, 0x45, 0xe7, 0xa4, 0xe9, 0x58, 0x36, + 0x78, 0x9f, 0xd7, 0xe7, 0xd7, 0xfe, 0x21, 0xa1, 0x32, 0xd8, 0x0d, 0x6f, 0x07, 0xbb, 0xb1, 0xc2, 0xaf, 0x79, 0xb5, + 0x50, 0xf1, 0x59, 0xf4, 0x25, 0xcb, 0x6d, 0xad, 0x73, 0x4b, 0x12, 0x4a, 0x01, 0xed, 0xb2, 0x2c, 0xa8, 0x89, 0x00, + 0x74, 0x3f, 0xfc, 0x05, 0x42, 0x67, 0xf8, 0xdb, 0x63, 0x70, 0x45, 0x0a, 0xf7, 0x0e, 0x41, 0x65, 0x4c, 0x37, 0x77, + 0x6a, 0x03, 0xbe, 0x18, 0xf7, 0x67, 0x4c, 0x1d, 0xfd, 0x36, 0x13, 0xbb, 0xba, 0x6e, 0x87, 0x2c, 0xc3, 0x47, 0xb8, + 0x52, 0x00, 0x2c, 0x13, 0xfe, 0x62, 0x6c, 0x49, 0xf9, 0x09, 0xc0, 0xa9, 0xa9, 0x88, 0x3e, 0x41, 0xa0, 0xe1, 0x94, + 0x68, 0x39, 0xba, 0x91, 0x8e, 0x68, 0x1a, 0x69, 0x4d, 0xb5, 0x42, 0x7b, 0xeb, 0x61, 0x91, 0xd6, 0x34, 0x9c, 0xb8, + 0x0f, 0x8a, 0x79, 0x95, 0x40, 0x00, 0xad, 0x8c, 0xe0, 0xad, 0xf5, 0x51, 0x1f, 0x21, 0x2e, 0x61, 0x49, 0x14, 0x61, + 0x71, 0x4c, 0xf1, 0x63, 0x42, 0x37, 0xbe, 0xb6, 0xe9, 0x03, 0xd2, 0x5f, 0x5c, 0xb3, 0x6e, 0xca, 0xb2, 0x71, 0xed, + 0xa1, 0xe2, 0xc5, 0xd4, 0x0f, 0x7e, 0x98, 0xc8, 0x62, 0xdc, 0x2f, 0x6a, 0x57, 0x6a, 0x05, 0x30, 0xcc, 0x5d, 0xf5, + 0xf4, 0xfb, 0x7e, 0xb6, 0x1c, 0x08, 0x95, 0xdb, 0x19, 0x24, 0x7d, 0x2a, 0x9e, 0x1f, 0x1c, 0xd1, 0xca, 0x42, 0xcf, + 0x1d, 0x97, 0xc6, 0x87, 0xca, 0x6b, 0x53, 0x23, 0x5a, 0x23, 0x43, 0x65, 0xea, 0x80, 0xf5, 0xfd, 0x43, 0xb8, 0xbb, + 0xa8, 0x69, 0xa8, 0x74, 0xcf, 0x5d, 0x8b, 0x82, 0x13, 0x77, 0x80, 0xb1, 0xb8, 0x90, 0x34, 0x2a, 0x08, 0x93, 0x7a, + 0x34, 0x38, 0xc9, 0x5e, 0x5d, 0x9d, 0x9c, 0x29, 0xe6, 0x09, 0x6c, 0x54, 0xcb, 0xb6, 0xbf, 0xa2, 0x54, 0x97, 0x72, + 0x73, 0x45, 0xf1, 0x3d, 0xa4, 0xcd, 0x55, 0x9c, 0xb7, 0x05, 0x17, 0xfc, 0x33, 0x05, 0x17, 0xc6, 0xc1, 0xba, 0xe3, + 0x4e, 0xd9, 0x73, 0x45, 0x99, 0xc6, 0x06, 0x77, 0xed, 0x31, 0x26, 0xda, 0x7e, 0x77, 0xc9, 0x93, 0x8f, 0xc8, 0x82, + 0x7f, 0x97, 0x15, 0xe0, 0x99, 0x6c, 0x5f, 0xc9, 0xfc, 0x3f, 0xb8, 0x57, 0x5b, 0xf3, 0xce, 0x98, 0x7f, 0x3a, 0xd6, + 0xc3, 0x9d, 0xc3, 0xe4, 0x06, 0xe8, 0x0c, 0xe8, 0xe6, 0x5a, 0xa4, 0x1c, 0x90, 0x01, 0x8c, 0x45, 0x32, 0x1a, 0xf0, + 0xa1, 0x95, 0x65, 0xdb, 0x77, 0x5a, 0x5e, 0x10, 0xf6, 0x12, 0xb8, 0xe9, 0xfe, 0xda, 0xf4, 0xcc, 0xa9, 0x5a, 0x89, + 0xa2, 0x4b, 0x63, 0x63, 0x59, 0x2a, 0x81, 0xdd, 0xf7, 0x9e, 0x64, 0xd3, 0xfc, 0x78, 0x39, 0xcd, 0x0d, 0x75, 0xdb, + 0xd8, 0x65, 0x03, 0x40, 0x88, 0x5d, 0x6b, 0x2b, 0x07, 0x90, 0xdc, 0x1e, 0x84, 0xf0, 0x35, 0x22, 0xf4, 0x54, 0x8a, + 0xd0, 0xa7, 0xa9, 0xdf, 0x07, 0xb3, 0xaa, 0xd6, 0x5e, 0x9c, 0xa3, 0x41, 0xaa, 0x18, 0xf9, 0xb7, 0x37, 0xbc, 0xbc, + 0xcc, 0xc5, 0x2d, 0x60, 0x20, 0x93, 0x46, 0x2b, 0x2c, 0xaf, 0xc1, 0x9d, 0x1f, 0x1d, 0xc7, 0x19, 0xc0, 0x26, 0x41, + 0xb0, 0x56, 0x84, 0x47, 0x56, 0x89, 0x33, 0x00, 0x41, 0x76, 0x27, 0x4d, 0xc5, 0x73, 0x2d, 0x31, 0xa6, 0x2f, 0x70, + 0x57, 0x39, 0x3b, 0xd9, 0xe4, 0x66, 0xd1, 0xfb, 0x33, 0xac, 0x3a, 0x52, 0x19, 0x1b, 0x8b, 0xae, 0x3b, 0x58, 0x6b, + 0x06, 0x4d, 0x11, 0x52, 0x3e, 0x64, 0x4f, 0xda, 0xbf, 0x02, 0x1a, 0x9c, 0x67, 0xe9, 0x9d, 0xb1, 0xca, 0xdf, 0x28, + 0x21, 0x4e, 0x14, 0x53, 0x2b, 0xbe, 0x89, 0x12, 0x75, 0x7e, 0x26, 0xda, 0x0d, 0x04, 0x52, 0x7f, 0xc0, 0xa0, 0x1a, + 0x65, 0x98, 0xc0, 0x75, 0x20, 0x8a, 0xcd, 0x89, 0xea, 0x2d, 0x47, 0xd0, 0x09, 0x21, 0xde, 0x01, 0x50, 0xef, 0xd8, + 0xfa, 0x08, 0x68, 0x96, 0xbe, 0x69, 0xad, 0x73, 0x4d, 0x28, 0xb4, 0x13, 0x98, 0x64, 0x90, 0xe4, 0x59, 0x67, 0x98, + 0xa0, 0xda, 0x8c, 0x49, 0xe7, 0x7d, 0x80, 0xee, 0xae, 0x45, 0x5d, 0x7c, 0xd3, 0xb9, 0x83, 0xf6, 0x71, 0xfd, 0x4a, + 0x8b, 0xec, 0xf1, 0xe7, 0x2d, 0x11, 0x16, 0x81, 0xb3, 0x56, 0xe7, 0xab, 0x47, 0x38, 0x30, 0x15, 0x99, 0x86, 0xbd, + 0x44, 0x2a, 0x59, 0xb6, 0xdb, 0x5e, 0x6f, 0xaf, 0x88, 0xab, 0xc7, 0x58, 0xed, 0xdc, 0xcc, 0xcd, 0x9d, 0x6a, 0x5d, + 0xec, 0xde, 0xb4, 0xdd, 0x14, 0x33, 0x6a, 0xad, 0xdd, 0xae, 0x39, 0x21, 0x4f, 0xbe, 0x15, 0xd5, 0x4a, 0x9d, 0xae, + 0x0d, 0xda, 0x21, 0x9e, 0x75, 0x91, 0xc1, 0x8d, 0xf2, 0xb9, 0x15, 0x3a, 0xc9, 0x38, 0xab, 0x56, 0x5d, 0xb0, 0xb9, + 0xe6, 0xf5, 0x52, 0xa4, 0x51, 0x45, 0xd1, 0xe6, 0x3c, 0x2a, 0x68, 0x22, 0xd6, 0x45, 0x1d, 0x89, 0x06, 0xf5, 0xa2, + 0x46, 0x63, 0x80, 0x80, 0x4c, 0xe7, 0xbe, 0x07, 0x55, 0x30, 0x1b, 0x8a, 0x48, 0x4c, 0xdf, 0x83, 0xa5, 0x7d, 0x01, + 0xfb, 0xa2, 0xd9, 0x57, 0x67, 0x8b, 0x6f, 0x75, 0x84, 0x60, 0x12, 0xb3, 0x07, 0xc2, 0xc0, 0xf9, 0xc6, 0x90, 0xd3, + 0x2e, 0x71, 0x99, 0xef, 0x96, 0xb0, 0x87, 0xdb, 0x15, 0xec, 0xc4, 0xce, 0x93, 0xe2, 0xe6, 0x4a, 0x76, 0x52, 0xce, + 0xc7, 0xa0, 0xfd, 0x12, 0xf2, 0xda, 0xa5, 0xb8, 0xf5, 0x78, 0x10, 0xd0, 0x60, 0x50, 0x6a, 0xfe, 0x75, 0xa2, 0x3d, + 0x3c, 0x69, 0x40, 0x90, 0x94, 0x83, 0x8b, 0xb6, 0x63, 0xf8, 0x3e, 0x99, 0x8a, 0x63, 0x8e, 0x16, 0xef, 0xd0, 0xea, + 0x04, 0xa2, 0x78, 0x81, 0xbd, 0x9b, 0x56, 0x15, 0x6a, 0x11, 0x94, 0xa3, 0xe5, 0x2f, 0x64, 0x75, 0x08, 0x28, 0xa4, + 0x7c, 0x45, 0xa1, 0x6c, 0x9d, 0x18, 0xea, 0xe1, 0x17, 0xf3, 0xc9, 0x42, 0xcd, 0xc0, 0x40, 0xcc, 0x8f, 0x16, 0x6a, + 0x16, 0x06, 0x62, 0xfe, 0xd5, 0xa2, 0xb1, 0xeb, 0x40, 0x11, 0x10, 0xc7, 0x85, 0xa3, 0x93, 0xd2, 0xca, 0x6c, 0x01, + 0xdd, 0x3c, 0x44, 0xd0, 0xff, 0x6e, 0x0e, 0x41, 0x2b, 0x17, 0xda, 0x91, 0x1b, 0xd0, 0x76, 0x1c, 0x02, 0x73, 0xc5, + 0xa4, 0x95, 0x0e, 0x40, 0x74, 0xcc, 0xc6, 0x60, 0x88, 0x2d, 0x3f, 0x38, 0x66, 0xe3, 0xa9, 0x4b, 0x82, 0x80, 0xd1, + 0xfd, 0x41, 0x43, 0x82, 0xdf, 0xe1, 0x55, 0xfa, 0x64, 0x83, 0xbe, 0x66, 0xce, 0xdd, 0xd0, 0xb9, 0xb8, 0x82, 0x53, + 0xb5, 0xbd, 0x27, 0xa1, 0x9b, 0x4c, 0x3b, 0x40, 0xaf, 0x26, 0x6e, 0xc8, 0xaf, 0x8c, 0x46, 0xa3, 0x62, 0x64, 0x00, + 0x20, 0x88, 0xe6, 0x1c, 0xfc, 0x9c, 0x86, 0xcb, 0x97, 0xb7, 0x9e, 0x4d, 0x31, 0x02, 0x5a, 0xc8, 0x44, 0xf3, 0x00, + 0x65, 0x55, 0x63, 0x68, 0x86, 0xde, 0x21, 0xc7, 0x0f, 0x0f, 0xbe, 0xce, 0xf8, 0x89, 0xc3, 0xb5, 0x87, 0x73, 0xe1, + 0xba, 0xac, 0x69, 0x99, 0x43, 0xe7, 0xd9, 0xc7, 0xf1, 0x1e, 0xc6, 0xc9, 0xa7, 0x59, 0x28, 0x67, 0xbc, 0xa6, 0xff, + 0x51, 0xe9, 0x7e, 0x87, 0x43, 0x4e, 0x57, 0xb0, 0xe2, 0x66, 0x75, 0xa8, 0xf9, 0x59, 0xe4, 0x8d, 0x23, 0xde, 0x90, + 0xa8, 0xee, 0x3e, 0xef, 0x4d, 0x98, 0xd2, 0x8e, 0x71, 0x00, 0x70, 0xa2, 0x56, 0x0d, 0xbb, 0xd2, 0xb8, 0x56, 0x07, + 0x31, 0x0c, 0x25, 0x6c, 0x95, 0x38, 0xaa, 0xa4, 0xbf, 0x01, 0x08, 0x8b, 0xa1, 0x38, 0xde, 0x1a, 0xd6, 0x07, 0xd8, + 0x0f, 0x55, 0xa0, 0x6e, 0x4e, 0x21, 0x67, 0x00, 0x90, 0x04, 0xdc, 0xd1, 0x53, 0x4d, 0x43, 0x65, 0x9b, 0xe3, 0xa1, + 0x65, 0x74, 0x05, 0x0f, 0xf4, 0xd4, 0x96, 0x0c, 0x8c, 0xab, 0x3c, 0xf6, 0x36, 0xfb, 0xdb, 0xa3, 0x54, 0xe4, 0x3b, + 0x9b, 0xd4, 0x34, 0xab, 0x46, 0x63, 0x1f, 0x47, 0xe8, 0x69, 0x05, 0x68, 0xbd, 0xb6, 0x54, 0xb4, 0xdf, 0x47, 0x31, + 0x6a, 0x5c, 0x4a, 0xb0, 0x0a, 0x1d, 0x09, 0x0e, 0x11, 0x46, 0x08, 0xfd, 0xbe, 0x08, 0x37, 0xae, 0x20, 0x83, 0x28, + 0xb8, 0x16, 0x15, 0x7f, 0xc8, 0xf2, 0xa2, 0x6d, 0xa9, 0xaa, 0x3e, 0x69, 0xda, 0x12, 0x78, 0x1d, 0x0e, 0xb0, 0x9d, + 0x7f, 0xea, 0x89, 0x5c, 0x2b, 0x1b, 0x25, 0x7c, 0x47, 0x5c, 0x0b, 0xa2, 0x9b, 0x46, 0xd7, 0xeb, 0xd9, 0x21, 0x5a, + 0x9a, 0xe2, 0xd0, 0x21, 0xfb, 0xdc, 0x3d, 0xb7, 0x65, 0x7c, 0xfb, 0x09, 0x72, 0xe7, 0x3b, 0x7b, 0x49, 0xc2, 0x20, + 0x6f, 0xd9, 0x40, 0xb1, 0x8e, 0xad, 0xa0, 0x00, 0xa3, 0xb6, 0xfc, 0x05, 0x74, 0x6c, 0x30, 0xa8, 0x09, 0x3e, 0x49, + 0x6c, 0x1b, 0x8f, 0xfc, 0x11, 0xe7, 0x86, 0x0e, 0xaf, 0x0d, 0x79, 0x20, 0x4e, 0x61, 0x9f, 0x28, 0x61, 0xff, 0x82, + 0x82, 0xee, 0x48, 0x2f, 0x57, 0x89, 0xab, 0xe2, 0x01, 0xaa, 0xec, 0x78, 0xae, 0xf9, 0x92, 0x16, 0x5a, 0x69, 0x64, + 0x15, 0x1d, 0x11, 0xb7, 0x60, 0x32, 0x66, 0xab, 0x6a, 0x54, 0x71, 0x2c, 0x50, 0xa4, 0x63, 0xce, 0x76, 0x0e, 0xd6, + 0x00, 0x78, 0x0a, 0x9b, 0x8b, 0x33, 0x2c, 0x28, 0xed, 0xb2, 0xa5, 0x2f, 0x81, 0x55, 0xf3, 0x30, 0xce, 0xcb, 0x8e, + 0x2f, 0x77, 0x47, 0xdb, 0x7b, 0xe8, 0x8d, 0xe8, 0x8d, 0xd7, 0xe7, 0x51, 0xd3, 0xcf, 0x9e, 0xe1, 0xda, 0x50, 0x90, + 0x07, 0x9a, 0xea, 0x10, 0x46, 0x8b, 0xc0, 0x34, 0xe5, 0x27, 0x6c, 0x3c, 0x1d, 0x0e, 0x35, 0x19, 0x74, 0x9a, 0x89, + 0xf1, 0xbf, 0x3e, 0x83, 0xd6, 0xe9, 0x89, 0xf3, 0x3e, 0x6d, 0x5f, 0x41, 0xeb, 0x3b, 0x94, 0xc9, 0x9d, 0x83, 0xe1, + 0x03, 0x2d, 0x98, 0x84, 0xa9, 0xc2, 0x1b, 0x22, 0x15, 0xec, 0xcd, 0xd2, 0x38, 0xec, 0x9b, 0x85, 0x42, 0x4b, 0x45, + 0xfc, 0x6a, 0x4d, 0xfc, 0xe4, 0x75, 0xe6, 0xdf, 0xa6, 0x7d, 0x72, 0x10, 0x4b, 0x43, 0x62, 0x24, 0xe2, 0x17, 0xa7, + 0xd2, 0x76, 0x42, 0x05, 0xc4, 0x43, 0xd7, 0xba, 0x71, 0x24, 0x64, 0xec, 0x49, 0x8d, 0xa7, 0x86, 0xfb, 0x5e, 0xc7, + 0xac, 0xc3, 0x2c, 0x76, 0xb3, 0x46, 0x42, 0x61, 0x9c, 0x9a, 0xe0, 0x94, 0x62, 0x15, 0xc9, 0xe8, 0x78, 0xa6, 0x30, + 0x88, 0x2a, 0x29, 0x21, 0xd6, 0x94, 0xad, 0x85, 0x89, 0x5d, 0x67, 0x0b, 0x53, 0xd4, 0x45, 0xa8, 0x37, 0x03, 0x9d, + 0x05, 0x0d, 0xf9, 0x1d, 0x1a, 0xad, 0xa8, 0x9a, 0x04, 0x0c, 0xe3, 0x28, 0xd5, 0xf8, 0xb7, 0x08, 0xb5, 0x1e, 0x06, + 0x00, 0xb6, 0x79, 0x27, 0xb2, 0xa2, 0x7e, 0x55, 0x20, 0x04, 0x9a, 0xb5, 0x9f, 0x2a, 0xeb, 0x9d, 0x59, 0xd0, 0x8a, + 0x76, 0x73, 0xe5, 0x73, 0x81, 0x13, 0xaa, 0x53, 0x79, 0x81, 0x7a, 0x29, 0xca, 0xd7, 0x22, 0xe5, 0xad, 0xb8, 0x98, + 0x07, 0x82, 0x7d, 0xc8, 0x47, 0x70, 0x5e, 0xa1, 0x53, 0xb9, 0xde, 0x26, 0xd2, 0x2c, 0x49, 0x30, 0x16, 0x68, 0x9b, + 0x97, 0x60, 0x26, 0x14, 0x33, 0x86, 0x5f, 0x43, 0x70, 0xb1, 0x9d, 0x93, 0x70, 0xb3, 0x9f, 0x07, 0x86, 0xd0, 0xe4, + 0x55, 0x4b, 0x34, 0x6c, 0xec, 0x78, 0x1d, 0xb9, 0x26, 0xdc, 0x87, 0xb5, 0x58, 0x93, 0x31, 0xc6, 0x95, 0xb9, 0x91, + 0xf1, 0xa3, 0x05, 0x1e, 0x8c, 0x49, 0xeb, 0x4f, 0x20, 0xd3, 0x52, 0xca, 0x3a, 0x5f, 0x68, 0x31, 0x93, 0x4c, 0x74, + 0x6e, 0xdf, 0xf8, 0x2c, 0xef, 0x22, 0xf2, 0xb7, 0xf2, 0x7b, 0x92, 0x0f, 0xf7, 0xee, 0x83, 0xc4, 0x1a, 0x94, 0x46, + 0x5c, 0x5a, 0x94, 0xa7, 0x0f, 0x74, 0xdd, 0xa4, 0x88, 0xd3, 0xf3, 0x55, 0x5c, 0x56, 0x3c, 0x85, 0x4a, 0x15, 0x75, + 0x8b, 0x7a, 0x13, 0xb0, 0x37, 0x44, 0x92, 0x64, 0x2c, 0x8d, 0x8d, 0xd8, 0xc5, 0x23, 0x3d, 0x7b, 0xc3, 0x2c, 0xbd, + 0xac, 0xd1, 0x90, 0x96, 0x3a, 0x67, 0xa1, 0x94, 0xf9, 0x4b, 0xfe, 0x33, 0x68, 0x24, 0xe8, 0xa8, 0x4f, 0x31, 0x9e, + 0x01, 0x23, 0xbe, 0x1b, 0xc1, 0xea, 0x01, 0xe2, 0xa2, 0x08, 0x4a, 0xb3, 0x23, 0x76, 0xfc, 0xd4, 0xe4, 0xe1, 0x5d, + 0xc8, 0x3a, 0x83, 0x4f, 0x1f, 0x66, 0x89, 0x5a, 0xeb, 0xa8, 0x1a, 0xc9, 0x19, 0x40, 0xd3, 0x41, 0x91, 0xf3, 0xb8, + 0x08, 0x66, 0x3d, 0x9d, 0x18, 0xf5, 0xb8, 0xfa, 0x05, 0x1a, 0x6a, 0xb7, 0x59, 0x59, 0x9e, 0xd5, 0xf7, 0x9f, 0xc3, + 0x81, 0x4d, 0x4d, 0x05, 0x3d, 0xde, 0xd4, 0xe2, 0xea, 0x4a, 0x76, 0xdb, 0x2d, 0x44, 0xcb, 0xe9, 0xbc, 0x6b, 0xe9, + 0xbc, 0x5e, 0xb0, 0x5e, 0x77, 0xba, 0x5e, 0xdc, 0x7e, 0x19, 0x1e, 0xc2, 0xda, 0xce, 0x27, 0x8a, 0x3f, 0xf3, 0xdb, + 0xee, 0xe2, 0x2d, 0x54, 0xb3, 0x00, 0x00, 0xd2, 0x83, 0x28, 0x58, 0x66, 0x29, 0x0f, 0xa8, 0xd8, 0xc7, 0x51, 0x96, + 0x52, 0x2f, 0x67, 0x18, 0x3f, 0x65, 0x1a, 0x6b, 0x9c, 0x15, 0xaa, 0xd0, 0xd8, 0xe8, 0x4e, 0x57, 0x19, 0x62, 0xfb, + 0x09, 0x9c, 0x2d, 0xc0, 0xfd, 0xd1, 0x43, 0xa1, 0xee, 0x4d, 0x5a, 0x9a, 0xa8, 0xf9, 0xae, 0x3d, 0x83, 0x8c, 0xe2, + 0x64, 0x95, 0x57, 0xd0, 0x8d, 0x3a, 0x6b, 0xa3, 0x4a, 0xdf, 0x43, 0xd4, 0xab, 0x18, 0x3c, 0xca, 0x5d, 0x5e, 0x1b, + 0x9d, 0x4c, 0x8b, 0x48, 0xb9, 0xf3, 0x93, 0x66, 0x99, 0xa5, 0x4a, 0x87, 0xed, 0x32, 0xec, 0xad, 0x31, 0xe9, 0x4d, + 0x48, 0x03, 0x23, 0xf1, 0xe9, 0x8c, 0x0a, 0x21, 0xa0, 0x2d, 0xc7, 0xdf, 0xe1, 0x33, 0x34, 0x4d, 0x81, 0xa5, 0x8a, + 0x5b, 0xd8, 0x0e, 0x9f, 0xff, 0x64, 0xd4, 0x02, 0x10, 0xc1, 0xca, 0xd5, 0xbb, 0x38, 0x25, 0x34, 0xe7, 0xca, 0x0c, + 0x00, 0x59, 0x50, 0xca, 0x2d, 0x3f, 0x25, 0xd3, 0xc1, 0x12, 0x45, 0xd9, 0xcb, 0xa9, 0x1b, 0x1d, 0x1b, 0x3f, 0xa4, + 0xe7, 0x02, 0xb6, 0x0b, 0xf9, 0xad, 0xbd, 0x7a, 0x89, 0x9a, 0x34, 0xa6, 0x59, 0x0f, 0xf0, 0xe5, 0x1a, 0x4d, 0x42, + 0x0b, 0xca, 0xa4, 0x29, 0x80, 0xc6, 0x4d, 0xd5, 0x0a, 0x26, 0xa5, 0x46, 0xc2, 0x96, 0x3a, 0x92, 0x65, 0xdf, 0x07, + 0xa7, 0xde, 0x23, 0xe8, 0x01, 0xf3, 0x08, 0xf4, 0xf4, 0x5f, 0xba, 0x6a, 0xff, 0x92, 0xa3, 0x93, 0xab, 0x26, 0x6a, + 0xfa, 0xbd, 0xb2, 0x23, 0x43, 0xca, 0xa5, 0x19, 0x08, 0x26, 0x1d, 0xf3, 0xd4, 0xd8, 0x3a, 0x46, 0x44, 0x0f, 0x9c, + 0x7d, 0xba, 0x5b, 0x4d, 0x2d, 0x00, 0xd1, 0xf1, 0xeb, 0x27, 0xaf, 0xae, 0xe3, 0x2b, 0x8d, 0xa2, 0xe4, 0x59, 0xc4, + 0x48, 0xd3, 0xbe, 0x5a, 0xc0, 0xe0, 0xfd, 0xf2, 0xfe, 0x27, 0x99, 0xa5, 0x71, 0x7b, 0xb0, 0x31, 0xa2, 0xaa, 0x5f, + 0x2a, 0x5e, 0xfa, 0x02, 0xac, 0x7d, 0x96, 0x28, 0x90, 0xfb, 0xbd, 0x49, 0xd3, 0xdf, 0x44, 0xde, 0xcd, 0x86, 0xf5, + 0xc6, 0x4d, 0xbb, 0xd4, 0x96, 0xec, 0xc8, 0x48, 0xe4, 0xf4, 0x62, 0xd0, 0xe3, 0x47, 0x2b, 0x8d, 0xd2, 0xb0, 0x41, + 0x55, 0x2a, 0x7e, 0xaf, 0x45, 0x70, 0xf2, 0x58, 0x95, 0x18, 0xd3, 0x80, 0xd9, 0x56, 0x36, 0x0a, 0xd4, 0x41, 0x2a, + 0x6d, 0x75, 0x14, 0xb6, 0xdf, 0x58, 0x49, 0xf5, 0xef, 0x7f, 0x6a, 0x43, 0x3e, 0x5f, 0x0a, 0x2a, 0x08, 0xd8, 0x19, + 0x78, 0x3d, 0x95, 0xc2, 0x40, 0x2a, 0xd8, 0x49, 0x05, 0x28, 0x5f, 0x44, 0x8e, 0xd5, 0x6e, 0x5f, 0xad, 0x1a, 0xa3, + 0x2d, 0x20, 0x34, 0x90, 0x1e, 0x5d, 0xf6, 0x71, 0x1b, 0xe3, 0x40, 0xe2, 0xc0, 0x09, 0xb6, 0x73, 0x75, 0x8d, 0x46, + 0x42, 0xf3, 0x87, 0x46, 0x03, 0x5e, 0xd3, 0x1a, 0x14, 0xea, 0x39, 0x8e, 0x86, 0xca, 0x0e, 0x29, 0x88, 0xd8, 0xa0, + 0x84, 0x7d, 0x7b, 0x3e, 0xd4, 0xfb, 0x7a, 0x9e, 0x2c, 0x48, 0x43, 0x85, 0xfd, 0xdc, 0x2e, 0x21, 0x63, 0xd5, 0x21, + 0xad, 0x3c, 0xc0, 0xf1, 0x42, 0xca, 0xfc, 0x2d, 0x26, 0x35, 0x4a, 0x63, 0x42, 0x6d, 0xc4, 0x02, 0x96, 0x04, 0xed, + 0xf5, 0x40, 0xdd, 0x32, 0x08, 0x75, 0x4c, 0x4f, 0x04, 0x3e, 0xa5, 0x5c, 0x7e, 0x5a, 0x92, 0x66, 0x5a, 0x32, 0x0b, + 0x7a, 0xe9, 0x5a, 0xf9, 0x15, 0xde, 0x47, 0xbb, 0x7b, 0x57, 0x5f, 0x58, 0xc7, 0x10, 0x0c, 0xbb, 0x72, 0x9b, 0xd3, + 0x50, 0x00, 0x36, 0x3c, 0x55, 0x65, 0xb9, 0x46, 0x4d, 0x64, 0x16, 0x87, 0x24, 0x02, 0xc9, 0xb6, 0xbf, 0xb9, 0xb5, + 0x60, 0xdb, 0x59, 0xa8, 0x9e, 0xfa, 0xcb, 0xd9, 0xee, 0x7b, 0x86, 0x97, 0x3b, 0x72, 0x6f, 0xdf, 0x86, 0xf2, 0x87, + 0xfd, 0xab, 0xe4, 0xff, 0xaa, 0x92, 0xfd, 0x56, 0x99, 0x4d, 0x5b, 0xbc, 0xdf, 0x75, 0xdc, 0x72, 0x8c, 0x06, 0x81, + 0x35, 0x05, 0x1a, 0xd2, 0x93, 0xc6, 0x34, 0x51, 0x21, 0x95, 0x19, 0xd3, 0x78, 0x74, 0x01, 0x9a, 0xc3, 0x74, 0x9e, + 0xc7, 0x00, 0x1c, 0xe0, 0x1e, 0x79, 0x84, 0xba, 0xa7, 0xf3, 0x3c, 0x38, 0x0f, 0x06, 0xc5, 0x20, 0x50, 0x9f, 0xd8, + 0xe6, 0x04, 0x0b, 0xd0, 0xb9, 0xc5, 0x0c, 0x82, 0x4d, 0x1a, 0x33, 0x87, 0xf8, 0x38, 0x99, 0x0e, 0x06, 0x31, 0xd9, + 0x00, 0x48, 0x5f, 0xbc, 0x30, 0xce, 0x41, 0xa5, 0x5a, 0x90, 0xad, 0xba, 0x4b, 0xbf, 0x62, 0xa7, 0xda, 0x69, 0xde, + 0xef, 0xe7, 0xf3, 0x62, 0x10, 0x78, 0x15, 0x96, 0xda, 0xfb, 0x8f, 0xfa, 0x5f, 0x6a, 0x9d, 0x34, 0xc1, 0x30, 0xb5, + 0xa7, 0xa8, 0x5e, 0x71, 0x34, 0xa3, 0xde, 0xed, 0x58, 0x2a, 0x5f, 0x40, 0x14, 0x0f, 0x0c, 0x59, 0x2b, 0xef, 0xce, + 0xc1, 0x6b, 0x73, 0xe3, 0xcd, 0x11, 0x05, 0xd8, 0xbe, 0x30, 0x4e, 0x28, 0x2e, 0xba, 0x6c, 0x88, 0x63, 0xb0, 0xd3, + 0xd5, 0x5b, 0x81, 0x56, 0xe3, 0xbd, 0x78, 0xd7, 0x6c, 0xfc, 0x8d, 0x38, 0x50, 0x65, 0x1e, 0x5c, 0x02, 0xe2, 0xec, + 0x41, 0x5c, 0x1f, 0x60, 0xa9, 0x07, 0xc1, 0xc0, 0x20, 0x87, 0xb4, 0xab, 0x55, 0x43, 0x11, 0xc9, 0xf3, 0x18, 0x0c, + 0x98, 0x74, 0x43, 0x1a, 0x32, 0xed, 0x95, 0x12, 0xd2, 0xc6, 0x58, 0x0b, 0x28, 0xc3, 0xe1, 0x6a, 0xc7, 0x6e, 0xd8, + 0x9e, 0x6e, 0x1d, 0x0a, 0x25, 0x8c, 0x5e, 0xdd, 0xf8, 0x87, 0x9a, 0xe7, 0x89, 0xa0, 0x06, 0x55, 0x6b, 0x3f, 0x1d, + 0x94, 0x27, 0xe5, 0xb1, 0x00, 0x17, 0x3d, 0x7c, 0xf9, 0xa2, 0xc0, 0x8b, 0xf6, 0x0e, 0xf2, 0x9c, 0xfe, 0x54, 0xfa, + 0x20, 0x7a, 0x6e, 0x19, 0x2e, 0xb4, 0x8f, 0x6b, 0x05, 0x32, 0x69, 0x3a, 0x9a, 0xda, 0xda, 0x1d, 0xde, 0x31, 0x81, + 0x7e, 0x53, 0x96, 0x52, 0x26, 0xba, 0x96, 0x25, 0x3b, 0xe9, 0xe5, 0xd2, 0x1b, 0x2a, 0x65, 0x27, 0xcb, 0x36, 0xe7, + 0x97, 0x7a, 0x09, 0xfd, 0xbe, 0x72, 0x07, 0xc2, 0x37, 0x72, 0xbd, 0x21, 0x2f, 0x1b, 0x22, 0x96, 0x43, 0xcc, 0xc0, + 0xf1, 0x42, 0x48, 0xd7, 0xee, 0xd2, 0x57, 0xd5, 0xed, 0x6c, 0xe5, 0x92, 0x16, 0x78, 0x2b, 0x05, 0x56, 0x91, 0x5a, + 0xbd, 0x9e, 0x4c, 0xdc, 0xf7, 0x51, 0x6c, 0x3e, 0x02, 0xb6, 0xd1, 0x3b, 0x1a, 0xbd, 0x5b, 0xc4, 0x06, 0x5f, 0x45, + 0x35, 0x2d, 0x39, 0x40, 0x70, 0xb7, 0x25, 0xb5, 0x34, 0xb3, 0x88, 0xfb, 0x92, 0x07, 0x68, 0xdf, 0xc5, 0xe1, 0x4c, + 0x2a, 0xc1, 0xb6, 0xae, 0x75, 0xce, 0x2a, 0x39, 0xa0, 0x9f, 0xe8, 0xf8, 0xa7, 0xd5, 0xa3, 0x22, 0x86, 0x55, 0x36, + 0x92, 0x56, 0x68, 0x0f, 0x4a, 0x97, 0x70, 0xf1, 0x05, 0x78, 0xd9, 0xde, 0xaf, 0xec, 0x3e, 0x5f, 0x62, 0xff, 0x30, + 0xaf, 0x9c, 0xe0, 0x91, 0xd3, 0x78, 0x73, 0x0f, 0xab, 0x3e, 0x57, 0x0a, 0xe1, 0x54, 0x4a, 0x43, 0x01, 0xc0, 0x20, + 0x09, 0x6a, 0xb8, 0xd2, 0xb6, 0x19, 0xa4, 0x34, 0x86, 0xdd, 0xad, 0xde, 0xe8, 0xff, 0x94, 0x0a, 0x17, 0xa0, 0x94, + 0x0d, 0xdc, 0x90, 0x75, 0xaa, 0xe5, 0x3a, 0xa6, 0xe0, 0xf9, 0x2e, 0x39, 0x02, 0x85, 0x1d, 0x18, 0x99, 0xd1, 0x84, + 0xfd, 0x82, 0xb7, 0xa1, 0x9c, 0xbd, 0x34, 0x92, 0x27, 0xbb, 0x2f, 0x69, 0x45, 0x13, 0x32, 0xad, 0xcc, 0xfe, 0x6d, + 0x6d, 0xd8, 0xe7, 0xa1, 0x18, 0x89, 0x02, 0x17, 0x07, 0x9d, 0x03, 0xd8, 0x1f, 0xe4, 0xd2, 0x36, 0x9f, 0x49, 0xbf, + 0x2f, 0xdf, 0x3f, 0xcb, 0xb3, 0xe4, 0xe3, 0xce, 0x7b, 0xcd, 0xd3, 0x2c, 0x19, 0x50, 0x89, 0x98, 0x1a, 0x57, 0xc5, + 0x70, 0xa9, 0x5c, 0x8c, 0x3d, 0x92, 0x11, 0xef, 0xa5, 0x0e, 0x31, 0x62, 0x7c, 0x91, 0x1d, 0x92, 0x92, 0xd3, 0x65, + 0xd3, 0xd9, 0x73, 0x25, 0x9a, 0x41, 0x63, 0xb8, 0x1d, 0xef, 0x25, 0xb5, 0x02, 0x64, 0x54, 0xe8, 0x9e, 0x01, 0xae, + 0xe1, 0xfe, 0x92, 0xf0, 0xb6, 0xf4, 0xb4, 0x25, 0x1a, 0xd8, 0x2b, 0x13, 0x12, 0x72, 0xe3, 0x00, 0x8b, 0xd8, 0x34, + 0x1f, 0x43, 0x01, 0x40, 0xad, 0x1a, 0xe9, 0x95, 0xbe, 0x24, 0x54, 0x26, 0x21, 0x18, 0x5d, 0x91, 0xf0, 0x2a, 0xa0, + 0x71, 0xa6, 0x13, 0x0d, 0x6c, 0x70, 0x40, 0x9f, 0xd7, 0x3a, 0x51, 0xdb, 0x90, 0x07, 0xb4, 0x36, 0x69, 0x00, 0x83, + 0x0f, 0x92, 0x24, 0xfa, 0x6a, 0xa9, 0x93, 0x40, 0x50, 0x82, 0xf2, 0x0d, 0xfa, 0x73, 0xe1, 0xf8, 0x58, 0x82, 0xff, + 0x91, 0x26, 0x82, 0x3f, 0x84, 0x02, 0x64, 0x8a, 0xaa, 0x62, 0x9a, 0xb1, 0x93, 0xac, 0xdb, 0x98, 0xc4, 0xf1, 0xb4, + 0xbb, 0x2b, 0xa5, 0x4b, 0x17, 0xf8, 0x95, 0x65, 0x88, 0x63, 0xfd, 0x2c, 0x5e, 0xb1, 0xd3, 0x90, 0x2b, 0xbc, 0xf4, + 0x67, 0xf1, 0x0a, 0x67, 0x88, 0xd6, 0xad, 0x04, 0x22, 0xfd, 0x57, 0x4d, 0xe0, 0x10, 0xfb, 0x09, 0x06, 0xb9, 0xa8, + 0x9d, 0x07, 0x02, 0x79, 0x5b, 0x41, 0x44, 0xfc, 0xec, 0x2a, 0x8c, 0x48, 0xbd, 0x93, 0xa4, 0xbf, 0xfc, 0x51, 0x64, + 0x85, 0xf3, 0x0d, 0x3c, 0xfa, 0xcd, 0x32, 0x29, 0xfa, 0x0b, 0x19, 0xcc, 0xc1, 0x7e, 0x22, 0xe3, 0x52, 0xd4, 0xee, + 0x13, 0x76, 0xc1, 0x89, 0xf1, 0xe0, 0xf4, 0x1a, 0x01, 0xf6, 0x6b, 0xf7, 0xc9, 0x19, 0xb3, 0xbf, 0x8c, 0x1b, 0x5f, + 0xa6, 0x23, 0x3e, 0xf0, 0xd1, 0x1d, 0xe5, 0xa3, 0x7b, 0x27, 0xd3, 0x1f, 0x1e, 0x94, 0xc8, 0xa8, 0xaa, 0xf9, 0x6a, + 0xc5, 0xd3, 0xd9, 0x5d, 0x12, 0x65, 0xa3, 0x9a, 0x17, 0x30, 0xbd, 0xe0, 0x78, 0x97, 0xac, 0x2f, 0xb2, 0xe4, 0x15, + 0xc4, 0x1e, 0x58, 0x09, 0x89, 0xc5, 0x0f, 0xcb, 0x4c, 0x2e, 0xe6, 0x42, 0xd4, 0xa2, 0xe0, 0xc1, 0xec, 0x26, 0x89, + 0xfe, 0x5a, 0x3a, 0x48, 0x6a, 0x7a, 0xca, 0x36, 0x8d, 0x25, 0xd4, 0xda, 0xd7, 0x91, 0x6e, 0x94, 0x05, 0x00, 0xdc, + 0xb3, 0x8b, 0x34, 0x12, 0xac, 0x1a, 0x4e, 0x1a, 0xc6, 0x75, 0x7a, 0x89, 0xa9, 0x71, 0xc3, 0x6a, 0x9a, 0x58, 0x0b, + 0x19, 0xd0, 0xfb, 0x03, 0x5e, 0x0e, 0x3e, 0x67, 0x45, 0x28, 0xa4, 0x35, 0x70, 0x71, 0x5c, 0xf6, 0xfb, 0xe2, 0xb8, + 0xdc, 0x6e, 0x8b, 0x93, 0xb8, 0xdf, 0x17, 0x27, 0xb1, 0xe6, 0x1f, 0xa4, 0x62, 0x5b, 0x9b, 0x1b, 0x24, 0x34, 0x17, + 0x10, 0xb5, 0x68, 0x04, 0x7f, 0x68, 0x96, 0xf3, 0x22, 0xca, 0x8f, 0x93, 0x7e, 0xbf, 0xb7, 0x9c, 0x55, 0x83, 0x7c, + 0x98, 0x44, 0xf9, 0x30, 0x71, 0x9c, 0x10, 0x7f, 0x75, 0x9c, 0x10, 0x25, 0x0d, 0x5c, 0xc1, 0x99, 0x01, 0x88, 0x02, + 0x2e, 0xfd, 0xa3, 0xaa, 0x96, 0x52, 0xd5, 0x12, 0xcb, 0x5a, 0x12, 0x55, 0x41, 0xc3, 0x6e, 0xca, 0xb0, 0xc0, 0x52, + 0xe8, 0x92, 0xfd, 0xb1, 0x04, 0x9e, 0x28, 0xe7, 0xf5, 0x06, 0x18, 0xd8, 0x08, 0xef, 0x1c, 0x3a, 0x9c, 0xc4, 0xba, + 0x61, 0x12, 0x32, 0xe9, 0x92, 0xae, 0xe8, 0x15, 0xf2, 0xb3, 0x97, 0x60, 0xb0, 0x74, 0xcc, 0xf2, 0xe9, 0x60, 0x70, + 0x49, 0x56, 0xac, 0x98, 0x87, 0xf1, 0x20, 0x5c, 0xcf, 0xf2, 0xe1, 0x65, 0x74, 0x49, 0xc8, 0x17, 0xe5, 0x82, 0xf6, + 0x56, 0xa3, 0xea, 0x63, 0x06, 0xc1, 0xfd, 0xd2, 0x59, 0x98, 0xe9, 0x38, 0x1f, 0xab, 0xd1, 0x1d, 0x5d, 0x41, 0xfc, + 0x1a, 0xb8, 0x91, 0x90, 0x08, 0x3a, 0x72, 0x45, 0x57, 0x74, 0x4d, 0x85, 0x9e, 0x61, 0x0c, 0xd1, 0x6d, 0x8e, 0x93, + 0x04, 0x1c, 0x93, 0x6d, 0xf1, 0xd1, 0x58, 0x16, 0xde, 0xf5, 0x1d, 0xa1, 0xbd, 0x5e, 0x62, 0x07, 0xe9, 0xbb, 0xf6, + 0x20, 0x01, 0x23, 0x32, 0x92, 0x03, 0xa5, 0x47, 0x46, 0x50, 0x3d, 0xa9, 0x38, 0x24, 0xb1, 0x3b, 0x24, 0x72, 0x1c, + 0x12, 0x77, 0x1c, 0x72, 0x35, 0x0e, 0xc8, 0xdd, 0x2f, 0xd9, 0x98, 0xa6, 0x6c, 0x4c, 0xd7, 0x72, 0x54, 0xe8, 0x35, + 0xbd, 0x50, 0xd4, 0xf1, 0x9c, 0xbd, 0x86, 0x03, 0x7b, 0x10, 0xe6, 0xb3, 0x78, 0xf8, 0x3a, 0x7a, 0x4d, 0xc8, 0x17, + 0x82, 0xde, 0xc8, 0x4b, 0x19, 0x84, 0x41, 0xbc, 0x06, 0xe7, 0x52, 0x1b, 0xea, 0xe4, 0x5a, 0xef, 0x38, 0x7c, 0xba, + 0xf2, 0x9e, 0x2e, 0x20, 0xa2, 0x0f, 0x5a, 0xa9, 0xf4, 0xfb, 0xe1, 0x25, 0x2b, 0xe6, 0xe7, 0xe1, 0x98, 0x00, 0x0e, + 0x8f, 0x1a, 0xce, 0xcb, 0xd1, 0x1d, 0xbd, 0x1c, 0xdd, 0x13, 0xb0, 0xf0, 0x1a, 0x4f, 0xd7, 0xc7, 0x2c, 0x9e, 0x0e, + 0x06, 0x6b, 0xa4, 0xea, 0x32, 0xf7, 0x9a, 0x2c, 0xe8, 0x25, 0x4e, 0x04, 0x01, 0x86, 0x3e, 0x2b, 0xd6, 0x9a, 0x86, + 0xbf, 0x66, 0xf0, 0xf1, 0x3d, 0xbb, 0x1c, 0xdd, 0xd3, 0x3b, 0xf6, 0x7a, 0x3b, 0x9e, 0x02, 0x33, 0xb5, 0x9a, 0x85, + 0xf7, 0xc7, 0x57, 0xb3, 0x2b, 0x76, 0x1f, 0xdd, 0x9f, 0x40, 0x43, 0xaf, 0xd9, 0x3d, 0x02, 0x2e, 0xa5, 0x8f, 0x97, + 0x83, 0xd7, 0xe4, 0x70, 0x30, 0x48, 0x49, 0x14, 0xde, 0x84, 0x4e, 0x2b, 0x5f, 0xd3, 0x7b, 0x42, 0x57, 0xec, 0x0e, + 0x47, 0xe3, 0x8a, 0xe1, 0x07, 0x17, 0xec, 0xbe, 0xb9, 0x09, 0x9d, 0xdd, 0x1c, 0x57, 0x9d, 0x20, 0x46, 0xe8, 0x6b, + 0x60, 0x69, 0x96, 0x0d, 0x33, 0x01, 0x4f, 0xfa, 0x22, 0xa3, 0x44, 0xa1, 0x19, 0x88, 0xb3, 0x12, 0x10, 0x4b, 0xa2, + 0xee, 0x37, 0x1b, 0x9d, 0xc3, 0x72, 0xee, 0xf7, 0x7b, 0xb5, 0xa6, 0x07, 0x88, 0x9c, 0xd9, 0x49, 0x0f, 0x7a, 0x2e, + 0x3d, 0xc0, 0x4f, 0xd4, 0xaa, 0x41, 0x9c, 0xcc, 0xef, 0x96, 0xd1, 0xaf, 0x0e, 0x7d, 0xf8, 0xa1, 0x9b, 0xf2, 0x54, + 0xf9, 0xbf, 0x4f, 0x79, 0x8a, 0x3c, 0x7a, 0x5d, 0x3b, 0x20, 0x78, 0xce, 0x9a, 0x94, 0x1a, 0x89, 0x7a, 0x74, 0xbe, + 0x8a, 0x41, 0x1b, 0x89, 0xda, 0x06, 0xf5, 0x84, 0x16, 0x56, 0x10, 0x21, 0xe7, 0xe8, 0x39, 0x18, 0xa4, 0x42, 0xa8, + 0x1c, 0xb9, 0x28, 0xd1, 0x10, 0x24, 0x17, 0x15, 0x97, 0xe1, 0x73, 0x08, 0x95, 0xa7, 0x8f, 0x35, 0x11, 0xd6, 0xf4, + 0x18, 0x0c, 0xb0, 0x2d, 0xfc, 0xdb, 0x0e, 0xb9, 0xa8, 0xf8, 0x15, 0x9e, 0xcd, 0x6d, 0x82, 0x51, 0xb2, 0xb8, 0x15, + 0xda, 0x06, 0xb1, 0x1f, 0x0b, 0x82, 0xf5, 0x08, 0x1a, 0x8f, 0x2a, 0x7d, 0x44, 0xb8, 0x51, 0x7c, 0x24, 0x3d, 0x8d, + 0x35, 0x89, 0xe4, 0x48, 0x22, 0xf9, 0x00, 0x08, 0x27, 0x41, 0x7f, 0x71, 0xdb, 0x64, 0xdb, 0x42, 0xa2, 0xd1, 0x9f, + 0x96, 0x4c, 0xc9, 0xee, 0x65, 0x8f, 0x5d, 0x45, 0x90, 0x3d, 0xa6, 0xff, 0x74, 0xfa, 0xf0, 0xcf, 0x25, 0xce, 0xa0, + 0xf1, 0x7c, 0x91, 0x9d, 0x99, 0x39, 0x83, 0x1b, 0x39, 0x5d, 0x56, 0xae, 0xcb, 0x97, 0xfc, 0x80, 0xdf, 0xd5, 0xbc, + 0x48, 0xab, 0x83, 0x9f, 0xeb, 0x36, 0x9e, 0x53, 0xb5, 0x5e, 0xd9, 0x38, 0x2b, 0xd2, 0x38, 0xd5, 0x91, 0xba, 0x68, + 0x6b, 0x58, 0xcf, 0xef, 0x11, 0x75, 0x25, 0x2d, 0x47, 0x4f, 0x21, 0x56, 0x7e, 0xca, 0xe5, 0x3a, 0xcf, 0x7f, 0xda, + 0x49, 0xc5, 0x29, 0xf6, 0x53, 0x90, 0x2a, 0xb5, 0x5c, 0x40, 0xd5, 0x1c, 0xb5, 0xdc, 0x2d, 0xf5, 0x0e, 0xb0, 0x6e, + 0x9b, 0xf2, 0x63, 0x69, 0x76, 0xe1, 0x24, 0x7b, 0xf7, 0x27, 0x5d, 0x86, 0x01, 0xa3, 0x50, 0x66, 0xd5, 0xb5, 0xb2, + 0x2f, 0x34, 0x4e, 0xc3, 0x70, 0xe5, 0xc7, 0x0b, 0x48, 0x17, 0x30, 0x8e, 0x13, 0x25, 0x13, 0xe3, 0xf6, 0xa8, 0xad, + 0x50, 0x7d, 0xce, 0x56, 0x20, 0x60, 0xae, 0xe1, 0xec, 0xba, 0x8e, 0xb6, 0x3b, 0xe2, 0x94, 0x51, 0xb5, 0x8a, 0x8b, + 0xef, 0xe3, 0x55, 0x35, 0xb3, 0x43, 0x1b, 0xf9, 0x63, 0x3a, 0xfd, 0x7b, 0x12, 0xba, 0x85, 0x50, 0xb8, 0xe5, 0x16, + 0x46, 0x9e, 0xdc, 0x1e, 0x96, 0x71, 0x83, 0x5e, 0x89, 0x2b, 0xd5, 0x37, 0x2d, 0x85, 0x54, 0x23, 0x5f, 0xfb, 0x02, + 0x7a, 0x3d, 0xf6, 0x7e, 0x2a, 0xcc, 0xdb, 0x9e, 0x31, 0x97, 0x08, 0x56, 0xb2, 0xec, 0xf6, 0x9d, 0x1a, 0x53, 0x31, + 0x83, 0x2e, 0xb6, 0x9d, 0x45, 0xa7, 0x1b, 0xf9, 0xa7, 0x99, 0xfb, 0x65, 0xde, 0xe1, 0xae, 0xa8, 0xde, 0x02, 0x17, + 0x9a, 0x95, 0x55, 0xdd, 0x96, 0x0d, 0x9b, 0xc6, 0x6b, 0x59, 0x28, 0x36, 0xc0, 0xb0, 0xe7, 0xae, 0x85, 0x07, 0x88, + 0x9b, 0x70, 0xcf, 0x2e, 0x1a, 0xb8, 0x31, 0x7c, 0x5e, 0x49, 0xae, 0x2b, 0x8d, 0xbe, 0xf4, 0xc9, 0xd2, 0xaa, 0xe1, + 0x64, 0x31, 0xe2, 0x45, 0xba, 0x68, 0x32, 0xb3, 0x16, 0x3e, 0xe1, 0x65, 0x38, 0xe7, 0x0b, 0xad, 0x9b, 0x52, 0xa5, + 0x97, 0x2c, 0x56, 0x9d, 0xde, 0xac, 0x14, 0x56, 0x4a, 0xc4, 0x8d, 0x59, 0x26, 0x50, 0x96, 0xa2, 0x91, 0xc2, 0x9b, + 0xb2, 0x65, 0x2b, 0xa9, 0xe5, 0x3d, 0x73, 0x70, 0x1f, 0xfb, 0x01, 0x31, 0x91, 0x75, 0x60, 0x52, 0x34, 0x74, 0x40, + 0xbb, 0xea, 0xd2, 0x35, 0xa3, 0x1e, 0x0c, 0x72, 0x43, 0x12, 0xb1, 0x82, 0x14, 0x2b, 0x58, 0x37, 0xac, 0x9c, 0xe7, + 0x0b, 0x7a, 0xc9, 0xc4, 0x3c, 0x5d, 0xd0, 0x15, 0x13, 0xf3, 0x35, 0xde, 0x84, 0x2e, 0xe1, 0x84, 0x24, 0x9b, 0x58, + 0x2a, 0x60, 0x2f, 0xf1, 0xf2, 0x86, 0x67, 0xaa, 0xa2, 0x65, 0x57, 0x92, 0x03, 0x8c, 0x2f, 0xaa, 0x30, 0x2c, 0x86, + 0x97, 0x60, 0x2d, 0x71, 0x18, 0xae, 0xe6, 0x7c, 0x21, 0x7f, 0x43, 0xc0, 0xf9, 0x24, 0x94, 0xec, 0x82, 0xd9, 0x0b, + 0x64, 0x7a, 0x3d, 0xe7, 0x0b, 0x39, 0x12, 0xaa, 0xe0, 0x6b, 0x63, 0x6c, 0x12, 0x3b, 0x82, 0x96, 0x59, 0x3c, 0x1f, + 0x2f, 0xa2, 0xb8, 0x81, 0x65, 0x78, 0x26, 0x67, 0xa6, 0x25, 0xff, 0xd1, 0x76, 0x52, 0xea, 0x06, 0x2b, 0xc9, 0x1f, + 0x1e, 0x1f, 0x5d, 0x02, 0x19, 0x33, 0xbb, 0x82, 0xe9, 0x0f, 0x5d, 0x1f, 0x19, 0xdc, 0x73, 0x53, 0xce, 0xb8, 0x0c, + 0x12, 0xa5, 0x05, 0x0e, 0x72, 0x96, 0xb4, 0xb5, 0x08, 0xdf, 0x3d, 0x2a, 0xca, 0x3e, 0x13, 0xba, 0x01, 0xdd, 0x47, + 0x82, 0x3e, 0xd0, 0x7b, 0xa5, 0x0a, 0x97, 0xd5, 0x36, 0x13, 0x70, 0x17, 0x09, 0xf2, 0x5b, 0xa1, 0x53, 0x35, 0x06, + 0x55, 0x34, 0x8b, 0x58, 0xb8, 0xf7, 0x11, 0x37, 0xca, 0xe6, 0x9f, 0xfa, 0x1e, 0x2f, 0x25, 0x0c, 0x6e, 0x48, 0x4d, + 0x9f, 0xcc, 0x9b, 0x2b, 0xf6, 0x1e, 0x3a, 0xea, 0x50, 0x6b, 0xbc, 0xaf, 0x5e, 0x72, 0x0a, 0x31, 0x4a, 0x28, 0x3a, + 0x09, 0x06, 0x70, 0xbb, 0x84, 0x14, 0x7b, 0x83, 0xdd, 0xf8, 0xd7, 0xbc, 0x28, 0xb8, 0x58, 0xd7, 0x75, 0xe0, 0x06, + 0x34, 0x9c, 0x2f, 0x76, 0x43, 0x18, 0x8e, 0x69, 0xeb, 0x1a, 0x06, 0x61, 0xc6, 0x30, 0x12, 0x82, 0xd3, 0xbf, 0xe8, + 0x2b, 0x9a, 0xc4, 0xab, 0xef, 0xf8, 0x5f, 0x19, 0x2f, 0x25, 0x91, 0x06, 0x11, 0x52, 0x37, 0xf1, 0x8d, 0x74, 0x93, + 0x02, 0x0a, 0x01, 0x46, 0x01, 0x95, 0x58, 0xd3, 0x54, 0xfc, 0x2d, 0x17, 0x1f, 0xfc, 0x54, 0x74, 0x3c, 0x1a, 0x37, + 0xad, 0xce, 0xc8, 0xa0, 0x33, 0xd0, 0xa3, 0x56, 0xd4, 0xd3, 0xa0, 0x95, 0xa0, 0x1b, 0xa9, 0xdf, 0xda, 0x87, 0xc0, + 0x29, 0xd3, 0xe0, 0x9d, 0x07, 0x74, 0x73, 0xee, 0x82, 0x27, 0x8f, 0xe9, 0xb9, 0x45, 0x4f, 0xae, 0xd9, 0x49, 0xdd, + 0x43, 0xed, 0xbd, 0x1e, 0xa1, 0xa0, 0xdf, 0xc7, 0x14, 0xe8, 0x46, 0xd0, 0x38, 0x57, 0xf7, 0x1f, 0x8b, 0x5d, 0x0e, + 0xdf, 0x72, 0x96, 0x1b, 0xc0, 0x52, 0x11, 0x8d, 0x04, 0x8f, 0x02, 0xd4, 0xa5, 0x2a, 0x84, 0x2d, 0x66, 0x71, 0xa8, + 0xcc, 0x56, 0xad, 0x87, 0x82, 0x1c, 0x17, 0x23, 0x70, 0x08, 0x5d, 0x57, 0x83, 0x62, 0xb4, 0xcc, 0xea, 0xf7, 0xf8, + 0x5b, 0xb1, 0x0e, 0x49, 0xb6, 0x8f, 0x75, 0xe0, 0x86, 0x75, 0x98, 0x7e, 0xd4, 0x48, 0x01, 0x68, 0xb2, 0x11, 0xd8, + 0x04, 0xe0, 0xbd, 0xdd, 0x47, 0x84, 0x5a, 0x99, 0xee, 0x65, 0x2c, 0xe4, 0xf7, 0x5e, 0x12, 0x94, 0xe0, 0x27, 0xd4, + 0x96, 0xa5, 0xe0, 0x9d, 0x47, 0x3a, 0x27, 0x4d, 0x56, 0xbc, 0x07, 0x71, 0x5a, 0xf8, 0xc0, 0xde, 0x82, 0xe0, 0x9c, + 0x25, 0xbd, 0xc7, 0xdb, 0xac, 0x92, 0xda, 0xa8, 0x81, 0x02, 0xf8, 0xdd, 0xe0, 0x1e, 0x41, 0xbe, 0xbc, 0xe1, 0x5a, + 0x89, 0xdb, 0x90, 0x0f, 0x4b, 0x7a, 0x44, 0x06, 0xe6, 0xb9, 0x1a, 0xc6, 0xf4, 0x88, 0x1c, 0x9b, 0x67, 0x61, 0x07, + 0x70, 0x20, 0xd4, 0xa8, 0xd2, 0x23, 0x68, 0xd0, 0x6f, 0xa6, 0x45, 0x86, 0x64, 0xfd, 0xa8, 0x1b, 0x8c, 0x88, 0xbf, + 0x20, 0xa2, 0x2e, 0xfe, 0xf9, 0x60, 0xae, 0x7b, 0xcc, 0x05, 0xc2, 0x1c, 0x0c, 0x38, 0x88, 0xdb, 0x20, 0xd4, 0x07, + 0xcc, 0xe6, 0x2e, 0xaa, 0xe8, 0xbd, 0x31, 0xcc, 0xec, 0xe8, 0x0f, 0x37, 0x12, 0x7c, 0x9d, 0xb5, 0x41, 0x9d, 0x17, + 0x87, 0x40, 0x10, 0xdc, 0x17, 0xaa, 0x9a, 0xab, 0x1e, 0xd8, 0x78, 0xcb, 0x7e, 0x6c, 0xb7, 0xe3, 0x69, 0x65, 0xaf, + 0xfd, 0x15, 0x85, 0x93, 0x4f, 0xca, 0xbf, 0xde, 0xeb, 0x0c, 0x16, 0x8c, 0x0c, 0x5f, 0x3a, 0xfb, 0x17, 0xbe, 0x56, + 0xd2, 0xbd, 0x6a, 0x50, 0x90, 0xc7, 0x47, 0x92, 0xfe, 0xed, 0x95, 0x95, 0x4f, 0xcd, 0xf4, 0x6f, 0xb7, 0x7a, 0x7d, + 0x1e, 0x8f, 0x26, 0xdb, 0x6d, 0x4f, 0x19, 0xb8, 0x52, 0x1d, 0x43, 0x08, 0x9d, 0xeb, 0xc9, 0xe1, 0x11, 0x44, 0x45, + 0xf0, 0xe3, 0x6e, 0x16, 0x9e, 0x44, 0xc6, 0x8d, 0xd3, 0x59, 0x78, 0x82, 0x1d, 0xee, 0x44, 0x25, 0x2e, 0x46, 0xad, + 0x0d, 0x4e, 0xcf, 0x93, 0x10, 0x42, 0x39, 0x60, 0x65, 0x77, 0xf2, 0xcf, 0xbd, 0x34, 0x13, 0x92, 0x93, 0xd5, 0xed, + 0x94, 0xee, 0x60, 0x9a, 0x1f, 0xe8, 0x11, 0x1c, 0x70, 0x67, 0x7f, 0x35, 0x1f, 0xc3, 0x24, 0x53, 0xe4, 0x14, 0xc9, + 0x2f, 0xd2, 0x53, 0x48, 0xda, 0xa1, 0xa7, 0x92, 0x00, 0x4e, 0xa8, 0xf9, 0x18, 0x7e, 0xc3, 0xb8, 0x7f, 0xe7, 0xbf, + 0xb6, 0x53, 0x11, 0x3d, 0xa1, 0x58, 0xa6, 0x22, 0xa7, 0x49, 0x56, 0x26, 0x10, 0xb5, 0x51, 0x36, 0x23, 0xfa, 0xca, + 0xc6, 0x7c, 0x94, 0x84, 0xcf, 0xa9, 0xf5, 0x7f, 0x86, 0xf0, 0x69, 0x74, 0x46, 0x80, 0xcb, 0x2b, 0xaf, 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, 0xdb, 0x6f, 0xe9, 0xc1, 0xe4, 0xab, 0x23, 0x2f, 0x6d, 0xf2, 0xf4, 0x5b, 0x7a, 0xf0, 0xf5, + 0x13, 0x2f, 0xed, 0x68, 0xfc, 0x94, 0x1e, 0x7c, 0xf3, 0xb5, 0x4e, 0xfb, 0x1b, 0x64, 0xfb, 0xf6, 0x08, 0xff, 0xd3, + 0x69, 0x93, 0xa7, 0x5f, 0xd1, 0x83, 0xc9, 0x18, 0x2a, 0x79, 0x6a, 0x2b, 0x19, 0x4f, 0xe0, 0xe3, 0xaf, 0xe0, 0xbf, + 0xbf, 0x91, 0x60, 0x41, 0x6b, 0xc1, 0x92, 0x0a, 0xf5, 0x67, 0x28, 0xe2, 0x44, 0xd5, 0x44, 0xc2, 0x43, 0xcc, 0x2c, + 0xbf, 0x89, 0xc3, 0x80, 0xd8, 0x74, 0x28, 0x88, 0x1e, 0x8c, 0x47, 0x4f, 0x49, 0xe0, 0xc2, 0xd3, 0xdd, 0xba, 0x20, + 0x63, 0x49, 0x35, 0xcf, 0xbe, 0x48, 0x34, 0x63, 0xe0, 0x00, 0x58, 0x7d, 0x74, 0x73, 0xd5, 0x62, 0x9e, 0x7d, 0x51, + 0x8b, 0xdd, 0x5c, 0xbf, 0xb5, 0x00, 0xe5, 0xdd, 0x55, 0xcb, 0x6e, 0x4b, 0x19, 0x3a, 0xad, 0x35, 0xfa, 0xec, 0x23, + 0xa6, 0x0f, 0x06, 0xce, 0x0d, 0xfb, 0xef, 0x3b, 0xe5, 0xb4, 0xbe, 0x51, 0x28, 0xd4, 0xa8, 0x3c, 0x24, 0xec, 0x04, + 0x8a, 0x1e, 0x0c, 0x80, 0x27, 0x70, 0x70, 0xdf, 0xfe, 0xcd, 0x32, 0x3e, 0x76, 0x94, 0xf1, 0x33, 0xca, 0x10, 0xd0, + 0xa8, 0x87, 0x99, 0x4d, 0x0f, 0x1b, 0xdd, 0xe8, 0x25, 0x0b, 0x79, 0x32, 0xf9, 0x9e, 0xc1, 0xae, 0xd6, 0xb5, 0x38, + 0xd0, 0xa2, 0x68, 0x71, 0x79, 0x90, 0xf2, 0x59, 0xcd, 0xfe, 0xbe, 0x44, 0xf5, 0x56, 0xe4, 0xbd, 0x11, 0xd9, 0xac, + 0x66, 0xdf, 0xeb, 0x37, 0xc0, 0xcd, 0xb0, 0xdf, 0xe4, 0x93, 0x1b, 0x38, 0x83, 0x0b, 0xd3, 0x1e, 0x69, 0x62, 0x04, + 0x58, 0x01, 0x19, 0x38, 0xf0, 0x00, 0xe8, 0xa0, 0x3b, 0xda, 0xdb, 0xad, 0x4c, 0xf1, 0xfb, 0x6c, 0x60, 0x00, 0x35, + 0xf3, 0x36, 0xb1, 0x65, 0xff, 0xcb, 0x93, 0x97, 0xa0, 0x70, 0xcb, 0x2f, 0x6f, 0xa7, 0x30, 0x84, 0x10, 0xfc, 0x71, + 0xc9, 0x00, 0x70, 0x20, 0xc0, 0x60, 0xac, 0x55, 0x40, 0xf5, 0x96, 0x8f, 0x36, 0x5c, 0xaa, 0x27, 0x81, 0x33, 0xb8, + 0x14, 0x65, 0xc2, 0xdf, 0x2a, 0xb1, 0x3f, 0x5a, 0x3f, 0xba, 0xbe, 0x3d, 0x0e, 0xac, 0x7d, 0x8f, 0x8f, 0xd4, 0x67, + 0xde, 0x75, 0x60, 0xd3, 0xf2, 0x8d, 0xaf, 0x1a, 0x23, 0xf1, 0x28, 0x80, 0x37, 0xd0, 0x11, 0x29, 0x34, 0x52, 0x2d, + 0x70, 0x0c, 0x85, 0xb4, 0x40, 0x1c, 0x79, 0x75, 0x83, 0x2d, 0x88, 0x08, 0xc1, 0xc3, 0xed, 0x5f, 0x4b, 0x19, 0x38, + 0xaa, 0xdf, 0xe7, 0xc2, 0x75, 0x7b, 0xd2, 0x76, 0xe4, 0x38, 0xf5, 0x53, 0x07, 0xdf, 0x9c, 0x34, 0x8d, 0xb6, 0x5c, + 0x49, 0x99, 0x61, 0x59, 0xd8, 0x49, 0xa8, 0xe4, 0x1e, 0xb5, 0x03, 0xc9, 0x17, 0x72, 0x88, 0x64, 0x81, 0x51, 0x28, + 0xc8, 0x70, 0x42, 0xc1, 0x66, 0xaa, 0x5a, 0x66, 0x97, 0x75, 0xb8, 0x91, 0x0a, 0x65, 0x4e, 0xd1, 0xb7, 0x1b, 0x1c, + 0x48, 0x48, 0x94, 0x55, 0x6f, 0xe2, 0x37, 0x21, 0x82, 0xd5, 0x71, 0x65, 0x0b, 0xc5, 0x9d, 0xfd, 0xc9, 0xd3, 0x2e, + 0xfe, 0x48, 0xbb, 0x80, 0xda, 0x58, 0x4c, 0xc3, 0x89, 0x89, 0x7d, 0x63, 0xbf, 0x30, 0x9a, 0x1e, 0x80, 0xfa, 0xae, + 0xa4, 0x18, 0x41, 0x7e, 0xa5, 0xed, 0x63, 0x7b, 0x8c, 0x89, 0x19, 0xc4, 0x1a, 0x96, 0x39, 0x33, 0xd9, 0x37, 0xc2, + 0x4e, 0x00, 0xb8, 0x11, 0x5a, 0x23, 0x21, 0xf0, 0x78, 0x1d, 0xe2, 0x79, 0x29, 0xc3, 0xb7, 0x66, 0x84, 0x8e, 0xc1, + 0x9b, 0xca, 0x34, 0x32, 0x13, 0xae, 0x60, 0x50, 0x1f, 0xdb, 0x2a, 0x0a, 0xab, 0xa9, 0x2c, 0x3b, 0x01, 0xb8, 0x81, + 0xec, 0x58, 0x5f, 0x3c, 0x67, 0xf5, 0x3c, 0x5b, 0x44, 0x3a, 0x28, 0x60, 0x5e, 0x19, 0x06, 0xed, 0xcd, 0x1e, 0xd9, + 0x8e, 0x45, 0xe8, 0x86, 0xfb, 0x08, 0xc6, 0xd3, 0xf6, 0x05, 0x2b, 0x88, 0x46, 0x88, 0x87, 0x19, 0x33, 0xf8, 0x5e, + 0x69, 0xca, 0x53, 0xd9, 0x12, 0x08, 0x1c, 0x85, 0x50, 0x17, 0xbb, 0x46, 0x09, 0x36, 0x93, 0x17, 0xcc, 0x60, 0xc7, + 0x8e, 0xd4, 0x74, 0xc9, 0x3a, 0x1d, 0xca, 0x29, 0x2d, 0xd4, 0x94, 0x2a, 0x5f, 0xc3, 0x6a, 0x5e, 0xa0, 0x87, 0x1e, + 0xb8, 0x1e, 0x28, 0x87, 0xbc, 0x82, 0x4e, 0x74, 0x04, 0x9d, 0x56, 0x9b, 0xb0, 0x73, 0x23, 0xd5, 0xb2, 0x06, 0x79, + 0xc7, 0x50, 0xef, 0x88, 0x17, 0x4e, 0xa0, 0x2e, 0x84, 0x08, 0xd9, 0xdb, 0x22, 0x7d, 0x44, 0xb3, 0xac, 0x7a, 0x09, + 0x65, 0x71, 0xc4, 0xd6, 0x05, 0x2b, 0x6d, 0x34, 0xb9, 0xe4, 0x11, 0x4f, 0x11, 0x11, 0xf0, 0x54, 0x6a, 0xd7, 0x77, + 0x5a, 0x42, 0x68, 0x96, 0x02, 0x71, 0xb3, 0x51, 0x9c, 0x1b, 0x13, 0xc8, 0x02, 0xe8, 0xdb, 0x4f, 0xd9, 0xb5, 0x13, + 0x0e, 0x76, 0x73, 0x9d, 0x15, 0xcf, 0xf9, 0x65, 0x56, 0xf0, 0x14, 0xc1, 0xae, 0xee, 0xf4, 0x03, 0xb7, 0x6c, 0x1b, + 0x58, 0xbe, 0x7d, 0x07, 0x0b, 0xa6, 0x0a, 0x95, 0x52, 0x22, 0x2b, 0xa2, 0x0a, 0x32, 0xbb, 0xcc, 0xdd, 0xeb, 0xac, + 0x78, 0x1d, 0xdf, 0x81, 0x37, 0x85, 0xc7, 0x4f, 0x8f, 0x2e, 0xf0, 0x4b, 0x44, 0x12, 0x85, 0x18, 0xb6, 0x18, 0x11, + 0x0b, 0x91, 0x63, 0xc7, 0x84, 0x72, 0x29, 0x68, 0x6d, 0x0d, 0x81, 0x13, 0x7f, 0x5a, 0x76, 0xef, 0x3a, 0x2b, 0xb4, + 0x7d, 0xc6, 0x75, 0x7c, 0xc7, 0x0a, 0x09, 0x66, 0x81, 0x71, 0xee, 0xdb, 0x52, 0x92, 0xeb, 0xac, 0xd0, 0x02, 0x92, + 0xeb, 0xf8, 0x8e, 0xfa, 0x32, 0x0e, 0x65, 0x45, 0xe7, 0xc4, 0xf9, 0xdd, 0x1d, 0x7e, 0x81, 0xa1, 0x56, 0xc6, 0xfd, + 0x3e, 0x48, 0xcc, 0x84, 0x69, 0xca, 0x4c, 0x44, 0x42, 0xa1, 0x85, 0xd4, 0x94, 0x0f, 0x26, 0x64, 0x77, 0xa5, 0x1a, + 0x46, 0xd4, 0x7c, 0x15, 0x56, 0xb3, 0x71, 0x34, 0x21, 0x74, 0xd2, 0xb1, 0xde, 0x75, 0x6b, 0x21, 0xd3, 0xe8, 0x69, + 0xe4, 0xf8, 0x74, 0x96, 0xac, 0x9e, 0x96, 0xc7, 0x8c, 0x4f, 0xcb, 0xc1, 0x80, 0xa8, 0xd0, 0xc1, 0x1b, 0xac, 0x07, + 0x4c, 0x69, 0x6c, 0xbc, 0x35, 0xdd, 0xea, 0x97, 0x42, 0x86, 0xa4, 0x77, 0x0c, 0x48, 0x32, 0x61, 0x83, 0xdd, 0x82, + 0x44, 0xd1, 0xf1, 0xbf, 0x93, 0x5b, 0x70, 0xd7, 0x83, 0xd1, 0x8f, 0xee, 0xeb, 0x18, 0xff, 0xa1, 0xb6, 0x05, 0x51, + 0x9f, 0x2a, 0xd6, 0xeb, 0x48, 0x94, 0x21, 0x17, 0xe1, 0x67, 0x47, 0x43, 0x34, 0x51, 0xed, 0xb1, 0xa0, 0x58, 0x5f, + 0x5f, 0xf0, 0x12, 0xa7, 0x9f, 0xd9, 0xcb, 0x15, 0x6c, 0x0b, 0x5a, 0x6b, 0x1a, 0xf5, 0x26, 0x7e, 0x13, 0x99, 0xcb, + 0x82, 0x2a, 0xf2, 0x39, 0x0a, 0x59, 0xf3, 0x30, 0xac, 0x87, 0xed, 0x41, 0x24, 0x87, 0xed, 0x49, 0xf0, 0x1a, 0x03, + 0x0b, 0x64, 0x87, 0x46, 0xe0, 0x22, 0x34, 0xf2, 0xb7, 0x63, 0x70, 0xe1, 0x32, 0x88, 0x2c, 0x43, 0x15, 0xbf, 0xa9, + 0xdd, 0x04, 0xd9, 0x2b, 0x74, 0x9a, 0xc2, 0xaa, 0xa4, 0x49, 0x3e, 0xfc, 0x7a, 0x29, 0x4a, 0xcc, 0xe4, 0x74, 0xd9, + 0xa1, 0xaf, 0xed, 0xf6, 0x0e, 0x74, 0xc1, 0xaa, 0x4f, 0xce, 0xd7, 0x8f, 0x3b, 0x7b, 0x02, 0x46, 0xb1, 0x32, 0x87, + 0x2f, 0xa4, 0x54, 0x3e, 0x28, 0xcd, 0xc7, 0x30, 0xaf, 0x14, 0xc7, 0x6e, 0x00, 0x93, 0x80, 0x7d, 0x86, 0x54, 0x87, + 0x69, 0xc7, 0x3e, 0x47, 0x1b, 0x58, 0x12, 0x70, 0xf8, 0x47, 0x99, 0x68, 0xdc, 0xab, 0x7b, 0x95, 0xfa, 0x21, 0x5b, + 0xe6, 0x0b, 0xe0, 0xf3, 0x61, 0xd7, 0x46, 0x05, 0xca, 0x26, 0x22, 0x41, 0x61, 0xcb, 0x63, 0x90, 0xf6, 0x28, 0xa6, + 0xab, 0x92, 0x27, 0x19, 0x4a, 0x29, 0x12, 0xe5, 0x13, 0x9c, 0xc3, 0x1b, 0xdc, 0x8f, 0x32, 0x20, 0xbc, 0x0c, 0x39, + 0x1d, 0xa5, 0x54, 0x59, 0xc0, 0x48, 0xea, 0x01, 0xa2, 0xbc, 0x0c, 0xe4, 0x78, 0xdb, 0xed, 0x84, 0xae, 0xd8, 0x72, + 0x38, 0xa1, 0x48, 0x4a, 0xae, 0xb0, 0xdc, 0x6b, 0xd0, 0x79, 0x5c, 0xb0, 0xde, 0x0b, 0xc0, 0x22, 0x38, 0x87, 0xbf, + 0x31, 0xa1, 0x37, 0xf0, 0x37, 0x27, 0xf4, 0x35, 0x0b, 0xaf, 0x87, 0x57, 0xe4, 0x30, 0x4c, 0x07, 0x13, 0x29, 0x18, + 0xbb, 0x67, 0xcb, 0x22, 0x94, 0x89, 0xab, 0xc3, 0x4b, 0xf2, 0xf8, 0x92, 0xde, 0xd1, 0x5b, 0x7a, 0x46, 0xdf, 0x02, + 0xe1, 0xbf, 0x3f, 0x9e, 0xf0, 0xe1, 0xe4, 0x49, 0xbf, 0xdf, 0xbb, 0xe8, 0xf7, 0x7b, 0xe7, 0xda, 0x80, 0x42, 0xed, + 0xa2, 0xab, 0x86, 0xaa, 0x5f, 0xd7, 0xcd, 0x62, 0xfa, 0x56, 0x6e, 0xdc, 0x84, 0x67, 0x79, 0x78, 0x7d, 0x78, 0x4f, + 0x86, 0xf8, 0x78, 0x99, 0x0b, 0x51, 0x86, 0x57, 0x87, 0xf7, 0x84, 0xbe, 0x3d, 0x01, 0xbd, 0x29, 0xd6, 0xf7, 0xf6, + 0xf1, 0xbd, 0xaa, 0x8d, 0xd0, 0x17, 0x61, 0x02, 0xdb, 0xe4, 0x8e, 0x99, 0xbb, 0xf6, 0x64, 0x0c, 0xb1, 0x4c, 0xee, + 0x9d, 0xf2, 0xee, 0x1f, 0xdf, 0x91, 0xc3, 0x3b, 0xf0, 0x14, 0x35, 0xe4, 0x6f, 0x16, 0xde, 0xb2, 0x56, 0x0d, 0x8f, + 0xef, 0xe9, 0x59, 0xab, 0x11, 0x8f, 0xef, 0x49, 0x14, 0xde, 0xb2, 0x2b, 0x7a, 0xc6, 0xae, 0x09, 0xbd, 0xe8, 0xf7, + 0xcf, 0xfb, 0x7d, 0xd1, 0xef, 0xff, 0x3d, 0x0e, 0xc3, 0x78, 0x58, 0x92, 0x43, 0x41, 0xef, 0x0f, 0x27, 0xfc, 0x2b, + 0x32, 0x0b, 0x55, 0xf3, 0xe5, 0x82, 0x33, 0x2a, 0x6f, 0x99, 0xeb, 0x9e, 0x82, 0xb5, 0xc2, 0x3d, 0x93, 0x4f, 0x6f, + 0xe9, 0x2d, 0x2b, 0xe9, 0x19, 0x8b, 0x49, 0x74, 0x03, 0xad, 0xb8, 0x98, 0x95, 0xd1, 0x2d, 0x3d, 0x63, 0xe7, 0xb3, + 0x38, 0x3a, 0xa3, 0x6f, 0x59, 0x3e, 0x9c, 0x40, 0xde, 0xb3, 0xe1, 0x2d, 0x39, 0x7c, 0x4b, 0xa2, 0xf0, 0xad, 0xfa, + 0x7d, 0x4f, 0xaf, 0x78, 0xf8, 0x96, 0x3a, 0xd5, 0xbc, 0x25, 0xba, 0x7a, 0xaf, 0xf6, 0xb7, 0x24, 0x72, 0x07, 0xf3, + 0xad, 0xb1, 0xa7, 0x79, 0x64, 0x69, 0x63, 0x5a, 0x84, 0xa0, 0x6f, 0x2e, 0xc2, 0x5b, 0x42, 0xa6, 0xfe, 0xd8, 0xc1, + 0x80, 0xce, 0x1e, 0x45, 0x09, 0xa1, 0xb7, 0x6e, 0xa9, 0xb7, 0x38, 0x86, 0x7a, 0x84, 0x64, 0xda, 0x19, 0xa6, 0xe1, + 0x3a, 0x78, 0xa5, 0xc0, 0x3a, 0x2e, 0xfa, 0xfd, 0x70, 0xdd, 0xef, 0x43, 0xa4, 0xfb, 0x72, 0xa6, 0x63, 0xbb, 0x59, + 0xb2, 0x49, 0x6f, 0x41, 0xfb, 0xff, 0x6a, 0x30, 0x80, 0xce, 0x38, 0x25, 0x85, 0xb7, 0x83, 0x57, 0x8f, 0xef, 0x89, + 0xac, 0xa3, 0xa4, 0x95, 0x08, 0x4b, 0xfa, 0x9a, 0x66, 0x00, 0xf8, 0xf5, 0x6a, 0x30, 0x20, 0x91, 0xfe, 0x8c, 0x4c, + 0x5f, 0x1d, 0xbf, 0x9d, 0x0e, 0x06, 0xaf, 0xf4, 0x36, 0xf9, 0x8b, 0xed, 0x29, 0x05, 0xd6, 0xdf, 0x79, 0xbf, 0xff, + 0xd7, 0x49, 0x4c, 0x2e, 0x4a, 0x1e, 0x7f, 0x9c, 0xfa, 0x6d, 0xf9, 0xcb, 0x46, 0x55, 0x3b, 0xef, 0xf7, 0xd7, 0xfd, + 0xfe, 0x19, 0x60, 0x17, 0xcd, 0xac, 0xaf, 0x27, 0x48, 0x5b, 0xe6, 0x96, 0x22, 0x29, 0x92, 0x43, 0x63, 0x68, 0x5b, + 0x2c, 0xdb, 0x36, 0xeb, 0xc8, 0xc0, 0xe2, 0xc8, 0xaf, 0x28, 0x6e, 0x48, 0x14, 0xf6, 0xce, 0xb7, 0xdb, 0x33, 0xc6, + 0x58, 0x4c, 0x40, 0xfa, 0xe1, 0xbe, 0x3e, 0x6b, 0xbc, 0x18, 0x62, 0x95, 0x40, 0x66, 0x73, 0xb3, 0x34, 0x87, 0x40, + 0xc4, 0x61, 0xd3, 0xbf, 0xd7, 0xf7, 0xf2, 0xaa, 0xb1, 0x7c, 0xeb, 0xcf, 0x00, 0x42, 0x24, 0x58, 0xc8, 0x67, 0x38, + 0x06, 0x55, 0x06, 0xc0, 0xbf, 0x91, 0x9c, 0x79, 0x01, 0xa0, 0xe6, 0x64, 0xbb, 0x1d, 0x8d, 0xc7, 0x13, 0x5a, 0xb2, + 0xd1, 0xdf, 0x9e, 0x3e, 0xae, 0x1f, 0x87, 0x41, 0x30, 0xc8, 0x48, 0x4b, 0x4f, 0x61, 0x16, 0x6b, 0x7d, 0x08, 0x46, + 0xf0, 0x8a, 0x7d, 0xbc, 0xc9, 0x3e, 0x9b, 0x7d, 0x44, 0xc2, 0xea, 0x31, 0x8e, 0xbc, 0x48, 0x5b, 0x7a, 0xbb, 0x3d, + 0x0c, 0x26, 0x2f, 0xd2, 0x4f, 0xb0, 0x9d, 0x2e, 0xff, 0xe6, 0xc0, 0x78, 0xc2, 0xc1, 0x68, 0x2f, 0x0a, 0xea, 0x4c, + 0xdb, 0x6e, 0x6b, 0xf7, 0x12, 0xf8, 0x06, 0x53, 0x41, 0xc7, 0x66, 0x58, 0xb8, 0x41, 0x4d, 0xe4, 0xd1, 0x32, 0xa8, + 0x1b, 0x69, 0x3b, 0x07, 0xd4, 0x12, 0xab, 0xd2, 0x71, 0x0b, 0x34, 0x43, 0x86, 0xba, 0xdc, 0xd3, 0xfa, 0x5f, 0xbc, + 0x14, 0x1a, 0x3e, 0xc3, 0x8a, 0x08, 0x1d, 0x6e, 0x8d, 0xbb, 0xdc, 0x5a, 0xf5, 0x09, 0x6e, 0xad, 0x40, 0x12, 0xab, + 0x61, 0x49, 0xf5, 0xe5, 0x28, 0x61, 0x27, 0x05, 0xe3, 0x33, 0xa0, 0xe3, 0x31, 0x3c, 0x08, 0x56, 0xcd, 0x44, 0x94, + 0xa0, 0x7d, 0xa2, 0x8d, 0x30, 0xf8, 0x27, 0x60, 0xf6, 0xd3, 0x1c, 0xfe, 0x0a, 0x32, 0x4d, 0x8e, 0x21, 0x20, 0xc4, + 0xf1, 0x78, 0x16, 0x87, 0x63, 0x12, 0x25, 0x27, 0xf0, 0x04, 0xff, 0x95, 0xe1, 0x98, 0x34, 0xea, 0x0e, 0x23, 0xe4, + 0xe5, 0x36, 0x61, 0x00, 0x57, 0x36, 0x9e, 0x4d, 0x22, 0x23, 0xdd, 0x15, 0x8f, 0x47, 0xe3, 0xa7, 0x64, 0x1a, 0x87, + 0x62, 0x90, 0x10, 0x0a, 0xde, 0xbd, 0x61, 0x31, 0x4c, 0x14, 0x3c, 0x1b, 0xb0, 0x79, 0x85, 0x65, 0xf3, 0x04, 0x9c, + 0x80, 0x30, 0x4c, 0xc8, 0xb1, 0xee, 0x41, 0x4a, 0x51, 0xe7, 0x39, 0xf6, 0x53, 0x1d, 0x41, 0x98, 0x1d, 0xb5, 0x54, + 0x7c, 0x05, 0x40, 0x97, 0x38, 0x38, 0xd4, 0x20, 0x61, 0x54, 0xb3, 0xb0, 0x70, 0xa8, 0x94, 0xae, 0xee, 0xb0, 0xf2, + 0x28, 0xbf, 0x6e, 0xd0, 0x61, 0x45, 0x06, 0x13, 0x5a, 0x9c, 0x4c, 0xf8, 0x57, 0x10, 0xc0, 0xc3, 0x8b, 0xf8, 0x25, + 0x71, 0x62, 0x20, 0xbc, 0x0a, 0x32, 0x50, 0x69, 0x23, 0x1b, 0x33, 0x32, 0x15, 0x1f, 0x40, 0x98, 0x94, 0x83, 0x5b, + 0xb1, 0xce, 0x53, 0x88, 0x0a, 0xb6, 0xce, 0xeb, 0x83, 0x2b, 0xb0, 0x64, 0x8f, 0x6b, 0x88, 0x13, 0xb6, 0x5e, 0x01, + 0x76, 0xee, 0xa3, 0x4d, 0xd1, 0x1c, 0xc8, 0xef, 0x0e, 0xb0, 0xe5, 0xf0, 0xaa, 0x16, 0x07, 0x93, 0xf1, 0x78, 0x3c, + 0xfa, 0x1d, 0x8e, 0x0e, 0x20, 0xb4, 0x24, 0xd2, 0x7c, 0x32, 0x40, 0xe3, 0xae, 0x6b, 0xee, 0x8c, 0x0b, 0x45, 0x59, + 0xe9, 0x64, 0x42, 0x40, 0xfc, 0xac, 0xfb, 0x06, 0xfb, 0x8a, 0xab, 0xf8, 0x27, 0xbb, 0x9f, 0xe8, 0x15, 0x2d, 0x57, + 0xea, 0xe8, 0xdd, 0xdb, 0xb3, 0x57, 0x1f, 0x5e, 0xfd, 0xfa, 0xe2, 0xfc, 0xd5, 0x9b, 0x97, 0xaf, 0xde, 0xbc, 0xfa, + 0xf0, 0xcf, 0x07, 0x18, 0x6c, 0xd7, 0x56, 0xc4, 0x8c, 0xbd, 0x73, 0x8f, 0x71, 0x6a, 0x71, 0x85, 0xb3, 0x47, 0xf6, + 0x16, 0x0b, 0xb0, 0x09, 0x9a, 0x5b, 0xa8, 0xa8, 0x62, 0x34, 0x6a, 0x75, 0x4f, 0x40, 0x46, 0xa3, 0x46, 0x36, 0x1e, + 0x56, 0x6c, 0x8d, 0x5c, 0xbc, 0x65, 0x38, 0xf8, 0xc8, 0xfc, 0x96, 0x9c, 0x09, 0x37, 0xa3, 0xad, 0x58, 0x11, 0xf0, + 0xf9, 0x5a, 0x17, 0xb5, 0xc3, 0x85, 0xc8, 0xbd, 0x6d, 0x9e, 0x43, 0x42, 0x1d, 0x22, 0xd7, 0xc1, 0xfb, 0x7a, 0x64, + 0x8f, 0x8f, 0x9c, 0x27, 0xe9, 0x19, 0xea, 0x72, 0x34, 0x7c, 0xe4, 0x3d, 0xa3, 0x13, 0x73, 0xa3, 0x75, 0xa8, 0xe7, + 0x25, 0xec, 0x6f, 0x29, 0xc6, 0x86, 0x68, 0x0f, 0x29, 0x62, 0x7d, 0x58, 0xdd, 0xef, 0xee, 0xcd, 0xe8, 0x7b, 0x38, + 0x7e, 0xa4, 0x6a, 0x02, 0x69, 0x51, 0x20, 0x75, 0x65, 0xc8, 0x6d, 0xcf, 0xc2, 0x52, 0xff, 0x0c, 0x3d, 0x02, 0x68, + 0x2e, 0x3b, 0x86, 0x04, 0xea, 0xc5, 0x6b, 0x5c, 0xff, 0x9c, 0x7c, 0x59, 0xd1, 0xce, 0x17, 0xdf, 0x41, 0x88, 0x61, + 0xf7, 0x8a, 0xe0, 0x4d, 0xb8, 0x9d, 0x64, 0x7b, 0x69, 0xd1, 0xf7, 0xaa, 0xeb, 0x18, 0x8f, 0xbb, 0x3d, 0x57, 0x0a, + 0xff, 0xd6, 0x05, 0xf6, 0x40, 0xfe, 0x75, 0xbc, 0x60, 0x21, 0x60, 0x33, 0x1e, 0x9a, 0x45, 0x62, 0xfd, 0xde, 0xe9, + 0x84, 0x1c, 0x1e, 0x4d, 0xf9, 0x90, 0x15, 0xb4, 0x1a, 0xb0, 0xa2, 0xd9, 0xa1, 0xe6, 0xbc, 0x4d, 0xc8, 0xab, 0x5d, + 0x1a, 0x5e, 0x0d, 0xf9, 0x43, 0x97, 0xa4, 0x0f, 0xdc, 0x73, 0xa8, 0x36, 0xcd, 0xc5, 0x90, 0xa6, 0x9c, 0xee, 0x52, + 0x19, 0x10, 0x22, 0x5d, 0xc7, 0x35, 0x69, 0xd4, 0x51, 0xb5, 0xb4, 0x92, 0x8e, 0x9b, 0x6c, 0xf3, 0x89, 0x4b, 0xb6, + 0xbc, 0x5d, 0xbb, 0xd7, 0xea, 0xf6, 0x85, 0x19, 0xc8, 0xdf, 0x1f, 0x88, 0x6a, 0xa2, 0x21, 0xba, 0x80, 0x0a, 0xfe, + 0x01, 0x5e, 0x9e, 0x3c, 0x52, 0x0a, 0xd0, 0x7d, 0x67, 0x47, 0xd7, 0x1e, 0xf7, 0x66, 0xb1, 0xb5, 0xc4, 0x39, 0xab, + 0x5d, 0x67, 0x79, 0x59, 0xb6, 0x44, 0xd7, 0xa9, 0xd8, 0xcf, 0x61, 0x47, 0xdf, 0x9d, 0x6d, 0x00, 0x44, 0x29, 0xac, + 0xed, 0xd9, 0x5f, 0x39, 0x67, 0x7f, 0x65, 0xce, 0x7e, 0xb3, 0x09, 0xa4, 0x0f, 0x2b, 0xb4, 0xec, 0xa5, 0x28, 0x6a, + 0xdd, 0xe4, 0xb1, 0xaf, 0xcb, 0x42, 0x5a, 0xcc, 0x0f, 0x0d, 0xed, 0x7a, 0x32, 0xa6, 0x02, 0xd5, 0x23, 0x3f, 0x60, + 0xab, 0x0e, 0x0b, 0xf2, 0xf0, 0x3d, 0xf3, 0x7f, 0xf6, 0x06, 0xb9, 0xef, 0x6e, 0xf7, 0x7f, 0x73, 0xa1, 0x83, 0xdb, + 0xda, 0xb2, 0x72, 0xd4, 0xd5, 0x71, 0x89, 0x77, 0xb5, 0xe5, 0xc3, 0x77, 0xb5, 0x77, 0x99, 0x5a, 0x76, 0x35, 0xa0, + 0x06, 0x15, 0xeb, 0x6b, 0x5e, 0x66, 0x49, 0x63, 0x14, 0x1a, 0x6f, 0x39, 0x84, 0xf6, 0x70, 0x0e, 0x2e, 0x90, 0xc3, + 0x12, 0x42, 0x3f, 0xd6, 0x5a, 0x00, 0xe8, 0xb2, 0xd8, 0x6f, 0x79, 0x98, 0x91, 0x81, 0x2b, 0xf1, 0x2b, 0x84, 0x2b, + 0x2e, 0x3e, 0xdc, 0xc9, 0x4c, 0xd0, 0xab, 0xc4, 0x46, 0xcd, 0x15, 0xed, 0x98, 0x1f, 0xee, 0x17, 0x18, 0x0d, 0xc2, + 0x69, 0x4b, 0x76, 0x58, 0x75, 0xcc, 0x72, 0x0d, 0x47, 0x6d, 0x61, 0xcb, 0x2c, 0x5a, 0xd7, 0xcf, 0x7a, 0x98, 0xa9, + 0x33, 0xe5, 0x2d, 0xc8, 0xbe, 0x90, 0xbb, 0x9f, 0xaa, 0x8a, 0x2b, 0x72, 0x32, 0x19, 0x4f, 0x49, 0x35, 0x18, 0xb4, + 0x92, 0x8f, 0x31, 0x79, 0x38, 0xdc, 0x61, 0x2e, 0x2b, 0xd5, 0x0f, 0xa7, 0x0f, 0x50, 0x9f, 0xb7, 0x25, 0xc9, 0xa6, + 0x66, 0x7f, 0x82, 0x59, 0x2c, 0x10, 0x47, 0x0b, 0xbf, 0x38, 0x5f, 0x00, 0xc8, 0x32, 0x2c, 0x33, 0x25, 0x2c, 0x82, + 0x2b, 0x3d, 0x74, 0xb2, 0x64, 0xe2, 0x78, 0x3c, 0x73, 0x7b, 0x6e, 0x19, 0x1c, 0x42, 0xa2, 0x89, 0x31, 0x7e, 0x71, + 0xb3, 0x60, 0x1c, 0x87, 0xe2, 0x44, 0x78, 0xdf, 0x15, 0x24, 0x1a, 0x6b, 0x53, 0x65, 0x75, 0x95, 0xa8, 0x87, 0x09, + 0x79, 0x5c, 0x92, 0xc3, 0x92, 0x2e, 0xdd, 0xb1, 0xc4, 0xf4, 0xc3, 0xf8, 0x70, 0x32, 0x26, 0x8f, 0xe3, 0xc7, 0x13, + 0x0d, 0x37, 0xec, 0xe6, 0xc8, 0x87, 0x4b, 0x72, 0xe8, 0x57, 0x09, 0xa6, 0xa8, 0xba, 0x67, 0x6e, 0x25, 0xc9, 0x60, + 0x39, 0x48, 0x1f, 0xb7, 0xf2, 0x62, 0xad, 0x6a, 0xbc, 0xd7, 0xc7, 0x7c, 0x4a, 0x2a, 0xef, 0xc6, 0xb0, 0xa6, 0xd7, + 0xf1, 0x1f, 0xa2, 0x8c, 0x0a, 0x01, 0x88, 0x84, 0xa0, 0xde, 0xce, 0x2e, 0xb3, 0x24, 0x2e, 0xd2, 0x28, 0x6d, 0x08, + 0x4d, 0x4f, 0xd8, 0x64, 0x3c, 0x4b, 0x59, 0x7a, 0x3c, 0x79, 0x3a, 0x9b, 0x3c, 0x8d, 0x8e, 0xc6, 0x51, 0x3a, 0x18, + 0x40, 0xf2, 0xd1, 0x18, 0x5c, 0xec, 0xe0, 0x37, 0x3b, 0x82, 0xa1, 0x3b, 0x41, 0x96, 0xb0, 0x84, 0xa6, 0x7d, 0x5e, + 0x93, 0xd4, 0x70, 0x5e, 0xca, 0x9e, 0xc4, 0x77, 0x74, 0xed, 0x38, 0xb8, 0xb8, 0x2d, 0xbc, 0xb4, 0x2d, 0xbc, 0xdc, + 0x6d, 0xa1, 0xb6, 0x20, 0x28, 0xc5, 0xff, 0x8f, 0x1b, 0xc6, 0xbe, 0xbb, 0x84, 0x5e, 0x5c, 0x37, 0xd9, 0x68, 0x55, + 0x8a, 0x5a, 0xc0, 0x6d, 0x42, 0x8a, 0xc2, 0x46, 0xf1, 0x6a, 0x95, 0x2b, 0x17, 0xb1, 0x79, 0x4d, 0x01, 0xdc, 0x05, + 0xce, 0x56, 0x60, 0xa1, 0xb5, 0x81, 0xdc, 0x5f, 0xbc, 0x14, 0xcc, 0xa8, 0x7d, 0xf4, 0x3d, 0xf2, 0x8f, 0x10, 0xc1, + 0x96, 0x4e, 0xc6, 0xb3, 0x0a, 0x11, 0x2d, 0x3e, 0x25, 0xef, 0xfd, 0x37, 0x8e, 0x22, 0x73, 0x34, 0x8f, 0x51, 0xa1, + 0x65, 0x3c, 0xe2, 0xcc, 0xc9, 0xe4, 0x64, 0xe0, 0x6e, 0x06, 0x23, 0xfd, 0xb5, 0xb7, 0x19, 0x63, 0xdb, 0xa3, 0x7a, + 0xa1, 0x85, 0xa2, 0x7f, 0xe1, 0x3b, 0x5d, 0x2f, 0xe0, 0x12, 0xca, 0x81, 0x5d, 0x5f, 0x5d, 0xf1, 0x0a, 0x40, 0x84, + 0xb2, 0xa2, 0xdf, 0xef, 0xfd, 0xa1, 0xa1, 0x49, 0x2b, 0x5e, 0xbe, 0xce, 0x0a, 0xe3, 0x8c, 0x03, 0x4d, 0x05, 0xea, + 0xff, 0xb1, 0x36, 0xcf, 0x74, 0x4c, 0x66, 0xee, 0xe3, 0x70, 0x42, 0x22, 0xff, 0x35, 0xf9, 0xc4, 0x69, 0xfa, 0x89, + 0x2b, 0xda, 0x7f, 0x20, 0x33, 0xd7, 0x1c, 0x32, 0xd4, 0x5f, 0x58, 0xe6, 0x49, 0xeb, 0x75, 0x62, 0x76, 0x52, 0xb1, + 0x7a, 0x06, 0xe8, 0xe9, 0x25, 0x3c, 0xc8, 0x6b, 0x59, 0x3c, 0x85, 0xd9, 0x07, 0x35, 0x62, 0x75, 0xcc, 0xc6, 0xb3, + 0x50, 0x84, 0x13, 0xb0, 0xef, 0x9d, 0x8c, 0xe1, 0x3e, 0x20, 0xc2, 0x8f, 0x75, 0x58, 0x51, 0x94, 0x92, 0x97, 0xf0, + 0x1b, 0x14, 0x13, 0x00, 0x11, 0x08, 0x79, 0xfb, 0x7d, 0x21, 0x93, 0xf0, 0x75, 0x81, 0x29, 0xa5, 0xfc, 0xe0, 0x3f, + 0x91, 0xaa, 0x5b, 0xa6, 0x5f, 0xae, 0x1f, 0x77, 0x26, 0x24, 0x9f, 0x6e, 0x53, 0xe2, 0x3b, 0x08, 0xee, 0x2c, 0x40, + 0x07, 0x51, 0xa3, 0x19, 0xdb, 0xc3, 0xfc, 0x6e, 0xb5, 0x9f, 0xdf, 0xad, 0xfe, 0xdf, 0xf1, 0xbb, 0xd5, 0x43, 0x8c, + 0x61, 0x6d, 0xa0, 0xe1, 0x67, 0xc1, 0x38, 0x88, 0xfe, 0x73, 0x3e, 0x71, 0x2f, 0x4f, 0x7d, 0x9d, 0x15, 0xd3, 0x3d, + 0x4c, 0xb3, 0x4b, 0x50, 0x10, 0x56, 0x71, 0x97, 0x9e, 0xac, 0x6b, 0x73, 0x6b, 0x25, 0x43, 0xcc, 0xf3, 0x00, 0x6b, + 0x14, 0xd6, 0x0e, 0xd0, 0x3d, 0xaa, 0x36, 0x88, 0x15, 0xc1, 0xc3, 0x98, 0x19, 0xe9, 0xfb, 0x76, 0xab, 0x55, 0x98, + 0x0f, 0x72, 0x51, 0x90, 0x5d, 0x7f, 0x3c, 0x1b, 0x47, 0x21, 0x36, 0xe0, 0x3f, 0x66, 0xac, 0x3c, 0xd9, 0x7c, 0x27, + 0x23, 0xb5, 0x63, 0xf2, 0x34, 0xd9, 0x25, 0xbd, 0x03, 0xde, 0x21, 0x3f, 0x6f, 0x3e, 0x86, 0xa5, 0xd0, 0xfc, 0x96, + 0xb8, 0x8a, 0xcb, 0xac, 0x5e, 0x5e, 0x67, 0x09, 0x32, 0x5d, 0xf0, 0xe2, 0xb3, 0x99, 0x2e, 0xe7, 0x63, 0x75, 0xc0, + 0x38, 0x4a, 0xf1, 0xc6, 0x13, 0xa5, 0xa7, 0x2d, 0xcf, 0x0a, 0x79, 0x79, 0x92, 0x31, 0xdb, 0xb3, 0x0a, 0x9c, 0x4e, + 0xc1, 0x04, 0x5f, 0xfd, 0xb4, 0xbd, 0x8f, 0x01, 0x17, 0x14, 0x6a, 0x4e, 0x4b, 0xb1, 0xd2, 0x58, 0x4e, 0x06, 0xba, + 0x13, 0x30, 0x43, 0x45, 0x81, 0x17, 0x28, 0xf8, 0x8b, 0x06, 0x46, 0xf4, 0xa5, 0xfd, 0x4d, 0x06, 0x1a, 0xe9, 0x52, + 0x9f, 0x08, 0x63, 0xcb, 0xed, 0x94, 0x69, 0x2b, 0xca, 0x19, 0x67, 0xef, 0xe5, 0x95, 0x02, 0x0c, 0xf0, 0x36, 0xb7, + 0xd1, 0x45, 0x82, 0x5e, 0x0b, 0x52, 0xe7, 0x0d, 0xdc, 0xcd, 0x32, 0xd2, 0xc2, 0xc5, 0xc7, 0xb5, 0xc3, 0x82, 0x3b, + 0xf6, 0x0b, 0xb1, 0xd0, 0x9a, 0x69, 0x30, 0x66, 0x73, 0x82, 0x05, 0x56, 0x32, 0x50, 0x60, 0x31, 0x53, 0x96, 0xa6, + 0xf5, 0x90, 0x1f, 0x1e, 0xa1, 0xb5, 0x69, 0x3d, 0xe0, 0x87, 0x47, 0x4d, 0x94, 0x1d, 0x43, 0x96, 0x13, 0x37, 0x83, + 0x7c, 0xdd, 0x44, 0x3a, 0x45, 0x67, 0x77, 0xeb, 0x4b, 0xdd, 0x51, 0xdd, 0x80, 0xeb, 0x07, 0x20, 0x80, 0x0d, 0xc0, + 0x21, 0x50, 0x0e, 0x96, 0x42, 0x04, 0x8b, 0x32, 0x89, 0xf6, 0x35, 0x74, 0xde, 0x28, 0xf8, 0x2f, 0x70, 0x17, 0x11, + 0x2b, 0xf7, 0x13, 0x04, 0xfe, 0x8a, 0x32, 0xa5, 0x4c, 0x71, 0x3f, 0x51, 0xea, 0x15, 0xca, 0x99, 0x6f, 0xcd, 0x07, + 0xd1, 0x9a, 0x08, 0x55, 0x8c, 0x21, 0xf8, 0xb7, 0xb2, 0x4c, 0x59, 0xaa, 0x4a, 0xf5, 0xa1, 0xf6, 0x5a, 0x2b, 0xad, + 0xe5, 0xe3, 0xc8, 0x79, 0x8d, 0xa1, 0x63, 0x13, 0x6b, 0x29, 0x27, 0x53, 0x67, 0x6f, 0x0e, 0x45, 0x64, 0x01, 0xa7, + 0x13, 0x36, 0x9e, 0x26, 0xc7, 0x62, 0x9a, 0x58, 0xc8, 0xfc, 0x9c, 0x61, 0x64, 0x55, 0x0d, 0xc2, 0x22, 0x6d, 0x28, + 0x4d, 0x01, 0x3a, 0x39, 0x21, 0x64, 0x8a, 0xa1, 0x28, 0xf2, 0x91, 0xea, 0x87, 0xf1, 0x66, 0xb5, 0x5f, 0xbc, 0x53, + 0x00, 0xa7, 0x61, 0x02, 0x81, 0xc0, 0xcb, 0xf8, 0x36, 0x2b, 0xae, 0xc0, 0x63, 0x78, 0x00, 0x5f, 0x82, 0x9b, 0x5c, + 0xca, 0x7e, 0xab, 0xc3, 0x1c, 0xd7, 0x16, 0x30, 0x68, 0xb0, 0x7a, 0x10, 0x1d, 0x2e, 0xa5, 0x7e, 0x57, 0x01, 0x62, + 0x63, 0x0a, 0xff, 0xb3, 0xb5, 0x61, 0xcf, 0xbe, 0x97, 0x4d, 0x43, 0xeb, 0x84, 0xd3, 0xe2, 0x2a, 0x87, 0x28, 0x2a, + 0x83, 0x18, 0xdc, 0x91, 0x1c, 0x3e, 0xef, 0x5d, 0x15, 0x5e, 0x12, 0x70, 0x2b, 0x8b, 0x45, 0xb8, 0xa2, 0xcb, 0xd1, + 0x1d, 0x5d, 0x8f, 0x6e, 0xe9, 0x98, 0x4e, 0xbe, 0x19, 0x83, 0x45, 0xb6, 0x4a, 0xbd, 0xa7, 0xeb, 0xd1, 0x92, 0x7e, + 0x3b, 0xa6, 0x47, 0x7f, 0x03, 0x13, 0x3e, 0x3c, 0x4c, 0xe8, 0x25, 0x38, 0x76, 0x91, 0x06, 0x3d, 0x35, 0x5d, 0x83, + 0xc3, 0x7a, 0x94, 0x0f, 0xf9, 0x28, 0xa7, 0x7c, 0x54, 0x0e, 0xeb, 0x11, 0x78, 0x3a, 0xd6, 0x43, 0x3e, 0xaa, 0x29, + 0x1f, 0x5d, 0x0c, 0xeb, 0xd1, 0x05, 0xf1, 0x9b, 0xfe, 0xaa, 0xe6, 0xd7, 0x15, 0x4b, 0x61, 0x5b, 0xc0, 0xf2, 0xb5, + 0xab, 0x2c, 0x49, 0xdd, 0x55, 0xad, 0x4f, 0x66, 0xc3, 0xd9, 0x9b, 0xeb, 0x2e, 0x27, 0x06, 0x8f, 0xdb, 0xa4, 0xc3, + 0xd5, 0x97, 0x13, 0x79, 0xd2, 0x4b, 0xe4, 0x87, 0xf1, 0x54, 0x9d, 0x43, 0x60, 0x26, 0x31, 0x0b, 0x63, 0x86, 0xcd, + 0x54, 0x69, 0xa0, 0xc0, 0xc9, 0x46, 0x8e, 0x8b, 0x62, 0x36, 0xca, 0x29, 0xbc, 0x8f, 0x09, 0x89, 0xf0, 0xac, 0x3a, + 0xa9, 0x47, 0x25, 0xc4, 0x1c, 0x61, 0x21, 0x3e, 0x42, 0xbf, 0xe4, 0x47, 0x0e, 0x12, 0x78, 0x86, 0x7d, 0x2d, 0x07, + 0x31, 0x1c, 0xf1, 0xa6, 0xb2, 0x7a, 0x16, 0x26, 0x50, 0x59, 0x3d, 0x2c, 0x74, 0x65, 0x25, 0xcd, 0x46, 0xb5, 0x5b, + 0x59, 0x8d, 0x63, 0x94, 0x10, 0x12, 0x15, 0xaa, 0x32, 0x50, 0x9f, 0x24, 0x2c, 0x2c, 0x54, 0x65, 0x17, 0xf2, 0xa3, + 0x0b, 0xb7, 0xb2, 0x0b, 0x70, 0x21, 0x1d, 0x24, 0xee, 0x55, 0x2a, 0x4f, 0xdb, 0xd7, 0x41, 0x6f, 0x55, 0xd1, 0x0d, + 0xbf, 0xab, 0xcb, 0x38, 0x2a, 0xa8, 0x8d, 0x01, 0x8d, 0x0b, 0x23, 0x12, 0x54, 0xad, 0x51, 0xf0, 0x87, 0x04, 0x51, + 0x69, 0x0c, 0x5e, 0x9d, 0x49, 0xd7, 0x4a, 0xad, 0x69, 0x35, 0x28, 0x06, 0x25, 0xdc, 0x9f, 0xf2, 0xd6, 0x42, 0xfa, + 0x1e, 0x22, 0x2a, 0x43, 0x79, 0x83, 0x7f, 0x60, 0xf0, 0x64, 0xb6, 0x4a, 0xc3, 0x64, 0x74, 0x4f, 0xe3, 0xd1, 0x12, + 0xe1, 0x60, 0xd8, 0x3a, 0x95, 0x78, 0xeb, 0x97, 0x90, 0x7e, 0x47, 0xe3, 0xd1, 0x2d, 0x4d, 0x8d, 0xcd, 0xa9, 0x86, + 0xba, 0xea, 0x8d, 0xe9, 0x5d, 0x04, 0xaf, 0xef, 0xa3, 0x25, 0x85, 0xad, 0x74, 0x9a, 0x67, 0x57, 0x45, 0x94, 0x52, + 0x44, 0x20, 0x5c, 0x23, 0x72, 0xe0, 0x52, 0xa1, 0x0d, 0xae, 0x07, 0x50, 0x86, 0x82, 0x0b, 0x5c, 0x0e, 0xe2, 0xd1, + 0xd2, 0x21, 0x53, 0x4b, 0x75, 0x91, 0x45, 0xf8, 0x68, 0x6b, 0xa3, 0x25, 0x79, 0x46, 0x2c, 0x8c, 0x4b, 0x18, 0x42, + 0x55, 0x58, 0xa1, 0x0b, 0x12, 0x36, 0x70, 0x64, 0x2f, 0x2c, 0xeb, 0x70, 0x03, 0xa6, 0x45, 0xf7, 0x60, 0x1e, 0x05, + 0x0a, 0x07, 0x9b, 0x20, 0xdc, 0x84, 0xa2, 0x9d, 0xa3, 0xd0, 0x39, 0x9c, 0x09, 0x4a, 0x77, 0x26, 0x08, 0x69, 0x57, + 0x37, 0xd9, 0x12, 0xae, 0xc1, 0xf6, 0x0e, 0x9d, 0x8a, 0x4a, 0xaa, 0xce, 0x2d, 0x98, 0x2c, 0xe1, 0x11, 0xb6, 0x84, + 0xa9, 0x99, 0x4e, 0xe1, 0x06, 0x7c, 0x78, 0xb4, 0x33, 0xdf, 0xe5, 0xec, 0xcd, 0x21, 0xd8, 0x76, 0x4a, 0x1f, 0x10, + 0x43, 0xec, 0x96, 0x6c, 0x3c, 0x5d, 0x1e, 0x17, 0xd3, 0x25, 0x12, 0x3b, 0x4d, 0xb7, 0x18, 0x9f, 0x2f, 0x17, 0x34, + 0xc1, 0xb3, 0x8d, 0xd5, 0xf3, 0xa5, 0x46, 0x4b, 0x49, 0x19, 0xae, 0xb7, 0x25, 0xfa, 0xff, 0xcb, 0x8b, 0x5f, 0x0a, + 0xf0, 0x12, 0x8c, 0x05, 0x80, 0x70, 0x0f, 0xa6, 0x05, 0xa9, 0x89, 0xb2, 0xb1, 0x4c, 0xc3, 0x14, 0x17, 0x81, 0x4e, + 0xe9, 0xf7, 0xc3, 0x9c, 0xa5, 0xc4, 0x81, 0x0e, 0x35, 0xa3, 0xb4, 0x4e, 0x5d, 0x21, 0x08, 0xf0, 0x48, 0xf2, 0x1c, + 0x9b, 0x7c, 0x33, 0x9e, 0x05, 0x72, 0x20, 0x82, 0x28, 0x3b, 0xc6, 0x47, 0x0c, 0x5c, 0x14, 0xa9, 0xb8, 0x9d, 0xb6, + 0x88, 0xcb, 0xdd, 0x63, 0x16, 0xe2, 0x24, 0x61, 0xae, 0x59, 0x36, 0x64, 0x75, 0x84, 0x09, 0xaa, 0x30, 0x30, 0xcb, + 0x1b, 0xb2, 0xfa, 0xf0, 0x08, 0x22, 0xb5, 0x9a, 0x32, 0x56, 0x5d, 0x65, 0x7c, 0x0b, 0x40, 0xd6, 0x8c, 0xb1, 0xa3, + 0xbf, 0x8d, 0x67, 0xf2, 0x9b, 0x28, 0xe4, 0x27, 0x47, 0x7f, 0x83, 0xe4, 0xe3, 0x6f, 0x91, 0x99, 0x83, 0x64, 0xaf, + 0xa0, 0x2b, 0x7f, 0xd6, 0x15, 0x94, 0x26, 0xae, 0xbd, 0x42, 0xad, 0x3d, 0xa1, 0xd7, 0x5e, 0x89, 0xee, 0xd4, 0x9a, + 0xf7, 0x90, 0xb6, 0xb3, 0x60, 0x82, 0x8e, 0x66, 0x77, 0xa0, 0x83, 0xb7, 0x8a, 0xa0, 0x17, 0x49, 0xa8, 0x3d, 0x42, + 0xa5, 0x51, 0x2f, 0xec, 0xc8, 0x6e, 0xd6, 0x25, 0x73, 0x0c, 0x98, 0x63, 0x73, 0x0e, 0x55, 0xc3, 0x5c, 0x1e, 0xd4, + 0x29, 0x2b, 0x86, 0x39, 0x1e, 0xc0, 0x6b, 0x26, 0x86, 0xd5, 0x20, 0x57, 0x28, 0xdf, 0x97, 0xac, 0x1c, 0x16, 0x83, + 0x5c, 0x71, 0x33, 0x53, 0x3f, 0x36, 0x6d, 0xa2, 0xc2, 0x33, 0xaf, 0xd8, 0xc9, 0xaa, 0x07, 0x7c, 0x2c, 0x78, 0x32, + 0xbb, 0x9e, 0x8f, 0xaf, 0x81, 0x93, 0xd9, 0xdc, 0x45, 0x4b, 0x7a, 0x1f, 0xa5, 0xf4, 0x36, 0x5a, 0xd3, 0x65, 0x74, + 0xa9, 0x4d, 0x8c, 0x93, 0x06, 0xce, 0x01, 0x68, 0x15, 0x40, 0xe2, 0xc9, 0x5f, 0xef, 0x79, 0x52, 0x87, 0x4b, 0x9a, + 0x82, 0xdb, 0xb0, 0x6b, 0x9f, 0x79, 0xed, 0x4a, 0xa4, 0x36, 0x88, 0xb1, 0x66, 0x0c, 0x15, 0x37, 0xce, 0xba, 0x8f, + 0xaa, 0x06, 0x76, 0xae, 0x8d, 0x4d, 0x54, 0x0f, 0x27, 0xd3, 0x02, 0x10, 0x5b, 0x8b, 0xe1, 0xd0, 0x1e, 0x21, 0xbb, + 0xc7, 0x8f, 0x0a, 0xf4, 0xdc, 0x13, 0x06, 0xdb, 0xb6, 0xe5, 0x0f, 0x0c, 0x61, 0x4a, 0x3f, 0x7d, 0xe4, 0x17, 0x84, + 0x4c, 0xaf, 0xe0, 0x6c, 0x04, 0xea, 0x68, 0x84, 0x4e, 0xbf, 0xd5, 0x61, 0xa9, 0x0e, 0xf0, 0xcd, 0x5d, 0x94, 0xd0, + 0xfb, 0x28, 0x77, 0xc8, 0xda, 0xb2, 0x61, 0x62, 0x7a, 0x9e, 0x85, 0xbc, 0x7d, 0xa0, 0x17, 0x0b, 0x00, 0xd1, 0x1a, + 0xc4, 0xae, 0xd4, 0xf5, 0x08, 0x9c, 0x86, 0xd0, 0x24, 0x34, 0x82, 0xab, 0x0a, 0xc2, 0x08, 0xd8, 0x92, 0xf0, 0x37, + 0x98, 0xa8, 0xc0, 0x17, 0xe0, 0x22, 0x93, 0xa6, 0x39, 0x0f, 0x1a, 0x77, 0x24, 0xcf, 0xca, 0xb6, 0xb7, 0x2b, 0x8c, + 0x26, 0x18, 0x7b, 0xa2, 0x7d, 0x1e, 0x29, 0x46, 0x71, 0x99, 0x84, 0xd9, 0xe8, 0x4e, 0x9e, 0xe7, 0x34, 0x1b, 0xdd, + 0xab, 0x5f, 0x35, 0x1d, 0xd3, 0xef, 0x54, 0x40, 0x1b, 0x29, 0x7d, 0xeb, 0x38, 0x1b, 0xd0, 0x7a, 0xb1, 0xd0, 0xfe, + 0xd7, 0x62, 0x74, 0x47, 0xc5, 0xe8, 0xde, 0xb5, 0xa4, 0x9a, 0x4c, 0xcb, 0xe3, 0x0a, 0x0d, 0xa9, 0x3a, 0xbf, 0x2f, + 0x81, 0x9f, 0x2b, 0xb4, 0xef, 0xb4, 0xfe, 0xde, 0x69, 0xff, 0x45, 0x27, 0x4f, 0x20, 0x59, 0xa2, 0x92, 0xd5, 0x23, + 0xb0, 0x63, 0x5f, 0xe7, 0x71, 0xa9, 0x47, 0x29, 0xa6, 0xc6, 0xa4, 0x1f, 0x03, 0x57, 0x4c, 0x7b, 0x25, 0xb8, 0x5a, + 0x6e, 0xb7, 0x32, 0x86, 0x26, 0xec, 0xd9, 0x31, 0x44, 0x3d, 0xd7, 0x8e, 0x51, 0xc2, 0x73, 0x0f, 0x88, 0x95, 0xcc, + 0x5b, 0xba, 0x04, 0x24, 0xf0, 0xd6, 0xc1, 0xa4, 0x28, 0x46, 0x29, 0xc0, 0x4f, 0xa8, 0x3c, 0x0e, 0xfa, 0x84, 0x7c, + 0xa1, 0x50, 0x27, 0x84, 0xb7, 0x25, 0x70, 0xfc, 0x61, 0x7d, 0x54, 0x08, 0x5e, 0xe5, 0xb8, 0xfe, 0x0a, 0xe3, 0xfa, + 0x4b, 0x85, 0xe3, 0x8e, 0x65, 0xbb, 0x7e, 0xde, 0xa6, 0x46, 0x2f, 0xc1, 0xc2, 0x77, 0x23, 0xcd, 0x23, 0xb9, 0x41, + 0x48, 0x95, 0x60, 0xa5, 0x76, 0x21, 0xc1, 0xfc, 0x4b, 0x39, 0x5b, 0x9d, 0xb9, 0xea, 0x91, 0x07, 0xe5, 0x6c, 0x6a, + 0xfa, 0x3d, 0x09, 0xda, 0x7d, 0x47, 0x9a, 0xc3, 0x5b, 0x74, 0xf8, 0xec, 0x1a, 0x4b, 0xcc, 0x9d, 0x44, 0xc9, 0xf3, + 0x49, 0x60, 0xab, 0xe7, 0xd9, 0xb5, 0xf4, 0xb1, 0xda, 0xc5, 0xf1, 0xd3, 0xe7, 0x4f, 0x5c, 0x87, 0x69, 0xe5, 0x29, + 0x41, 0xc0, 0x9b, 0x43, 0xdb, 0x15, 0xca, 0x80, 0x86, 0xfa, 0x06, 0x8e, 0x73, 0x35, 0xac, 0x15, 0x01, 0x53, 0x52, + 0x1e, 0x15, 0xe0, 0x50, 0xe7, 0x91, 0xbb, 0x69, 0x58, 0x6b, 0xba, 0xe6, 0xf5, 0xb9, 0xad, 0x74, 0xc6, 0x9b, 0x0d, + 0x3f, 0x3c, 0x1a, 0xd4, 0xf8, 0x93, 0xf8, 0xa3, 0xd1, 0xce, 0x0d, 0x77, 0x9a, 0x0a, 0x33, 0xd7, 0x62, 0x45, 0x76, + 0x47, 0xc9, 0xc9, 0xef, 0xe8, 0x85, 0xb1, 0x3f, 0xff, 0xb9, 0x98, 0x70, 0xd2, 0x12, 0x13, 0xa2, 0xa5, 0x83, 0x12, + 0x1d, 0xec, 0x28, 0xaf, 0xcc, 0x4b, 0xbc, 0x74, 0x8e, 0xff, 0x7d, 0x3d, 0xd6, 0xae, 0x02, 0xa1, 0xd5, 0xc9, 0xc3, + 0xf6, 0x64, 0x81, 0xa8, 0x01, 0xd5, 0xec, 0xb2, 0x1c, 0x69, 0xda, 0x59, 0x93, 0x8d, 0x27, 0x73, 0xdd, 0xcd, 0xe2, + 0xd9, 0x4c, 0x76, 0x2c, 0x2c, 0x3d, 0x0c, 0xc6, 0x4e, 0x15, 0x7d, 0x0e, 0x5a, 0x7e, 0x04, 0xcf, 0x7d, 0xe5, 0x99, + 0xcb, 0x66, 0x69, 0xf1, 0x02, 0x9d, 0x73, 0xaa, 0x21, 0x87, 0x1c, 0x80, 0xe3, 0x02, 0x8d, 0x25, 0x8a, 0x28, 0x08, + 0x1a, 0x13, 0x84, 0x5d, 0x95, 0xee, 0x48, 0x9f, 0x76, 0xf1, 0x69, 0x2b, 0xf4, 0x3d, 0xde, 0x67, 0x20, 0x31, 0x75, + 0x24, 0x0f, 0xb5, 0xd7, 0x1c, 0x95, 0x3c, 0x8b, 0x53, 0x85, 0xcf, 0x2f, 0x65, 0x67, 0xfe, 0xdd, 0x6a, 0x4c, 0xf1, + 0x1f, 0x69, 0xda, 0x77, 0x2e, 0x4d, 0x13, 0xdd, 0xb5, 0x3c, 0x68, 0x29, 0x2c, 0x38, 0x6e, 0x1b, 0x77, 0xfd, 0xfa, + 0x39, 0xaa, 0x61, 0x61, 0x73, 0x38, 0x13, 0x3a, 0xb4, 0x77, 0x95, 0x9d, 0xb9, 0x3e, 0xa2, 0x56, 0x5d, 0xac, 0xda, + 0x80, 0x92, 0x25, 0xe7, 0xd6, 0xe9, 0x88, 0x95, 0xbe, 0x3b, 0x0c, 0x77, 0xe6, 0x51, 0xb1, 0xbb, 0xdb, 0xed, 0x84, + 0xb4, 0xed, 0x83, 0xf1, 0xbe, 0x84, 0x85, 0x58, 0xef, 0xb0, 0x83, 0xef, 0xc3, 0xfa, 0x31, 0x1f, 0xfc, 0x1c, 0xca, + 0x75, 0x55, 0x3f, 0xcf, 0xa4, 0xa1, 0xcf, 0xcb, 0x52, 0x5c, 0xcb, 0x4e, 0xb9, 0x42, 0xb7, 0x96, 0xa9, 0xf7, 0x9b, + 0xf8, 0x4d, 0x2b, 0x40, 0x8c, 0xd3, 0x15, 0x23, 0xc5, 0x1b, 0x1a, 0x61, 0x9c, 0x87, 0xdb, 0x64, 0x51, 0x4b, 0x95, + 0x40, 0xd4, 0xe6, 0x27, 0x8f, 0x79, 0xa4, 0xd5, 0x99, 0xf0, 0xdd, 0x63, 0xee, 0x4a, 0xd7, 0x76, 0x9b, 0xf8, 0xa9, + 0xa6, 0x1d, 0xee, 0x0e, 0x74, 0x47, 0xeb, 0x1e, 0x6e, 0x9e, 0xc9, 0xcf, 0x23, 0xfd, 0xc5, 0x00, 0x9b, 0xb5, 0xcb, + 0xb8, 0xec, 0x18, 0xee, 0x3b, 0xd3, 0x83, 0xb1, 0x80, 0x40, 0x62, 0x86, 0x5e, 0x06, 0x36, 0x70, 0x81, 0xbd, 0xc2, + 0x80, 0x21, 0xae, 0x6e, 0xc9, 0xb9, 0xb2, 0xb2, 0x75, 0x91, 0xb7, 0x51, 0x21, 0xd8, 0x34, 0x1d, 0x37, 0x49, 0x0e, + 0xc1, 0x09, 0x5b, 0xee, 0x7d, 0xed, 0xb5, 0x33, 0xfc, 0xc7, 0xa0, 0xb2, 0x6e, 0x89, 0x8e, 0x51, 0xdb, 0x63, 0xa5, + 0xee, 0xd5, 0xbc, 0xca, 0x7d, 0xe4, 0x58, 0xbf, 0xe9, 0x97, 0x9a, 0x7d, 0xc1, 0x6b, 0x29, 0x38, 0x34, 0xb6, 0x5b, + 0x61, 0x17, 0x8b, 0x73, 0xb4, 0x1a, 0x59, 0x6b, 0xab, 0xbd, 0x46, 0x2a, 0xba, 0x7f, 0xcd, 0x71, 0x62, 0x2d, 0x85, + 0xcd, 0x87, 0x0f, 0x17, 0x6c, 0x9b, 0x00, 0x06, 0x2d, 0x3a, 0x0b, 0x94, 0x20, 0x93, 0x95, 0xaa, 0xdd, 0x4c, 0x89, + 0x5b, 0xee, 0x67, 0x5d, 0x66, 0x3b, 0x8f, 0x5f, 0x3b, 0x69, 0x9f, 0xf8, 0x1c, 0xfd, 0x30, 0xbf, 0x33, 0x4e, 0x4a, + 0xd6, 0x30, 0xae, 0xe5, 0xff, 0x57, 0xd3, 0xab, 0x32, 0x4b, 0xa3, 0x8d, 0xe6, 0xc1, 0x4c, 0xa8, 0x4d, 0x17, 0x1a, + 0xa3, 0xb6, 0xcb, 0x46, 0x12, 0xd1, 0xfa, 0x0e, 0x04, 0x33, 0x92, 0xfb, 0xaa, 0xda, 0xbc, 0x52, 0x6d, 0xe0, 0x1d, + 0x3e, 0xb1, 0xd1, 0x3d, 0xdb, 0x13, 0x42, 0xf9, 0xee, 0x69, 0xa1, 0x57, 0x2d, 0xad, 0x3c, 0xb6, 0xab, 0x72, 0x2e, + 0x46, 0xb5, 0x7a, 0xc2, 0x64, 0xc3, 0x82, 0xc9, 0xfe, 0x7f, 0x5f, 0x66, 0x69, 0x9b, 0xa2, 0x03, 0xd3, 0xe9, 0xfb, + 0x74, 0xd2, 0x0d, 0xae, 0x33, 0x60, 0x11, 0xc1, 0x96, 0x0a, 0xc7, 0xa3, 0x50, 0x6e, 0x90, 0x30, 0x11, 0x5c, 0x47, + 0xbd, 0xec, 0x68, 0x99, 0x94, 0x55, 0x01, 0xcf, 0x2f, 0x5d, 0x65, 0x3a, 0x8e, 0x86, 0x7e, 0xff, 0x3a, 0xd5, 0xa1, + 0x5f, 0x69, 0xe1, 0x9c, 0x23, 0xcb, 0xcc, 0x51, 0x75, 0xc8, 0x30, 0x46, 0x4b, 0x9a, 0xc2, 0x31, 0x98, 0x5d, 0x86, + 0x29, 0x5e, 0xce, 0x36, 0x09, 0xfb, 0x8c, 0x81, 0x5c, 0x2a, 0x83, 0x7a, 0x45, 0x89, 0xd6, 0xac, 0xbd, 0x99, 0x53, + 0x42, 0x2f, 0x59, 0xe1, 0xde, 0x85, 0xd6, 0x20, 0x50, 0x14, 0x7e, 0xca, 0xf4, 0x42, 0xb5, 0xf3, 0x92, 0x26, 0xb4, + 0xa4, 0x2b, 0xd2, 0x80, 0xbe, 0xd7, 0xca, 0xd9, 0xd1, 0xc9, 0x4e, 0xcf, 0x7a, 0xcc, 0xca, 0xe1, 0x64, 0x1a, 0xc3, + 0x35, 0x2d, 0xb6, 0xd7, 0xb4, 0xa5, 0x7f, 0xe3, 0xf2, 0x36, 0x8e, 0x47, 0xbb, 0x40, 0xda, 0xa6, 0xb8, 0xfd, 0xd4, + 0xe1, 0xf6, 0xd7, 0x0d, 0x5b, 0x4e, 0x7b, 0xeb, 0xed, 0xb6, 0x97, 0x82, 0x8d, 0xa8, 0xc3, 0xc7, 0xaf, 0xa5, 0x74, + 0xdd, 0x70, 0xf9, 0x29, 0x3c, 0x3b, 0x7c, 0xfd, 0xd2, 0x05, 0x97, 0xa3, 0x75, 0x9b, 0xbb, 0x5f, 0xee, 0x22, 0xcb, + 0x7d, 0xd6, 0xd0, 0x72, 0x35, 0x43, 0x3e, 0x79, 0xd6, 0xda, 0x3b, 0xd4, 0x82, 0xe5, 0xac, 0x9b, 0xf0, 0xc4, 0x60, + 0xc7, 0x5e, 0x7b, 0x9b, 0xa3, 0xd6, 0x97, 0x2c, 0x8f, 0x04, 0xba, 0x24, 0x4f, 0x37, 0xfd, 0x83, 0x08, 0xf3, 0xd1, + 0x1d, 0xcd, 0x01, 0x57, 0xac, 0x36, 0x97, 0x0c, 0xd2, 0xd4, 0xed, 0x25, 0x2e, 0x7d, 0x85, 0x43, 0xb2, 0xc1, 0x27, + 0xcd, 0x54, 0x7d, 0x72, 0xc9, 0x83, 0xff, 0xb7, 0x51, 0xab, 0xf4, 0xec, 0x24, 0x7b, 0x8e, 0x7f, 0x9d, 0xb4, 0x7d, + 0x4c, 0x34, 0x12, 0xf0, 0xd4, 0x2c, 0x86, 0x7a, 0x54, 0x97, 0x71, 0x51, 0xe5, 0x3a, 0xe6, 0xd8, 0xde, 0xae, 0xa1, + 0x83, 0x32, 0xf8, 0x75, 0xc3, 0x27, 0xfa, 0x0e, 0x6c, 0x04, 0x3a, 0x2a, 0x51, 0x5f, 0x86, 0x99, 0xbe, 0x0c, 0xd3, + 0xae, 0xad, 0x02, 0xc3, 0x2b, 0xb7, 0x4a, 0x22, 0x5d, 0x8d, 0x7a, 0x5c, 0xcf, 0x92, 0xdf, 0x8b, 0xbc, 0x7b, 0x4d, + 0x3a, 0x12, 0x7f, 0xba, 0x74, 0xe4, 0xf5, 0x30, 0x20, 0xe2, 0x73, 0x96, 0x86, 0x6d, 0x14, 0x04, 0xa7, 0x96, 0x3b, + 0x90, 0xe6, 0x23, 0x40, 0xe6, 0xc7, 0x69, 0xf8, 0x4e, 0x89, 0x73, 0xc8, 0x46, 0x6a, 0x9c, 0xd8, 0x52, 0xab, 0x87, + 0xe0, 0xce, 0x7b, 0xcd, 0x63, 0x08, 0x7c, 0xf8, 0x01, 0x37, 0x83, 0x8c, 0x6e, 0x4b, 0x74, 0x94, 0x36, 0x87, 0xba, + 0xe5, 0x23, 0x4f, 0xa8, 0x64, 0x64, 0x78, 0x31, 0xb4, 0x77, 0x47, 0x60, 0x54, 0x5b, 0x81, 0xcc, 0xb0, 0x3c, 0x3c, + 0x1a, 0xa6, 0x52, 0x50, 0x34, 0x14, 0xc3, 0x25, 0xca, 0x01, 0x31, 0x09, 0x04, 0x46, 0xe5, 0x20, 0x55, 0x95, 0xc9, + 0x17, 0x83, 0x54, 0xdd, 0xaa, 0x48, 0x73, 0x9e, 0x85, 0x35, 0x55, 0x2d, 0xa2, 0x63, 0x3a, 0x14, 0x74, 0xa9, 0x77, + 0x6a, 0xae, 0xa4, 0x17, 0x72, 0x39, 0x3e, 0x53, 0x69, 0x30, 0x8a, 0x67, 0x36, 0x45, 0xbd, 0x95, 0xfb, 0xd9, 0x7d, + 0x8b, 0x29, 0x0d, 0x62, 0x53, 0x3b, 0x8b, 0x18, 0x56, 0xed, 0x87, 0xac, 0xce, 0x41, 0xbb, 0x0b, 0xca, 0xc6, 0x5a, + 0x3b, 0xcf, 0x7b, 0xc1, 0xcc, 0x41, 0xdb, 0x58, 0xfb, 0x3e, 0xf4, 0x5a, 0x8c, 0xda, 0x1b, 0x53, 0x85, 0x7b, 0x02, + 0x3f, 0x4d, 0xd0, 0x74, 0x27, 0xf2, 0x1c, 0x75, 0xc8, 0xbb, 0xfb, 0x99, 0x25, 0x3b, 0x93, 0x4f, 0x62, 0x99, 0x34, + 0xed, 0x63, 0x12, 0xa3, 0x96, 0x18, 0x46, 0x17, 0x6e, 0x64, 0x52, 0xfb, 0xb9, 0x33, 0xfd, 0x88, 0x67, 0xf2, 0xb0, + 0x1d, 0x1a, 0x75, 0xa5, 0x61, 0x2d, 0x29, 0xa2, 0xba, 0xa0, 0xb7, 0xa6, 0x3a, 0x3a, 0xa2, 0x4e, 0x47, 0x60, 0x75, + 0x45, 0x1b, 0xd4, 0x00, 0x4c, 0xc6, 0x8d, 0xa9, 0xcd, 0xe5, 0x60, 0x1a, 0xa3, 0x2a, 0x78, 0x4a, 0x77, 0x85, 0xd2, + 0xbd, 0x49, 0xd3, 0xb4, 0x86, 0xd8, 0x00, 0x06, 0x04, 0x76, 0xf4, 0xe4, 0xf4, 0x07, 0x3e, 0x2a, 0x00, 0x0d, 0xbc, + 0xdb, 0x99, 0xca, 0x91, 0xa8, 0x77, 0x72, 0xd3, 0xfa, 0xa9, 0x4e, 0x55, 0x2e, 0x80, 0x8a, 0x3b, 0x4b, 0xe7, 0x97, + 0x7a, 0xc4, 0x02, 0x18, 0xf7, 0xc0, 0x9a, 0xea, 0x9d, 0x66, 0x60, 0x3d, 0x91, 0xe7, 0x59, 0xc5, 0x13, 0x51, 0xc0, + 0x8c, 0x88, 0xeb, 0x6b, 0x51, 0xc0, 0x30, 0xc8, 0x01, 0x40, 0x8b, 0xe6, 0x2a, 0x9a, 0xf0, 0xaf, 0x1a, 0xba, 0x2f, + 0x0f, 0xff, 0x4a, 0xe5, 0xfa, 0x7a, 0xdc, 0x80, 0xa1, 0xf2, 0xba, 0xe6, 0x3b, 0x99, 0xbe, 0xe6, 0x4f, 0x9c, 0x4c, + 0x4b, 0xb1, 0x2e, 0x77, 0xb2, 0x7c, 0xf5, 0x35, 0x7f, 0xaa, 0xf2, 0x1c, 0x3d, 0x69, 0x68, 0x1a, 0xdf, 0xef, 0x64, + 0xf9, 0xe6, 0xeb, 0x27, 0x26, 0xcf, 0x57, 0xe3, 0x86, 0xde, 0x72, 0xfe, 0xd1, 0x66, 0x9a, 0xa8, 0xaa, 0xc6, 0x4f, + 0xbe, 0x31, 0xb9, 0x9e, 0x34, 0xf4, 0x5a, 0x14, 0xf5, 0x72, 0xa7, 0xa8, 0xa3, 0xaf, 0x8f, 0xbe, 0xe1, 0x5f, 0xeb, + 0xee, 0x1d, 0x35, 0xf4, 0xcf, 0x75, 0x5c, 0xd6, 0xbc, 0xdc, 0x29, 0xee, 0x6f, 0xdf, 0x7c, 0xf3, 0xc4, 0x64, 0x7c, + 0xd2, 0xd0, 0x7b, 0x1e, 0x77, 0xb4, 0x7d, 0xf2, 0xf4, 0x09, 0xff, 0x5b, 0xd3, 0xd0, 0x5f, 0x98, 0x1b, 0x1c, 0xf5, + 0x34, 0x73, 0xf4, 0xf0, 0x89, 0xf0, 0x51, 0x03, 0x86, 0x0e, 0x1a, 0x40, 0x2e, 0x8c, 0x9a, 0x66, 0x8f, 0x57, 0x2e, + 0xb8, 0x7d, 0x9f, 0xc7, 0x69, 0xbc, 0x82, 0x83, 0x60, 0x83, 0xc6, 0x59, 0x25, 0x70, 0xaa, 0xc0, 0x7b, 0x46, 0x05, + 0xcd, 0x2a, 0xf1, 0x0f, 0xce, 0x3f, 0xc2, 0xa0, 0x21, 0xa4, 0x8d, 0x8c, 0x0c, 0xf4, 0x76, 0xa5, 0x22, 0x1b, 0xa1, + 0xff, 0xa6, 0x1f, 0x07, 0xc7, 0x85, 0xd1, 0xeb, 0xf7, 0xc3, 0x92, 0x55, 0x61, 0x49, 0x08, 0xfd, 0x23, 0x2c, 0xc1, + 0xa1, 0xa4, 0x64, 0x4e, 0x3e, 0xed, 0x7b, 0xae, 0x8c, 0xc2, 0x42, 0x10, 0xdd, 0x45, 0xe6, 0x01, 0x55, 0x8f, 0xae, + 0x43, 0x37, 0xc4, 0xcb, 0x0a, 0x4b, 0x86, 0x0e, 0x66, 0x30, 0x43, 0x50, 0xfc, 0x6b, 0x1e, 0x0a, 0xf0, 0x89, 0x07, + 0xf8, 0xe8, 0x31, 0x99, 0x71, 0x79, 0xad, 0x7d, 0x7b, 0x19, 0x96, 0x34, 0x50, 0x6d, 0x87, 0xa0, 0x03, 0x91, 0xfb, + 0x02, 0x3c, 0x05, 0x06, 0x2e, 0x2c, 0xec, 0x52, 0xec, 0xfa, 0xab, 0xff, 0xa2, 0x59, 0x47, 0x1b, 0x7e, 0xf4, 0x17, + 0xe3, 0xc2, 0x9e, 0x91, 0xa9, 0x38, 0x2e, 0x86, 0x93, 0xe9, 0x60, 0x20, 0x6c, 0x1c, 0xb7, 0xd3, 0x6c, 0xfe, 0xcb, + 0x5c, 0x2c, 0x16, 0xa8, 0xfb, 0xc6, 0x79, 0x9d, 0xa9, 0xbf, 0x91, 0x72, 0x3e, 0x78, 0x7d, 0xfa, 0xdb, 0xf9, 0xd9, + 0xe9, 0x4b, 0x70, 0x3e, 0xf8, 0xf0, 0xe2, 0xfb, 0x17, 0xef, 0x65, 0x70, 0x77, 0x39, 0xe7, 0xfd, 0xbe, 0x95, 0xfa, + 0x84, 0x7c, 0x58, 0x93, 0xc3, 0x30, 0x7e, 0x5c, 0x4a, 0xa3, 0x07, 0x72, 0xcc, 0x0c, 0x14, 0x32, 0x54, 0xd1, 0x98, + 0xdf, 0xc5, 0x70, 0xe2, 0x80, 0x59, 0xdc, 0x7b, 0x22, 0x5c, 0xb7, 0xe5, 0x26, 0xc8, 0x9a, 0x38, 0x71, 0xfa, 0xc1, + 0xc9, 0x54, 0x58, 0xb6, 0xb0, 0x64, 0x50, 0x36, 0xb4, 0xe9, 0x34, 0x9b, 0x97, 0x0b, 0xd3, 0x2e, 0xbb, 0x40, 0x46, + 0x69, 0x76, 0x79, 0x19, 0x4a, 0xe8, 0xea, 0x13, 0xd0, 0x00, 0xe8, 0x46, 0x95, 0xb6, 0x45, 0x7c, 0xe6, 0x96, 0x1f, + 0x8d, 0x9d, 0xe6, 0xdd, 0xa1, 0xee, 0x49, 0x37, 0xab, 0xf6, 0x06, 0x74, 0x30, 0xa1, 0xdc, 0x0e, 0xba, 0x0e, 0x26, + 0x23, 0xdb, 0xf2, 0xcb, 0xbc, 0x5e, 0xe8, 0xe6, 0xd8, 0x61, 0xa8, 0x9d, 0x92, 0xd7, 0xc2, 0x43, 0x64, 0x20, 0x19, + 0x86, 0x3d, 0x1a, 0xa3, 0x48, 0xfd, 0x60, 0xd7, 0x3b, 0x7e, 0x93, 0x0b, 0x88, 0xa6, 0x98, 0x81, 0x74, 0xfe, 0x51, + 0x25, 0x9d, 0xcb, 0x05, 0xe3, 0xf3, 0x6a, 0x71, 0x02, 0x6e, 0xe7, 0xf3, 0x6a, 0x11, 0x61, 0x50, 0xbe, 0x0c, 0x62, + 0x95, 0x80, 0xdd, 0x8b, 0x85, 0xf0, 0xed, 0x84, 0x36, 0x30, 0x1b, 0x48, 0xb0, 0x41, 0x61, 0x56, 0x1a, 0xa2, 0xdc, + 0x49, 0x8f, 0x36, 0x88, 0x3c, 0xc4, 0xea, 0x79, 0xdd, 0xf6, 0x64, 0xd3, 0x17, 0x13, 0x5c, 0x65, 0x31, 0x13, 0xd3, + 0xf8, 0x98, 0x95, 0xd3, 0x18, 0x4a, 0x89, 0xd3, 0x34, 0x8c, 0xe9, 0x84, 0x56, 0x84, 0x24, 0x8c, 0xcf, 0xe3, 0x05, + 0x4d, 0x50, 0x4a, 0x10, 0x42, 0xc8, 0x8f, 0x11, 0xda, 0xe6, 0xc0, 0x92, 0x37, 0xdb, 0xcf, 0xd1, 0xcf, 0xed, 0x18, + 0x2e, 0xa3, 0x22, 0x74, 0x83, 0xce, 0x1a, 0xee, 0x8d, 0xa8, 0xa4, 0x31, 0x56, 0x0c, 0x41, 0xc0, 0x4b, 0x8c, 0x4a, + 0x58, 0x92, 0x98, 0xd5, 0x10, 0x45, 0xa0, 0x98, 0xc7, 0x0b, 0x56, 0x52, 0xdf, 0xe6, 0x34, 0x56, 0x26, 0x41, 0x3d, + 0x8b, 0xa5, 0x76, 0x20, 0xa4, 0x0a, 0xb1, 0xc7, 0x67, 0x55, 0x74, 0xa3, 0x0c, 0x0d, 0x00, 0x05, 0x4a, 0xca, 0xc5, + 0x6f, 0x3f, 0xdf, 0xc3, 0x4d, 0x42, 0xff, 0xb3, 0x8d, 0x8e, 0x76, 0x96, 0xcb, 0x43, 0x6f, 0xbe, 0xa0, 0x71, 0x9e, + 0x43, 0x28, 0x36, 0x8d, 0x40, 0x5e, 0x64, 0x35, 0x44, 0xb4, 0xb8, 0x0f, 0x74, 0x48, 0x38, 0x68, 0xd3, 0x2f, 0x90, + 0xea, 0x89, 0xc9, 0xa5, 0x27, 0x06, 0xc6, 0xed, 0x90, 0x09, 0x05, 0x1c, 0xe9, 0x79, 0xf6, 0x97, 0x8f, 0xb1, 0xa6, + 0xa8, 0x99, 0x8e, 0xb7, 0x21, 0x11, 0x0d, 0x5a, 0x10, 0xcd, 0xe0, 0xfd, 0x73, 0xcd, 0xf1, 0xaa, 0x03, 0x3f, 0xe0, + 0x9d, 0x8d, 0x33, 0x2f, 0x67, 0x1e, 0x91, 0x53, 0x1f, 0xe5, 0x88, 0x7e, 0xc9, 0xc3, 0x7a, 0xa4, 0x92, 0x31, 0x56, + 0x12, 0x07, 0xbd, 0x0d, 0x16, 0xcc, 0x09, 0x5d, 0xf1, 0xd0, 0xf0, 0xf1, 0x2f, 0x91, 0xc9, 0xa8, 0x68, 0xa1, 0xd8, + 0x8d, 0xca, 0x60, 0xc4, 0x39, 0x0d, 0x33, 0x34, 0x59, 0xd2, 0xc5, 0x52, 0x91, 0xe6, 0x4a, 0x9a, 0x06, 0xb8, 0x04, + 0x1a, 0x3c, 0x1f, 0xf4, 0x43, 0x43, 0x3d, 0x84, 0x86, 0xdd, 0x21, 0xe0, 0x23, 0xfb, 0xd0, 0xe1, 0xff, 0xe7, 0xd8, + 0x05, 0x22, 0xed, 0xcd, 0x75, 0x64, 0x3c, 0xd2, 0x78, 0x38, 0x28, 0xda, 0xc7, 0xde, 0x4f, 0xfc, 0xcc, 0x19, 0x7d, + 0x48, 0x2a, 0xdf, 0xe1, 0x83, 0xe5, 0x8e, 0x37, 0xd5, 0xb3, 0x32, 0x82, 0xf5, 0xb0, 0xdd, 0xe2, 0x82, 0x68, 0xbb, + 0x00, 0x52, 0xc7, 0x78, 0xb5, 0x74, 0x8d, 0x57, 0xe3, 0x3d, 0xc6, 0xab, 0xf6, 0x4c, 0x0d, 0x73, 0xb2, 0x41, 0x7d, + 0x96, 0x94, 0xe7, 0xe7, 0x28, 0x13, 0xf4, 0x5d, 0xce, 0x0a, 0x2a, 0x53, 0x09, 0xed, 0xc5, 0x6e, 0xc6, 0xf8, 0x8e, + 0x60, 0x9c, 0x15, 0x8b, 0x91, 0x40, 0x65, 0x2a, 0x69, 0xc2, 0x5e, 0x09, 0xea, 0x31, 0x78, 0xaf, 0x31, 0x44, 0xb5, + 0x8c, 0x5d, 0xb7, 0x81, 0xd0, 0x50, 0x5b, 0x8f, 0xf6, 0x8c, 0xf5, 0xe8, 0x76, 0x5b, 0x6b, 0x7f, 0x3b, 0xb1, 0x2e, + 0x13, 0x44, 0x15, 0x96, 0xa3, 0x09, 0xf0, 0xa6, 0x89, 0xb9, 0x2d, 0x59, 0xa5, 0x05, 0x86, 0xcf, 0xfe, 0x23, 0x2c, + 0xac, 0x4a, 0xa2, 0x20, 0xb3, 0x22, 0x1a, 0xd8, 0x73, 0xf0, 0x79, 0x5c, 0xc3, 0x1a, 0x80, 0x48, 0x8e, 0xe8, 0xe1, + 0xfa, 0x47, 0x28, 0x6c, 0x66, 0x41, 0x66, 0x02, 0x32, 0xf3, 0x22, 0x6d, 0x67, 0x1d, 0x4c, 0xac, 0x49, 0xad, 0x33, + 0x16, 0x62, 0xa8, 0x91, 0x1f, 0x40, 0x19, 0x62, 0xf1, 0xc9, 0x07, 0x13, 0x2a, 0x64, 0x28, 0x55, 0xaf, 0x9b, 0xdd, + 0xc0, 0x2b, 0x1f, 0xb2, 0x6b, 0x5e, 0xd5, 0xf1, 0xf5, 0x4a, 0x5b, 0x12, 0x73, 0xb6, 0xcf, 0x6d, 0x8f, 0x56, 0xfa, + 0xd5, 0x9b, 0x17, 0xdf, 0x9f, 0x7a, 0xaf, 0x76, 0x11, 0x47, 0x43, 0xb0, 0xad, 0x18, 0x63, 0xf4, 0x16, 0x97, 0x06, + 0x13, 0xe9, 0x1a, 0x81, 0xde, 0xa5, 0xa0, 0xdf, 0xfe, 0x5c, 0x4f, 0xc0, 0x6b, 0xae, 0x96, 0x5f, 0xf2, 0x11, 0xb0, + 0x44, 0xf5, 0xac, 0x30, 0x3b, 0x2b, 0xb3, 0xbd, 0xdd, 0x8a, 0xf4, 0xb4, 0x4b, 0x8d, 0x0c, 0xc4, 0xab, 0xed, 0x30, + 0x16, 0x2e, 0x6c, 0xd3, 0xcd, 0x60, 0xd7, 0x4b, 0xc7, 0x12, 0x79, 0xbb, 0x2d, 0xa0, 0x43, 0x66, 0xc0, 0x9d, 0x97, + 0xf1, 0x1d, 0xbc, 0x2c, 0x9c, 0x6e, 0xfa, 0xc1, 0x13, 0xc0, 0x4c, 0xb8, 0xb4, 0x96, 0xc5, 0x31, 0xe3, 0x09, 0xcc, + 0x1f, 0x2d, 0x7d, 0x91, 0xb7, 0x24, 0xb4, 0x7a, 0x7f, 0x85, 0xd5, 0x08, 0xec, 0x4e, 0xce, 0x3e, 0x66, 0xab, 0xd9, + 0x12, 0x50, 0xf3, 0xaf, 0xb3, 0x02, 0x68, 0xae, 0x59, 0x0b, 0xa6, 0x29, 0xd4, 0x5f, 0xd7, 0xcf, 0xe2, 0x55, 0x9c, + 0x80, 0xea, 0x06, 0xbc, 0x45, 0xee, 0x95, 0xe8, 0x4a, 0xa3, 0x8b, 0xd2, 0x07, 0xca, 0x31, 0xa4, 0xd0, 0xd2, 0xf7, + 0x5e, 0x25, 0xcf, 0x3d, 0x0d, 0xb8, 0xa4, 0x50, 0xf3, 0x64, 0x4b, 0x19, 0x0b, 0x80, 0x85, 0x0e, 0x66, 0x92, 0x6c, + 0x45, 0x77, 0x1a, 0x93, 0x02, 0xde, 0x6a, 0xe0, 0x8f, 0x22, 0xab, 0xe5, 0x5d, 0xb1, 0x0a, 0x0b, 0xc7, 0xfe, 0xba, + 0xdf, 0x8f, 0x1d, 0xfb, 0xeb, 0x4b, 0x45, 0xeb, 0xe2, 0x76, 0x03, 0x48, 0x83, 0x01, 0x44, 0x4e, 0xd5, 0x40, 0xe8, + 0x88, 0x62, 0xbe, 0xef, 0xdf, 0xa9, 0xce, 0x22, 0x41, 0xe8, 0x77, 0xea, 0x75, 0xa4, 0x24, 0xa0, 0x53, 0xab, 0xd9, + 0xc9, 0x40, 0x99, 0x7d, 0x40, 0x40, 0x54, 0x37, 0x23, 0x9b, 0x2f, 0xa4, 0x73, 0xb1, 0x0c, 0x1f, 0x3e, 0xa6, 0x10, + 0x50, 0xb8, 0xa3, 0x46, 0xeb, 0x6d, 0x88, 0x04, 0xca, 0x08, 0x45, 0x8c, 0x79, 0xb1, 0x92, 0x84, 0xcc, 0xc7, 0x0b, + 0x14, 0x5c, 0x59, 0x60, 0x57, 0xce, 0x26, 0xc3, 0x22, 0xe2, 0x2c, 0xdc, 0xff, 0xcd, 0x64, 0x41, 0x50, 0x73, 0xe5, + 0x06, 0x72, 0xdc, 0xc9, 0xe4, 0xed, 0x29, 0xaf, 0x86, 0x8a, 0x89, 0x08, 0x02, 0xc3, 0x0d, 0x3f, 0xe3, 0xe3, 0xa3, + 0x05, 0x01, 0x15, 0x99, 0x31, 0x0b, 0xd1, 0x2f, 0x8e, 0xbf, 0x02, 0xd4, 0x98, 0xd1, 0xd1, 0x53, 0x00, 0x85, 0x85, + 0x80, 0xe8, 0x63, 0x90, 0xd1, 0x56, 0xf0, 0xbb, 0x92, 0xbf, 0x5b, 0x27, 0xbe, 0x0b, 0xfd, 0x5a, 0xd1, 0xcb, 0x18, + 0x18, 0x8e, 0x68, 0x72, 0x18, 0xf2, 0xc1, 0x64, 0x00, 0xda, 0x12, 0x67, 0xf7, 0xb5, 0xb4, 0xe2, 0xfa, 0x74, 0xe9, + 0x74, 0xff, 0xa4, 0x3e, 0x48, 0x22, 0x15, 0xac, 0x90, 0xc4, 0x00, 0x42, 0x59, 0xca, 0x6d, 0xb2, 0x04, 0xcb, 0x0a, + 0xbd, 0xa4, 0xb9, 0x46, 0x49, 0xdc, 0xdd, 0x0c, 0x1c, 0xa3, 0x66, 0x9d, 0x86, 0x45, 0xcb, 0x8d, 0x1a, 0xe0, 0x73, + 0x12, 0x56, 0x9a, 0x1b, 0xce, 0x4c, 0x38, 0x67, 0x3a, 0x5c, 0x1d, 0x73, 0xf6, 0x9a, 0x23, 0x18, 0x47, 0x82, 0x37, + 0x1e, 0xba, 0x64, 0x0a, 0x2a, 0x32, 0x65, 0x1c, 0x4c, 0x7b, 0x80, 0x7b, 0xcf, 0xc1, 0x38, 0x8c, 0x0d, 0x6a, 0x43, + 0xea, 0x53, 0xe7, 0x2e, 0x04, 0x82, 0xb4, 0xd6, 0xcb, 0x7c, 0x86, 0xa7, 0x67, 0x84, 0xb2, 0x3f, 0xe4, 0xf0, 0x01, + 0xf0, 0xb2, 0x24, 0x27, 0x13, 0xfe, 0xf4, 0xf1, 0x6e, 0xa0, 0x2a, 0x3e, 0x08, 0x0e, 0xe2, 0x22, 0x3d, 0x08, 0x06, + 0x15, 0xfc, 0x2a, 0xf9, 0x41, 0x2d, 0xc4, 0xc1, 0x65, 0x5c, 0x1e, 0xc4, 0xab, 0xb8, 0xac, 0x0f, 0x6e, 0xb3, 0x7a, + 0x79, 0xa0, 0x3b, 0x04, 0xd0, 0xbc, 0xc1, 0x20, 0x1e, 0x04, 0x07, 0xc1, 0xa0, 0xd4, 0x53, 0xbb, 0x62, 0x85, 0x77, + 0x9c, 0xe9, 0x10, 0x65, 0x81, 0x1f, 0x20, 0xcc, 0x3b, 0x0d, 0x80, 0x4f, 0x5d, 0xb3, 0x94, 0x5e, 0x62, 0xb8, 0x81, + 0x6a, 0xba, 0x86, 0x3e, 0x00, 0x8f, 0xbc, 0xa6, 0x31, 0x2c, 0x81, 0xcb, 0xc1, 0x80, 0xac, 0x21, 0x72, 0xc1, 0x9a, + 0x9a, 0x20, 0x0e, 0xe1, 0x5a, 0xda, 0x69, 0x17, 0x3b, 0x14, 0x76, 0xbb, 0x05, 0x44, 0xe5, 0x09, 0xe9, 0xf7, 0xcd, + 0x37, 0xd4, 0xbd, 0x60, 0x2f, 0xc1, 0xfe, 0xaa, 0xac, 0xc3, 0x44, 0x48, 0xcd, 0xf7, 0x15, 0x3b, 0x19, 0xc8, 0x88, + 0xc3, 0x3b, 0x8e, 0x14, 0x6d, 0x54, 0x2e, 0xc3, 0x9e, 0x2c, 0x3d, 0x5f, 0x89, 0x6b, 0x6e, 0xfd, 0xb8, 0x6a, 0x21, + 0xf2, 0x3a, 0x5b, 0x49, 0xf6, 0x6f, 0xc6, 0x15, 0xf7, 0x07, 0xd6, 0x9f, 0xfe, 0x2b, 0xb8, 0xb6, 0x3a, 0xef, 0x7c, + 0xae, 0x11, 0x39, 0x4b, 0x28, 0x97, 0x34, 0x26, 0x0f, 0x6f, 0xe9, 0xfb, 0xdc, 0xea, 0xdb, 0x4c, 0xa7, 0xf6, 0x59, + 0x85, 0x85, 0x0b, 0xd1, 0x8a, 0xe0, 0xd0, 0x10, 0x0b, 0xff, 0x08, 0xd0, 0xd7, 0x3e, 0x53, 0x41, 0x49, 0x9a, 0xf3, + 0x1a, 0xbd, 0x5b, 0x21, 0xe1, 0xa5, 0x62, 0x97, 0x1e, 0x06, 0x52, 0xc6, 0xed, 0xa1, 0x24, 0x4c, 0x4a, 0x5e, 0x84, + 0xf7, 0x5e, 0x7d, 0x93, 0x7b, 0x1e, 0x62, 0xf4, 0x22, 0xc7, 0x4e, 0x40, 0x5b, 0x77, 0x89, 0xce, 0x86, 0x27, 0x6e, + 0xc3, 0x73, 0xd6, 0xa2, 0xd1, 0x74, 0xc9, 0x92, 0x7e, 0x3f, 0x06, 0x13, 0xef, 0x94, 0xe5, 0xf0, 0x2b, 0x5f, 0xd0, + 0x35, 0x03, 0x4c, 0x31, 0x7a, 0x09, 0x09, 0x29, 0x22, 0x91, 0xac, 0xe5, 0x49, 0xf2, 0x89, 0xee, 0x42, 0x70, 0x84, + 0xcb, 0x59, 0x1a, 0x2d, 0xf7, 0x9a, 0x59, 0x20, 0x79, 0x86, 0xbe, 0xab, 0x60, 0x7b, 0x63, 0x17, 0xa4, 0x9c, 0x1f, + 0x57, 0xd3, 0xc1, 0x80, 0x13, 0x05, 0x37, 0x5e, 0x48, 0x71, 0xad, 0x6a, 0x71, 0xc7, 0x30, 0x16, 0xea, 0xb6, 0x88, + 0xc1, 0x01, 0xbb, 0x68, 0x65, 0xb7, 0x0f, 0xb0, 0xab, 0x1c, 0xef, 0x52, 0x65, 0x77, 0x7a, 0xcc, 0xf8, 0xcb, 0x56, + 0x91, 0x4e, 0x5a, 0xed, 0x27, 0xf2, 0x3e, 0x77, 0xd0, 0xe5, 0x72, 0xac, 0x78, 0xcb, 0x41, 0x45, 0x1e, 0xf3, 0x91, + 0xa4, 0xba, 0x9f, 0xe1, 0x08, 0xf3, 0x60, 0xdd, 0xfa, 0x93, 0x43, 0x5d, 0xe0, 0x10, 0x79, 0x52, 0xaf, 0x29, 0xa0, + 0x7b, 0xaf, 0x1e, 0x77, 0xf5, 0xdb, 0xd0, 0x5d, 0xa0, 0x44, 0x3b, 0x15, 0x7b, 0x7e, 0x4c, 0xd4, 0xea, 0x4c, 0x3d, + 0xa1, 0x7f, 0xad, 0xc5, 0xfd, 0x85, 0x76, 0x15, 0xf7, 0xbd, 0xcb, 0x67, 0x1c, 0xea, 0xec, 0x86, 0x50, 0x00, 0xae, + 0xda, 0xd1, 0xa9, 0x6b, 0x43, 0x7a, 0xa9, 0x44, 0x37, 0xc1, 0xc1, 0xf6, 0xfa, 0x8c, 0xa3, 0xe8, 0x47, 0xab, 0x91, + 0x6f, 0xa3, 0xea, 0xb1, 0x18, 0xc4, 0x8f, 0x4b, 0xba, 0x8c, 0xaa, 0xc7, 0xe5, 0x20, 0x7e, 0x2c, 0x9a, 0x66, 0xf7, + 0x5c, 0xd9, 0xdf, 0x47, 0xe4, 0x59, 0x77, 0xf6, 0x52, 0x01, 0x1b, 0x03, 0xcf, 0xae, 0x05, 0x84, 0x53, 0x70, 0x44, + 0xb6, 0x86, 0x3e, 0x74, 0x6e, 0xf7, 0xb1, 0x61, 0x92, 0x20, 0xe8, 0x79, 0x9b, 0x4d, 0xa2, 0xd8, 0x59, 0xff, 0xe8, + 0xc3, 0x29, 0x10, 0xd0, 0xed, 0xb6, 0x59, 0x57, 0x6b, 0x40, 0x31, 0x0d, 0xc7, 0xfc, 0xb0, 0x1c, 0xdd, 0xba, 0xee, + 0xfa, 0x87, 0xe5, 0x68, 0x49, 0x86, 0x13, 0x3d, 0xf9, 0xf1, 0xc9, 0x78, 0x16, 0x47, 0x93, 0xa6, 0xe3, 0xb4, 0x50, + 0xf8, 0xa7, 0xce, 0x2d, 0x14, 0x81, 0x53, 0x31, 0x82, 0x23, 0xa7, 0xca, 0x49, 0xc9, 0xc3, 0xf0, 0x3f, 0xa8, 0x77, + 0xb4, 0x69, 0xaf, 0xe3, 0x3a, 0x59, 0x66, 0xc5, 0x95, 0x0a, 0x1f, 0xae, 0xa2, 0x8b, 0x9b, 0x80, 0x76, 0xce, 0x65, + 0xda, 0xf2, 0xeb, 0xc4, 0xa3, 0x27, 0xb6, 0x66, 0x06, 0xdc, 0xba, 0x1b, 0xa1, 0x19, 0x02, 0xa3, 0xe5, 0xf9, 0x3b, + 0xc4, 0xdc, 0xfe, 0x55, 0xda, 0xfc, 0x4a, 0xda, 0x67, 0xc9, 0x48, 0xd1, 0x26, 0x23, 0x35, 0x18, 0x61, 0x8a, 0x22, + 0x89, 0xeb, 0xb0, 0x80, 0x20, 0xd8, 0x9f, 0x51, 0x5c, 0x8b, 0xa5, 0x77, 0x1a, 0x84, 0x09, 0xa6, 0x0b, 0xca, 0xaf, + 0x6e, 0xe7, 0xb6, 0xd2, 0x62, 0x8f, 0xe4, 0xf7, 0xb9, 0xb5, 0x5d, 0x51, 0xe4, 0xef, 0xf3, 0x06, 0xd4, 0x03, 0xa2, + 0xdc, 0xd7, 0x47, 0x29, 0x70, 0xd2, 0xe2, 0x86, 0x02, 0xa3, 0x17, 0x74, 0x75, 0x22, 0x77, 0xec, 0xd4, 0x9c, 0xa9, + 0x98, 0xc9, 0xb8, 0xf2, 0x7e, 0xcf, 0xdc, 0x07, 0x4d, 0x41, 0x2b, 0x30, 0xf0, 0xd6, 0x67, 0x3c, 0x3a, 0xd0, 0xdd, + 0x6a, 0x9d, 0x16, 0x6c, 0x16, 0xd4, 0x65, 0xdd, 0xb6, 0xf1, 0xa0, 0x11, 0x07, 0x45, 0xb2, 0x2a, 0x54, 0x4b, 0x78, + 0x22, 0x10, 0x30, 0x65, 0xd7, 0x3c, 0xd2, 0x82, 0x9a, 0xde, 0x84, 0xc2, 0x86, 0x82, 0xbf, 0x52, 0x54, 0xd3, 0x9b, + 0x50, 0x9f, 0x89, 0x53, 0x0c, 0x22, 0x98, 0x11, 0x9b, 0xfd, 0x16, 0x50, 0x7f, 0x6b, 0x46, 0x9b, 0xa6, 0x31, 0xda, + 0x2a, 0xe4, 0x92, 0x22, 0x69, 0xf9, 0x6f, 0xd5, 0x54, 0x50, 0x52, 0xcb, 0x45, 0x6f, 0xe2, 0xbb, 0xe8, 0xf1, 0x4c, + 0x4b, 0x02, 0xa5, 0x5b, 0xee, 0x18, 0xfd, 0x21, 0x0c, 0xf0, 0x88, 0x8e, 0x13, 0x0b, 0xe6, 0x56, 0x27, 0x2c, 0x9b, + 0x57, 0x8b, 0xd1, 0x4a, 0x40, 0xd8, 0xe0, 0x63, 0x96, 0xcd, 0x0b, 0xf5, 0x10, 0xba, 0xc2, 0xd2, 0xb7, 0x60, 0x17, + 0x1b, 0xac, 0x44, 0x15, 0x80, 0xef, 0x05, 0xdd, 0xac, 0x44, 0x15, 0x09, 0xd9, 0xfd, 0xb8, 0xc1, 0x12, 0x64, 0x5a, + 0x29, 0xd3, 0x92, 0x06, 0x0b, 0x02, 0x5f, 0x55, 0x55, 0x3e, 0x24, 0xdb, 0x0a, 0xe4, 0x53, 0x47, 0x0d, 0x38, 0x05, + 0xb2, 0x0a, 0x2c, 0x48, 0x80, 0xca, 0xd0, 0x56, 0x81, 0x69, 0x25, 0xa6, 0xe9, 0x2a, 0x6c, 0x94, 0xd9, 0xa1, 0xd0, + 0xeb, 0x25, 0x9f, 0xc5, 0x83, 0x30, 0x19, 0xc6, 0xe4, 0x31, 0x42, 0xed, 0x1f, 0xe6, 0x51, 0xac, 0xe4, 0x92, 0x2b, + 0xeb, 0x17, 0x7f, 0xfb, 0x09, 0x7b, 0xdd, 0x73, 0x0c, 0x16, 0x60, 0x2d, 0x6d, 0xaf, 0xb3, 0xe2, 0x9d, 0x68, 0x05, + 0xc7, 0xc1, 0x2c, 0xd2, 0x61, 0xd5, 0x91, 0x23, 0xea, 0x8b, 0x5c, 0x7b, 0x17, 0x21, 0x72, 0x90, 0xde, 0x63, 0x80, + 0xdd, 0x08, 0x5f, 0x87, 0xc6, 0xe6, 0x56, 0x55, 0x88, 0xbf, 0x51, 0x22, 0xf1, 0x93, 0x10, 0x1f, 0xd7, 0x2b, 0x99, + 0xab, 0xd6, 0x78, 0xac, 0xaa, 0x19, 0x3c, 0x53, 0xbe, 0xc7, 0xca, 0xbf, 0xb5, 0xdd, 0x1c, 0xe7, 0x3d, 0x78, 0xd0, + 0xba, 0xdf, 0x3a, 0x12, 0x42, 0x73, 0xe5, 0x24, 0x4d, 0x47, 0x8d, 0x8e, 0x99, 0xac, 0x16, 0x95, 0x30, 0xb9, 0x3b, + 0xa5, 0x63, 0xa0, 0xa2, 0x03, 0xb8, 0x96, 0xa8, 0x0e, 0x7a, 0x52, 0xb2, 0x31, 0x1c, 0x71, 0x06, 0x07, 0xed, 0x38, + 0x46, 0xf1, 0x72, 0x2e, 0xc5, 0xcb, 0xf9, 0x09, 0xe3, 0x00, 0xad, 0x05, 0x48, 0xf5, 0x0a, 0xf6, 0x33, 0x97, 0xb0, + 0xc0, 0xfa, 0xce, 0x77, 0x64, 0x80, 0x0c, 0x71, 0xb2, 0x39, 0x4e, 0xf6, 0xb8, 0x51, 0x73, 0x5f, 0xe1, 0xe3, 0xa4, + 0x59, 0x38, 0x75, 0x15, 0xed, 0xba, 0x96, 0xac, 0x98, 0x97, 0x83, 0x09, 0x04, 0x65, 0x29, 0xe6, 0xe5, 0x70, 0xb2, + 0xa0, 0x39, 0xfc, 0x58, 0x78, 0xe8, 0x10, 0xcb, 0x41, 0x02, 0x97, 0xce, 0x1e, 0x03, 0xde, 0x50, 0x28, 0x71, 0x37, + 0xd6, 0x91, 0x63, 0x1d, 0xe5, 0x61, 0x18, 0x03, 0xae, 0xac, 0x15, 0x78, 0xef, 0xbf, 0x3e, 0xfa, 0x80, 0xac, 0xca, + 0x15, 0x5e, 0x8e, 0x72, 0xd7, 0x95, 0x46, 0x5d, 0x52, 0x7a, 0x95, 0x13, 0x3c, 0x95, 0x6c, 0xb7, 0x3d, 0x63, 0x4f, + 0xe5, 0x20, 0xf1, 0x8e, 0x11, 0xbd, 0x98, 0x7a, 0x99, 0x39, 0x81, 0x33, 0xdb, 0x5e, 0xb6, 0x31, 0x3f, 0x76, 0x80, + 0x83, 0x45, 0x10, 0x12, 0x37, 0x84, 0x61, 0x62, 0x27, 0xc5, 0x50, 0x09, 0xe1, 0xba, 0x16, 0x5e, 0xc7, 0x69, 0x19, + 0x83, 0x8b, 0xb4, 0xb2, 0x4d, 0xdc, 0x43, 0xd7, 0x1d, 0x3f, 0xe6, 0x56, 0xc7, 0x68, 0xcb, 0x7c, 0xb8, 0xa3, 0xd3, + 0x07, 0x16, 0x03, 0x50, 0xf7, 0x60, 0x56, 0xb7, 0xcf, 0x24, 0xae, 0x4f, 0xbb, 0x8a, 0x90, 0x08, 0x44, 0x51, 0x2a, + 0x23, 0x4c, 0xff, 0x4e, 0x73, 0x59, 0x4d, 0xeb, 0x07, 0x79, 0xe6, 0x90, 0x67, 0xa1, 0xb3, 0x3d, 0x68, 0xed, 0xef, + 0x06, 0xed, 0xc4, 0x6d, 0xf7, 0xce, 0xff, 0x5b, 0xd6, 0xb5, 0xd5, 0x9a, 0xea, 0x71, 0xbb, 0xfa, 0x81, 0xb7, 0x57, + 0x7b, 0x32, 0x06, 0xcc, 0x4a, 0x38, 0x67, 0x54, 0xc5, 0xcb, 0x8c, 0x57, 0x78, 0x52, 0xad, 0x3c, 0x1f, 0xef, 0xdb, + 0x6c, 0xa4, 0x1f, 0xc8, 0x14, 0x10, 0xcf, 0x6f, 0x53, 0xad, 0x3e, 0x4e, 0x51, 0x02, 0xfe, 0x4e, 0xc5, 0x37, 0xa2, + 0x1f, 0xcd, 0x8b, 0x2b, 0x5e, 0xbf, 0xbd, 0x2d, 0xf4, 0x8b, 0xe7, 0x46, 0xe7, 0x4f, 0x5f, 0x97, 0x2e, 0x74, 0x38, + 0x6a, 0xef, 0xa0, 0xc8, 0x82, 0x55, 0x27, 0x13, 0x2d, 0x6b, 0xab, 0x66, 0x1f, 0x25, 0x5c, 0x4c, 0x54, 0xa3, 0x67, + 0x9d, 0x39, 0x61, 0x4a, 0xd0, 0x37, 0x8e, 0x51, 0xc9, 0x18, 0x96, 0x0c, 0xd4, 0x69, 0x52, 0x88, 0x1e, 0x56, 0x33, + 0x8c, 0x57, 0x0c, 0xa0, 0x30, 0xa5, 0x04, 0x51, 0xb4, 0x06, 0xc1, 0x40, 0x13, 0xfa, 0xfd, 0xdb, 0x54, 0x65, 0xa0, + 0x45, 0x33, 0x15, 0x20, 0xaa, 0x83, 0x68, 0xab, 0xbc, 0x0c, 0x7f, 0x5c, 0xd2, 0x22, 0xa3, 0x79, 0x45, 0x97, 0x15, + 0x4d, 0x32, 0x7a, 0xc9, 0xa5, 0xa8, 0x78, 0x5d, 0x31, 0x49, 0xdb, 0x35, 0x61, 0xff, 0x97, 0x47, 0xd7, 0x5b, 0xb1, + 0xd6, 0xd0, 0xee, 0x04, 0x19, 0xa1, 0xf9, 0x42, 0x05, 0x21, 0x43, 0xe5, 0x24, 0x74, 0xad, 0x56, 0x78, 0x05, 0x36, + 0x99, 0x66, 0xa3, 0x65, 0x5c, 0x85, 0x81, 0xf9, 0x2a, 0x30, 0x98, 0x1c, 0x98, 0x74, 0xb6, 0xbe, 0x78, 0x26, 0xae, + 0x57, 0xa2, 0xe0, 0x45, 0x2d, 0x21, 0xfa, 0x35, 0xee, 0xbb, 0x8e, 0xab, 0xce, 0xfc, 0x5a, 0xe9, 0x43, 0xdf, 0xba, + 0xac, 0x8d, 0xfd, 0x42, 0xe3, 0x18, 0xec, 0x7c, 0x44, 0x34, 0xa4, 0x41, 0xad, 0x5a, 0x1c, 0xea, 0x00, 0x5d, 0x2a, + 0xa4, 0x90, 0x21, 0x53, 0x99, 0x2c, 0x41, 0xc6, 0x37, 0x7e, 0x2f, 0x44, 0x3d, 0xfa, 0x73, 0xcd, 0xcb, 0xfb, 0x33, + 0x9e, 0x73, 0x1c, 0xa3, 0x20, 0x89, 0x8b, 0x9b, 0xb8, 0x0a, 0x88, 0x6b, 0x79, 0x15, 0x1c, 0xa5, 0x3a, 0x6c, 0xcc, + 0x4e, 0xd5, 0xa8, 0xf5, 0x12, 0xe8, 0x2b, 0x23, 0x7d, 0x63, 0x30, 0x34, 0x11, 0x95, 0xd0, 0xf7, 0x4a, 0xdd, 0xd3, + 0xea, 0x86, 0x01, 0xc4, 0x9f, 0x4b, 0xbd, 0x50, 0xeb, 0xb5, 0x1f, 0x73, 0x43, 0x47, 0x08, 0x1a, 0x7d, 0xd5, 0x2c, + 0x1a, 0xc7, 0x2d, 0x4d, 0x46, 0xc6, 0x8d, 0x36, 0x39, 0xbf, 0x02, 0x19, 0x9f, 0x35, 0x17, 0x9a, 0x34, 0x0d, 0x95, + 0x50, 0x85, 0xd1, 0xe6, 0xce, 0x4b, 0xa7, 0xf7, 0xe0, 0xce, 0xa6, 0xcd, 0x8e, 0x94, 0x4b, 0x63, 0x43, 0x4b, 0x5e, + 0xad, 0x44, 0x51, 0x41, 0x18, 0xe7, 0xde, 0x98, 0x5e, 0xc7, 0x59, 0x51, 0xc7, 0x59, 0x71, 0x5a, 0xad, 0x78, 0x52, + 0xbf, 0x87, 0x5b, 0x9c, 0xb4, 0xba, 0x69, 0x2a, 0xb8, 0xd2, 0x25, 0x07, 0x18, 0x4c, 0x4d, 0xc6, 0x3d, 0xb6, 0x06, + 0x17, 0xf5, 0xef, 0xd1, 0x52, 0x60, 0x2c, 0x54, 0x55, 0x7c, 0x7c, 0x51, 0x89, 0x7c, 0x5d, 0x83, 0x76, 0xf7, 0xb2, + 0x8e, 0x8e, 0x9e, 0xac, 0xee, 0xa6, 0xf2, 0x06, 0x13, 0x3d, 0x39, 0x5a, 0xdd, 0xf5, 0xb2, 0xeb, 0x95, 0x28, 0xeb, + 0xb8, 0xa8, 0xa7, 0x12, 0x91, 0x2c, 0x89, 0xf3, 0x24, 0x9c, 0x8c, 0xc7, 0x5f, 0x1c, 0x0c, 0x0f, 0x20, 0x03, 0x99, + 0xfe, 0x35, 0x94, 0x2e, 0x47, 0xc3, 0xc9, 0x78, 0x3c, 0x15, 0xf2, 0x6e, 0x17, 0x8d, 0x26, 0x0d, 0xd6, 0x33, 0x4c, + 0xd4, 0xcc, 0x8c, 0xf8, 0xdd, 0x2a, 0x2e, 0x52, 0x88, 0x5f, 0xa7, 0x8a, 0x3f, 0x7a, 0x32, 0xf6, 0xca, 0x37, 0x9f, + 0x3e, 0x6d, 0x7e, 0x6f, 0x74, 0x58, 0x6b, 0xdd, 0xee, 0x67, 0xbf, 0x1f, 0xcb, 0xf9, 0x3e, 0x39, 0x3e, 0x54, 0x3f, + 0x7e, 0x6f, 0x9a, 0xe9, 0xeb, 0x32, 0x9c, 0xff, 0x33, 0x94, 0xf3, 0x79, 0x5a, 0x96, 0xf1, 0x7d, 0x43, 0x16, 0x74, + 0x5d, 0x59, 0x6f, 0x12, 0xea, 0x6c, 0x03, 0x7a, 0x44, 0xa6, 0xeb, 0x8a, 0xc1, 0x37, 0xef, 0xeb, 0x30, 0xe0, 0xd5, + 0x6a, 0xc8, 0x8b, 0x3a, 0xab, 0xef, 0x87, 0x98, 0x27, 0xc0, 0x4f, 0x35, 0x6f, 0xf6, 0xac, 0xd4, 0xc4, 0xe6, 0xb2, + 0xe4, 0xfc, 0x2f, 0x1e, 0x4a, 0xe3, 0xe8, 0x31, 0x1a, 0x47, 0x8f, 0xa9, 0x1c, 0x8c, 0xc9, 0xd7, 0x54, 0x75, 0x66, + 0xf2, 0x35, 0x98, 0x20, 0x65, 0xed, 0x6f, 0xa4, 0x71, 0x62, 0x34, 0xa6, 0x37, 0x2f, 0xf3, 0x6c, 0x05, 0x4c, 0xf0, + 0x52, 0xfd, 0x68, 0x08, 0x7d, 0xcf, 0xdb, 0xd9, 0x47, 0xa3, 0xd1, 0xb3, 0x92, 0x8e, 0x46, 0xa3, 0x8f, 0x59, 0x43, + 0xe8, 0x65, 0xd5, 0xf1, 0xfe, 0x3d, 0xa7, 0x17, 0x22, 0xbd, 0x8f, 0x82, 0x80, 0x2e, 0xb3, 0x34, 0xe5, 0x85, 0x2c, + 0xeb, 0x2c, 0x6d, 0xe7, 0x95, 0x2d, 0x44, 0xe0, 0x1f, 0xd5, 0x46, 0x84, 0x20, 0x22, 0xf4, 0xed, 0x4e, 0xcf, 0x46, + 0xa3, 0xd1, 0x59, 0xaa, 0xab, 0xb5, 0x0c, 0xf9, 0x6b, 0x34, 0x1f, 0xb0, 0x76, 0xf9, 0x60, 0x7d, 0xa3, 0xa3, 0x9d, + 0x1c, 0xfe, 0xf7, 0x70, 0x36, 0x1f, 0x0f, 0xbf, 0x1d, 0x2d, 0x1e, 0x1f, 0xd2, 0x20, 0x70, 0x41, 0xab, 0x43, 0x65, + 0xcd, 0x31, 0x2d, 0x8e, 0xc7, 0x53, 0x52, 0x0c, 0xd8, 0x13, 0xe3, 0x4b, 0xf3, 0xc5, 0x13, 0x40, 0x22, 0x45, 0x11, + 0x6a, 0x60, 0xa5, 0x7f, 0x78, 0x15, 0x79, 0x55, 0x00, 0x3e, 0x9a, 0x89, 0x64, 0xa0, 0xb5, 0x80, 0xe3, 0x08, 0xca, + 0x6b, 0x8c, 0x69, 0x44, 0x8f, 0xb1, 0x4c, 0x47, 0x05, 0x1d, 0x4f, 0xab, 0xdb, 0xac, 0x4e, 0x96, 0x18, 0xd8, 0x28, + 0xae, 0x78, 0xf0, 0x45, 0x10, 0x15, 0xec, 0xe8, 0xe9, 0x54, 0xc2, 0xfb, 0x62, 0x52, 0xca, 0xaf, 0x20, 0xf1, 0xdb, + 0x31, 0x42, 0xa0, 0x12, 0xe5, 0xb1, 0x88, 0x35, 0xbe, 0xcc, 0x45, 0x0c, 0x1e, 0x9c, 0x95, 0xe2, 0x59, 0xcc, 0x49, + 0x60, 0xec, 0x2f, 0x5a, 0xcd, 0x11, 0xd0, 0x9c, 0x50, 0x30, 0x71, 0x58, 0x50, 0xf1, 0xc5, 0x04, 0xbd, 0x82, 0xc0, + 0xad, 0x3a, 0x82, 0xe3, 0xce, 0x58, 0x36, 0xa8, 0xe5, 0x93, 0xb2, 0xc3, 0xf9, 0xff, 0xae, 0xe8, 0x62, 0x70, 0x68, + 0x87, 0xe6, 0xad, 0x72, 0x5f, 0xad, 0x91, 0x51, 0xaa, 0xc3, 0x67, 0x29, 0x31, 0xc6, 0xa7, 0x9c, 0x9d, 0x6c, 0x74, + 0x77, 0x46, 0x75, 0x99, 0x5d, 0x87, 0x44, 0xf5, 0xca, 0x82, 0x62, 0x06, 0x51, 0x36, 0xc2, 0xf5, 0x03, 0xd6, 0x22, + 0x4e, 0x27, 0x6f, 0x78, 0x59, 0x67, 0x89, 0x7c, 0x7f, 0xe3, 0xbd, 0x07, 0x6a, 0x20, 0x1b, 0xf4, 0xae, 0x64, 0x30, + 0xcf, 0x6f, 0x4b, 0x00, 0xed, 0xac, 0x78, 0x79, 0xc3, 0x5d, 0xba, 0x11, 0x04, 0x8d, 0x6d, 0xe6, 0x95, 0x17, 0x6c, + 0x02, 0xbe, 0x7a, 0x57, 0x02, 0xe6, 0x46, 0x08, 0x52, 0x53, 0x08, 0x85, 0x03, 0x17, 0xf8, 0xba, 0x2e, 0xb3, 0x8b, + 0x75, 0xcd, 0x31, 0xd8, 0x47, 0x61, 0xb5, 0x98, 0xd2, 0x09, 0x8f, 0x87, 0x01, 0xfe, 0x08, 0xa8, 0x0c, 0xb8, 0xa1, + 0x3d, 0xec, 0xe0, 0x85, 0xfc, 0x65, 0xdf, 0xc8, 0x3d, 0xc2, 0x5e, 0xa7, 0x21, 0x04, 0xd7, 0xc1, 0x87, 0x00, 0x96, + 0x14, 0xa1, 0x6f, 0xf1, 0x54, 0x0d, 0x83, 0xcb, 0x3c, 0x5b, 0xa9, 0xa4, 0x7a, 0xd4, 0xd1, 0x7c, 0x28, 0xb5, 0x23, + 0x39, 0xa0, 0x4e, 0x7a, 0x8c, 0xe9, 0xa5, 0x4c, 0x97, 0x45, 0x59, 0x23, 0x94, 0x77, 0x6a, 0x62, 0x6c, 0x98, 0x3e, + 0x0e, 0x91, 0x5f, 0xde, 0x95, 0x32, 0xf4, 0x0b, 0x5f, 0x00, 0xf8, 0x15, 0xdc, 0xee, 0x77, 0xe3, 0xbb, 0xc8, 0xec, + 0xe7, 0x9c, 0x1d, 0xfe, 0xf7, 0x3c, 0x1e, 0xfe, 0x35, 0x1e, 0x7e, 0xbb, 0x18, 0x84, 0x43, 0xf3, 0x93, 0x3c, 0x7e, + 0x74, 0x48, 0x5f, 0x72, 0xc3, 0x95, 0xc0, 0xc2, 0xf7, 0x82, 0xdb, 0xc8, 0x95, 0x10, 0x44, 0x01, 0xde, 0x28, 0xec, + 0x6a, 0x9c, 0x00, 0xc0, 0x5f, 0xf0, 0x5f, 0x01, 0x1a, 0x09, 0xd9, 0x8b, 0x06, 0xe8, 0x07, 0xe4, 0xef, 0x93, 0xaf, + 0x3c, 0x03, 0x39, 0x10, 0x4f, 0xc8, 0x18, 0x28, 0x44, 0x95, 0x31, 0x91, 0xb0, 0xbf, 0x26, 0xfb, 0x76, 0xdb, 0x6b, + 0x4b, 0x7e, 0xf0, 0x4b, 0x37, 0xd3, 0x44, 0xcf, 0x3b, 0xdc, 0x50, 0x56, 0x62, 0x15, 0x22, 0x36, 0x9e, 0xfa, 0x95, + 0x33, 0x88, 0x35, 0x79, 0x93, 0x81, 0x0f, 0x83, 0xf9, 0x62, 0x3c, 0x03, 0x69, 0x11, 0xdc, 0x71, 0x4a, 0x7e, 0x99, + 0x81, 0x5b, 0x73, 0x11, 0xe3, 0x05, 0xdb, 0x2c, 0x89, 0x7e, 0xbf, 0x97, 0x67, 0x61, 0xae, 0x70, 0x96, 0xf3, 0x46, + 0x8b, 0xdd, 0x51, 0x27, 0x0c, 0xe2, 0x76, 0x35, 0x04, 0x43, 0x39, 0x04, 0x65, 0x47, 0x5b, 0x6c, 0xbd, 0xa6, 0x9e, + 0x52, 0xf7, 0x56, 0xd6, 0x57, 0x8e, 0xfe, 0x10, 0x59, 0x81, 0x85, 0xb4, 0x6b, 0x8e, 0x55, 0xcd, 0x31, 0xd2, 0x9e, + 0x7e, 0xbf, 0xf2, 0xc8, 0x4f, 0x67, 0xe1, 0x41, 0x20, 0x4b, 0x15, 0x3b, 0x65, 0x51, 0x6e, 0x4a, 0x73, 0xc6, 0xb0, + 0xa1, 0x79, 0x66, 0xe2, 0xba, 0xcc, 0x7a, 0xbd, 0x30, 0x44, 0x87, 0x46, 0x2c, 0x15, 0x6b, 0x83, 0xf0, 0x3e, 0x3a, + 0x61, 0x74, 0x0d, 0xb2, 0xba, 0xf0, 0x9c, 0x13, 0xe4, 0xcb, 0xc0, 0x64, 0x4d, 0x56, 0xeb, 0xe4, 0x84, 0x47, 0x2f, + 0x5f, 0x36, 0x82, 0x06, 0x39, 0x49, 0x51, 0x6f, 0x62, 0x77, 0xec, 0xa3, 0x16, 0x52, 0xe3, 0xa6, 0x99, 0xf6, 0x14, + 0xa9, 0xe8, 0xb1, 0x5e, 0x2d, 0x7f, 0x81, 0x65, 0x81, 0x21, 0x1f, 0x84, 0xf6, 0x14, 0xad, 0xc0, 0x0c, 0x37, 0x26, + 0x83, 0xa6, 0x1f, 0x16, 0x6d, 0x11, 0x3a, 0x23, 0xb7, 0x25, 0x84, 0x6d, 0x1b, 0x84, 0xb5, 0xf3, 0x44, 0xbe, 0x78, + 0xe2, 0x30, 0xc2, 0x21, 0xd7, 0x9b, 0xb9, 0xf2, 0x30, 0xcc, 0xaf, 0x85, 0xdf, 0x3c, 0xd5, 0x5c, 0x27, 0x2a, 0x66, + 0x05, 0xdb, 0xed, 0xb2, 0x22, 0xf8, 0xf7, 0x63, 0x36, 0xc3, 0xbf, 0x59, 0xbf, 0xdf, 0x0b, 0xf1, 0x17, 0xc7, 0xe0, + 0x3d, 0xf3, 0x6a, 0xc1, 0x3e, 0x82, 0x4c, 0x85, 0x44, 0x98, 0x2a, 0x8d, 0xdf, 0x58, 0x0d, 0x16, 0x70, 0xfa, 0x03, + 0x99, 0x0b, 0x33, 0x99, 0xcb, 0x8b, 0x6d, 0xc8, 0x69, 0x6b, 0x9c, 0xb2, 0x51, 0x96, 0x48, 0xd7, 0x85, 0x6c, 0x14, + 0xe7, 0x59, 0x5c, 0xf1, 0x6a, 0xbb, 0x55, 0x87, 0x63, 0x52, 0x72, 0xf4, 0x2b, 0x40, 0x2a, 0x55, 0xb0, 0x8e, 0x54, + 0x3b, 0xfe, 0x22, 0x2c, 0x71, 0x9f, 0xf2, 0x79, 0xb9, 0x30, 0x7b, 0x6b, 0x5e, 0x2e, 0x98, 0xbc, 0x95, 0xf6, 0xc2, + 0x12, 0x9a, 0x57, 0x10, 0xb2, 0xc1, 0x54, 0xc7, 0xa2, 0x35, 0x66, 0xd5, 0xbc, 0x5c, 0x40, 0x18, 0x99, 0x72, 0x01, + 0x36, 0x53, 0xbc, 0x00, 0x2f, 0x92, 0x18, 0x60, 0xe2, 0x62, 0x32, 0x85, 0x78, 0xe6, 0xb2, 0x9c, 0x78, 0xa1, 0xef, + 0x97, 0x89, 0x45, 0xca, 0x80, 0x57, 0x8d, 0x46, 0x13, 0x33, 0x0d, 0x47, 0x9d, 0x20, 0x27, 0x3a, 0xbf, 0x9b, 0x5a, + 0x11, 0x62, 0x4f, 0x1c, 0x01, 0x97, 0x15, 0xd3, 0x85, 0x17, 0x1d, 0x88, 0x31, 0x72, 0x70, 0x8a, 0x4f, 0x0c, 0x8e, + 0xc2, 0xe0, 0xdc, 0x38, 0x27, 0x48, 0x19, 0xc6, 0x64, 0x23, 0xd8, 0xb5, 0x08, 0xab, 0x79, 0xbc, 0x00, 0x65, 0x5d, + 0xbc, 0x00, 0xcb, 0x1a, 0x6d, 0x80, 0x09, 0xf2, 0x2a, 0xee, 0x84, 0x7e, 0xa2, 0xb8, 0x42, 0x84, 0x63, 0xe5, 0xfa, + 0xa8, 0x6c, 0x87, 0xbe, 0xc0, 0xeb, 0xbd, 0x34, 0xc7, 0xcd, 0x7a, 0x2c, 0x10, 0xd8, 0x10, 0x30, 0x36, 0x52, 0x69, + 0xb2, 0xb5, 0xf6, 0x8d, 0x9e, 0x07, 0x3e, 0xcd, 0x46, 0x85, 0xa8, 0xcf, 0x2f, 0x41, 0x84, 0xe2, 0xa2, 0xc1, 0x23, + 0xbf, 0x88, 0x3b, 0x4b, 0xbf, 0x35, 0x2d, 0x2a, 0xd8, 0xc9, 0x06, 0x40, 0xfa, 0x54, 0xb4, 0x28, 0x29, 0xa7, 0x28, + 0x48, 0x63, 0x37, 0x05, 0xac, 0x24, 0x77, 0x01, 0x43, 0xb0, 0xb1, 0x83, 0xca, 0xea, 0x14, 0x11, 0x49, 0x02, 0x91, + 0x15, 0xc3, 0x82, 0xe2, 0xd8, 0x16, 0x88, 0xfa, 0x69, 0xca, 0x32, 0x83, 0xa1, 0xa3, 0xe2, 0x3e, 0x4f, 0x1d, 0x4a, + 0x14, 0x04, 0x54, 0x0d, 0x39, 0x48, 0x6c, 0x4d, 0x03, 0xe1, 0x01, 0x79, 0x44, 0x67, 0xac, 0xbf, 0xcf, 0x3a, 0xcf, + 0x2e, 0x34, 0x47, 0xe5, 0x6a, 0x57, 0xe8, 0x31, 0xc2, 0x93, 0x4c, 0xc3, 0xe4, 0x3b, 0xe7, 0x99, 0x56, 0x53, 0xf4, + 0x1c, 0x7c, 0xb2, 0x53, 0x8c, 0x48, 0xb7, 0x67, 0xd0, 0x75, 0xf0, 0xaa, 0x0e, 0x1b, 0xed, 0x5a, 0x42, 0x48, 0xe8, + 0x5a, 0x14, 0x31, 0xeb, 0x19, 0x03, 0xea, 0xed, 0xb6, 0xa7, 0xe6, 0x6a, 0xff, 0xdc, 0x6d, 0xb7, 0x3d, 0xec, 0xd6, + 0xf3, 0xb4, 0xdb, 0x0a, 0xbc, 0x96, 0x1f, 0xb4, 0xc7, 0x9f, 0xdb, 0xf1, 0xe7, 0x1a, 0xc9, 0xa3, 0xb0, 0x34, 0xd3, + 0xd4, 0x07, 0xe1, 0x70, 0xd3, 0x7b, 0xaf, 0x49, 0xdf, 0x67, 0xa1, 0xa0, 0x97, 0x95, 0x57, 0x5d, 0x63, 0x4d, 0x2a, + 0x1f, 0x5c, 0xff, 0x0f, 0xaf, 0x02, 0x3c, 0xe2, 0xe4, 0xce, 0xde, 0xdb, 0xa0, 0xd2, 0x6b, 0x0b, 0x47, 0x8a, 0xd0, + 0x03, 0x92, 0xb0, 0xaf, 0x65, 0x2d, 0x6e, 0xf3, 0x2c, 0x7b, 0x98, 0x3e, 0xbd, 0x4a, 0x5d, 0xab, 0x7b, 0xbb, 0xcc, + 0x32, 0x7d, 0xe0, 0xd5, 0x14, 0x07, 0x34, 0xea, 0xa2, 0x7d, 0xd7, 0x59, 0x55, 0x81, 0x97, 0x07, 0x5c, 0x9f, 0xcf, + 0xb8, 0x0b, 0x37, 0x77, 0x55, 0xfb, 0x9b, 0xf4, 0x2c, 0x9b, 0x67, 0x8b, 0xed, 0x36, 0xc4, 0xbf, 0x5d, 0x2d, 0xb2, + 0x34, 0x79, 0x0e, 0x3a, 0x3c, 0x8c, 0xdc, 0xc3, 0x54, 0xe3, 0x9c, 0xcd, 0xff, 0xb2, 0xf2, 0x9c, 0x04, 0x4e, 0x81, + 0x5e, 0xcc, 0x1e, 0x81, 0x0c, 0x46, 0x3b, 0xf5, 0x57, 0x33, 0xb5, 0x66, 0x20, 0xfa, 0x56, 0x15, 0x01, 0x8e, 0x2e, + 0x36, 0x12, 0x8d, 0x2c, 0x38, 0x69, 0x08, 0x58, 0x6c, 0x9a, 0xf2, 0x3e, 0x18, 0xda, 0x56, 0x97, 0xf7, 0xce, 0x92, + 0xe6, 0xb8, 0x0e, 0xac, 0x6d, 0xbf, 0x1e, 0x62, 0x5d, 0x76, 0xbd, 0x40, 0xee, 0x97, 0x37, 0xb4, 0x37, 0x6e, 0x12, + 0x98, 0xb5, 0x4d, 0x63, 0x18, 0x3f, 0x53, 0xfa, 0x4f, 0x6a, 0x70, 0xa5, 0xf1, 0xd3, 0x5c, 0x5b, 0x25, 0x98, 0x7d, + 0xe3, 0xf8, 0x0e, 0x40, 0x38, 0x36, 0x97, 0x1e, 0x9f, 0x65, 0x0e, 0x3d, 0x06, 0xa2, 0xa3, 0x3f, 0x2a, 0xec, 0x47, + 0x66, 0xf7, 0xba, 0x01, 0xf0, 0xe6, 0x75, 0xbb, 0xa0, 0x79, 0xb1, 0x80, 0x40, 0xa2, 0x4e, 0x79, 0xa5, 0xe1, 0x33, + 0x63, 0x76, 0x05, 0x64, 0xa8, 0x24, 0x60, 0x93, 0xd4, 0x75, 0x2e, 0xc4, 0xb2, 0xc3, 0xd2, 0x7c, 0x24, 0x61, 0x27, + 0x21, 0xa0, 0xbd, 0x06, 0xc1, 0x2c, 0xf8, 0xaf, 0x60, 0x50, 0x0c, 0x82, 0x28, 0x88, 0x82, 0x80, 0x0c, 0x4a, 0xf8, + 0x85, 0x38, 0x63, 0x04, 0x63, 0x94, 0x40, 0x87, 0xdf, 0x71, 0xe6, 0x32, 0x22, 0x2f, 0xbc, 0x30, 0x96, 0x76, 0x00, + 0x2e, 0x84, 0xc8, 0x79, 0x8c, 0x3e, 0x16, 0xef, 0x38, 0xcb, 0x08, 0x7d, 0xe7, 0x9c, 0xca, 0x8f, 0xb8, 0x17, 0xdc, + 0x6e, 0x77, 0xd8, 0x5e, 0xf2, 0x30, 0xa3, 0xbd, 0x31, 0x7d, 0xc7, 0x49, 0x94, 0x79, 0xce, 0xc3, 0x1c, 0x7a, 0x56, + 0x1b, 0xd6, 0x8a, 0x6a, 0x72, 0x83, 0x62, 0x5d, 0x64, 0x99, 0xac, 0x0c, 0x57, 0xce, 0x69, 0x85, 0xeb, 0xce, 0xac, + 0x17, 0x90, 0x94, 0x55, 0x8a, 0xa5, 0x33, 0xe1, 0xab, 0x4d, 0xcb, 0x9e, 0xb7, 0x4e, 0x21, 0xa7, 0x21, 0x32, 0xfa, + 0xa1, 0x25, 0xa0, 0x9a, 0x56, 0x5c, 0xd5, 0xe0, 0xb2, 0xab, 0xdb, 0xc3, 0x75, 0x7b, 0x74, 0x33, 0x3e, 0x40, 0x8c, + 0x38, 0x8e, 0x2d, 0x03, 0xbb, 0x09, 0x8b, 0x67, 0x63, 0x7d, 0x5f, 0x76, 0xe9, 0xad, 0xad, 0xc5, 0x21, 0xac, 0x3d, + 0x67, 0x85, 0x84, 0x00, 0x69, 0xa9, 0x2b, 0xdd, 0x6e, 0x83, 0x00, 0x06, 0xb8, 0xdf, 0xef, 0x01, 0xd7, 0xaa, 0xd9, + 0x49, 0x7d, 0x6b, 0x36, 0xc4, 0x5e, 0x52, 0x78, 0x0c, 0x44, 0xa9, 0xf8, 0xcf, 0x20, 0xa0, 0x78, 0xee, 0x86, 0x60, + 0x5f, 0xc9, 0x4e, 0x36, 0x65, 0xbf, 0xff, 0xbc, 0xc4, 0x07, 0x94, 0x83, 0x82, 0x58, 0x57, 0xc5, 0xad, 0xd0, 0xec, + 0x93, 0xfc, 0x10, 0xc7, 0x22, 0xcf, 0x42, 0x4b, 0x58, 0x6a, 0x4d, 0x58, 0xb8, 0x64, 0xa4, 0x83, 0x38, 0x68, 0x48, + 0xe7, 0x60, 0xd5, 0x36, 0xd8, 0x70, 0xaf, 0xf7, 0xb2, 0x0a, 0x2b, 0x9a, 0x39, 0xc3, 0xf2, 0xde, 0x01, 0x00, 0xeb, + 0xf5, 0x70, 0xa1, 0x38, 0x64, 0xc2, 0x43, 0x9f, 0xc4, 0x97, 0x86, 0x5d, 0x9f, 0x29, 0x59, 0xc9, 0x68, 0x34, 0xaa, + 0x1b, 0x29, 0xf9, 0x30, 0xdf, 0xbd, 0x69, 0xa1, 0x56, 0x8a, 0x38, 0xe5, 0x29, 0x58, 0x7a, 0x6b, 0x4a, 0x37, 0x5f, + 0xd0, 0x15, 0x2f, 0x52, 0xf9, 0xd3, 0x41, 0x9b, 0xf4, 0x88, 0x6b, 0xe6, 0xeb, 0x2c, 0xcc, 0xf0, 0x43, 0xc0, 0x47, + 0xf3, 0x30, 0xb3, 0xe9, 0x0a, 0x96, 0x16, 0xc4, 0x91, 0x71, 0xc9, 0x43, 0x9b, 0x07, 0xb0, 0xfe, 0xf4, 0x21, 0x89, + 0x9f, 0xc2, 0xcf, 0x99, 0x4e, 0xeb, 0xf8, 0x0c, 0x67, 0x33, 0x2a, 0xe4, 0x8d, 0xa0, 0xfd, 0x1a, 0x12, 0x89, 0x46, + 0x36, 0xf6, 0x18, 0x8a, 0xd6, 0xdd, 0x06, 0xae, 0xfc, 0x86, 0xde, 0xb9, 0x34, 0x08, 0xb0, 0xad, 0xb1, 0x18, 0x38, + 0xe3, 0xf1, 0x07, 0xaa, 0x6a, 0xf4, 0x15, 0x45, 0x37, 0x4c, 0x26, 0x9a, 0x3b, 0x8e, 0xed, 0xa8, 0x76, 0x95, 0xad, + 0x58, 0x61, 0x6c, 0x79, 0xed, 0x5b, 0x5a, 0x9a, 0x12, 0x50, 0x0d, 0x86, 0x3b, 0x01, 0x7c, 0x46, 0x84, 0x3c, 0x10, + 0x44, 0xf7, 0xc1, 0x41, 0x73, 0x96, 0xe0, 0x79, 0x18, 0xc2, 0x1f, 0x58, 0x38, 0xb0, 0x2c, 0x65, 0x3f, 0x97, 0xd3, + 0x18, 0xce, 0xdd, 0x5c, 0xee, 0xf0, 0xd9, 0x12, 0x14, 0x79, 0x72, 0x4e, 0xf5, 0xe5, 0x2b, 0xf7, 0xf6, 0x7b, 0x4c, + 0x30, 0x8f, 0x9e, 0x6d, 0xf8, 0xad, 0xa6, 0xdb, 0xf8, 0xc2, 0xda, 0x81, 0x13, 0xe6, 0xc2, 0x69, 0x2e, 0xb6, 0x4b, + 0x0d, 0x71, 0xd7, 0x78, 0x42, 0x84, 0x57, 0x8a, 0x58, 0x64, 0x9e, 0x4c, 0xc7, 0x60, 0x63, 0xc8, 0x36, 0x95, 0xcf, + 0x94, 0x42, 0xbc, 0x9a, 0xca, 0x0b, 0x53, 0x2b, 0x95, 0x55, 0x1a, 0x61, 0xa6, 0x80, 0x45, 0x95, 0x81, 0xcf, 0x7e, + 0x8d, 0x14, 0xd7, 0xd4, 0xf3, 0x17, 0x2e, 0xdf, 0x4c, 0xb7, 0xd9, 0x7c, 0xfa, 0x32, 0x8f, 0xaf, 0xb6, 0xdb, 0xb0, + 0xfb, 0x05, 0x98, 0x5f, 0x56, 0x52, 0xa3, 0x06, 0x4e, 0x0f, 0x21, 0xfa, 0x39, 0xef, 0xc9, 0x39, 0xb1, 0x9c, 0x5c, + 0xbb, 0x79, 0xb3, 0x9d, 0x14, 0x2d, 0xb0, 0x80, 0x13, 0x17, 0xe9, 0x40, 0x4b, 0x05, 0xa7, 0x2c, 0xe3, 0x9d, 0x4d, + 0x6f, 0x29, 0x15, 0x5e, 0x2d, 0x14, 0x09, 0xa9, 0xed, 0xbd, 0xc4, 0x8c, 0x1a, 0x70, 0x4e, 0xf2, 0x0e, 0x02, 0x4e, + 0x6a, 0xaa, 0xb1, 0x46, 0x71, 0xaa, 0x13, 0x9c, 0x57, 0x6a, 0xe8, 0x12, 0xe5, 0xc4, 0x6d, 0xb7, 0x55, 0xd1, 0x42, + 0x7d, 0x3c, 0xc8, 0x59, 0x22, 0x8f, 0x07, 0x14, 0xba, 0xc8, 0xa3, 0x21, 0x5f, 0x90, 0x52, 0xad, 0x1c, 0xa5, 0x5a, + 0xdd, 0x95, 0x0c, 0x14, 0x72, 0x15, 0xe4, 0x0d, 0x31, 0xee, 0x5a, 0x99, 0xb7, 0xb8, 0x72, 0x42, 0x4a, 0x93, 0xf0, + 0xb9, 0xa5, 0x18, 0x58, 0xc1, 0xde, 0x98, 0xba, 0xc2, 0x25, 0x42, 0xdb, 0xdd, 0x86, 0x98, 0x64, 0xb0, 0x6e, 0xb6, + 0xdb, 0x57, 0x65, 0x38, 0xcf, 0x16, 0x54, 0x8c, 0xb2, 0x14, 0x21, 0xc4, 0xb4, 0x87, 0xae, 0xe9, 0x82, 0x9e, 0x18, + 0x6a, 0xdb, 0xe3, 0x24, 0xe9, 0x62, 0x4d, 0x92, 0x18, 0xc5, 0x17, 0xa2, 0x94, 0x6b, 0x8d, 0x10, 0x3c, 0xdc, 0xff, + 0x48, 0x21, 0x86, 0x9b, 0x5e, 0x77, 0xbf, 0xee, 0xdc, 0x10, 0xff, 0x80, 0x40, 0x02, 0x05, 0x7b, 0x55, 0x8e, 0x2e, + 0xb2, 0x22, 0xc5, 0x9d, 0x2a, 0xa3, 0xe2, 0xca, 0x75, 0xe0, 0xb7, 0xdc, 0xf0, 0xaf, 0x86, 0x28, 0x40, 0x5c, 0xe3, + 0x4a, 0x31, 0x9e, 0xb5, 0xb5, 0x14, 0xc9, 0x28, 0x36, 0x24, 0x2a, 0x9c, 0xa8, 0xe8, 0x2e, 0x4f, 0xa3, 0x7b, 0x68, + 0xd7, 0x20, 0xb8, 0x6a, 0xee, 0x6c, 0xa4, 0xf9, 0x82, 0x10, 0x39, 0x01, 0x02, 0x36, 0xaa, 0x3e, 0xb5, 0x56, 0xd5, + 0xc3, 0xac, 0xf2, 0xb9, 0x3a, 0x88, 0x57, 0x15, 0xf0, 0xb0, 0xce, 0xf6, 0xbe, 0xaa, 0x1c, 0xd6, 0x06, 0xdf, 0x6e, + 0xb7, 0xab, 0x6a, 0x1e, 0x04, 0x0e, 0xa3, 0xf9, 0x9d, 0x94, 0x98, 0xf7, 0xc6, 0x14, 0x56, 0xbc, 0xeb, 0xd2, 0xd6, + 0x4d, 0x6a, 0x8d, 0x05, 0xea, 0x0e, 0xd7, 0x07, 0x3c, 0x4f, 0x81, 0xa3, 0x1d, 0x15, 0x53, 0x61, 0x74, 0xe5, 0xd8, + 0x95, 0x0a, 0x03, 0x43, 0xff, 0x90, 0xb2, 0x0d, 0x98, 0xe3, 0x81, 0xb5, 0x0d, 0xfa, 0x29, 0x49, 0x2d, 0xcc, 0x18, + 0x8d, 0x59, 0xc4, 0xba, 0x8e, 0x8e, 0xb8, 0x8a, 0xde, 0xce, 0xa3, 0xbf, 0x3d, 0x1d, 0xd3, 0x32, 0x2e, 0x52, 0x71, + 0x0d, 0x2a, 0x08, 0x50, 0x86, 0xa0, 0xe1, 0xbf, 0xa2, 0x06, 0xa0, 0x41, 0xb0, 0x03, 0xf0, 0x8f, 0x4e, 0xa7, 0x41, + 0x53, 0x93, 0x8b, 0x49, 0x2a, 0x8b, 0x9c, 0xb5, 0xa1, 0xcc, 0x64, 0x72, 0x48, 0x1e, 0x17, 0x80, 0xe7, 0x88, 0xcd, + 0x92, 0x36, 0x17, 0x72, 0xb3, 0xc9, 0xd7, 0x92, 0x1d, 0xb9, 0xf3, 0x8a, 0xd6, 0x6b, 0x51, 0xd9, 0x49, 0xcc, 0x17, + 0xd3, 0x3b, 0x23, 0x0c, 0x9c, 0xea, 0xd6, 0xdc, 0xee, 0x40, 0xa7, 0x99, 0xfa, 0x74, 0x6e, 0x02, 0xc4, 0x01, 0x86, + 0xeb, 0x6e, 0x7e, 0xbb, 0x20, 0xf4, 0x8e, 0xdd, 0x19, 0xb1, 0xea, 0xad, 0x91, 0x8b, 0xe8, 0xb4, 0xdb, 0xc1, 0x04, + 0x2e, 0xe3, 0xac, 0x34, 0x2f, 0x94, 0xba, 0xa1, 0xec, 0x68, 0x9b, 0x30, 0x9f, 0x77, 0xb4, 0x1b, 0x2e, 0xf8, 0x46, + 0xac, 0x63, 0xdd, 0x90, 0xa6, 0x12, 0x3d, 0x3a, 0x50, 0xdb, 0x21, 0xa0, 0x39, 0x1b, 0xd3, 0x25, 0x40, 0x6d, 0xc2, + 0x7e, 0x59, 0x83, 0x59, 0xca, 0x25, 0xf4, 0xb5, 0xdb, 0x27, 0xf9, 0x52, 0xf6, 0xa4, 0x72, 0x96, 0x28, 0xf8, 0x72, + 0xa4, 0xe0, 0x95, 0x95, 0xf3, 0x58, 0xcf, 0x21, 0xe0, 0xb1, 0xc8, 0x12, 0x9d, 0x93, 0xe2, 0x0a, 0x94, 0xa9, 0x70, + 0x04, 0xea, 0xaa, 0x11, 0x4b, 0x38, 0xc0, 0xed, 0xc5, 0xd3, 0x80, 0x50, 0x90, 0xea, 0xae, 0xcd, 0x8a, 0xbc, 0x63, + 0x27, 0x9b, 0x3b, 0x30, 0x8b, 0xad, 0xd7, 0x55, 0xeb, 0x2b, 0x93, 0x6c, 0x3f, 0x6e, 0x08, 0xb6, 0xdd, 0x91, 0xf2, + 0x85, 0x77, 0xf4, 0x96, 0x6c, 0x6e, 0xfb, 0xfd, 0x10, 0xfa, 0x43, 0xa8, 0xea, 0xd0, 0x5d, 0x67, 0x87, 0xee, 0x5c, + 0xe6, 0xd7, 0xe8, 0xf9, 0xa4, 0x37, 0xc4, 0x07, 0x34, 0xd1, 0xa2, 0xab, 0xf8, 0x1e, 0x36, 0x75, 0x54, 0x53, 0x59, + 0x79, 0x94, 0x50, 0x50, 0x01, 0x67, 0xbc, 0x3a, 0xe3, 0x18, 0xdb, 0x54, 0x3d, 0xbd, 0x53, 0xbc, 0xda, 0x5a, 0xaf, + 0xcd, 0x6a, 0x7d, 0x01, 0x16, 0x01, 0x17, 0x3c, 0xba, 0x56, 0xb4, 0xe4, 0xca, 0x61, 0xea, 0xcf, 0x71, 0x54, 0x82, + 0xcb, 0x38, 0xcb, 0x79, 0x1a, 0xd0, 0x4b, 0xbf, 0xff, 0xa1, 0xb2, 0x95, 0x5a, 0x7a, 0x67, 0xee, 0x4d, 0x48, 0x36, + 0xff, 0x63, 0x03, 0xf5, 0x3a, 0xc4, 0x88, 0xa8, 0x7a, 0x41, 0xbf, 0x65, 0x10, 0x1b, 0x33, 0xa8, 0xd6, 0x49, 0xc2, + 0xab, 0x2a, 0xd0, 0x4a, 0xad, 0x35, 0x5b, 0xeb, 0xf3, 0xec, 0x11, 0x3b, 0x79, 0xd4, 0x63, 0xec, 0x8e, 0xd0, 0x44, + 0xe9, 0x84, 0x74, 0x8d, 0x91, 0xa3, 0x05, 0x52, 0x1d, 0x8a, 0xb2, 0xcb, 0xf0, 0x2d, 0x0a, 0x59, 0xda, 0xfb, 0x5c, + 0x9f, 0xc8, 0xf2, 0x1b, 0x65, 0x74, 0x11, 0xc9, 0x44, 0x90, 0x8d, 0xdf, 0x22, 0x60, 0x2f, 0x34, 0x3b, 0x20, 0x9b, + 0x25, 0x3b, 0xa3, 0xe7, 0xc6, 0x04, 0x06, 0x5e, 0xbf, 0x95, 0x89, 0x7a, 0x94, 0x25, 0xd1, 0x95, 0x46, 0x2e, 0xf7, + 0x21, 0x89, 0xce, 0x43, 0xe2, 0xe6, 0x86, 0xa5, 0x75, 0x13, 0xa2, 0x98, 0x59, 0x6f, 0x78, 0xd9, 0xdd, 0x47, 0xde, + 0xb6, 0xd2, 0x3e, 0xd5, 0x77, 0x26, 0x8d, 0x4c, 0xa1, 0xaf, 0xc3, 0x49, 0xbf, 0x0f, 0x7f, 0x15, 0xfd, 0xc0, 0x5b, + 0x0a, 0xfe, 0x62, 0x8f, 0x48, 0x93, 0xb0, 0x00, 0xe0, 0x08, 0x73, 0x5e, 0xfb, 0x13, 0xf8, 0x88, 0x9d, 0x6c, 0x1e, + 0x85, 0x67, 0xde, 0xcc, 0xdd, 0x87, 0x78, 0xa9, 0x4a, 0x7a, 0xce, 0x3c, 0xe9, 0x81, 0x58, 0x85, 0x7a, 0xbf, 0xde, + 0x31, 0xa3, 0x4f, 0x00, 0x22, 0x75, 0x67, 0x1c, 0x4a, 0xf1, 0x63, 0xdd, 0x65, 0xb2, 0x49, 0x59, 0x9b, 0x89, 0x92, + 0x2a, 0x12, 0x7f, 0x11, 0x40, 0xbf, 0x61, 0x38, 0x1a, 0x80, 0xf7, 0x56, 0x63, 0xaf, 0x87, 0xc6, 0x19, 0x53, 0x4d, + 0xcf, 0x36, 0x6a, 0x79, 0x5b, 0x0a, 0xa1, 0xc7, 0x22, 0xba, 0xb3, 0xc7, 0x62, 0x78, 0x46, 0xdf, 0x42, 0x85, 0xaf, + 0x43, 0x8c, 0xa6, 0x4b, 0x9a, 0x66, 0xba, 0x96, 0x5b, 0xe9, 0x96, 0xd0, 0x1c, 0xa3, 0xf8, 0x38, 0x6d, 0xbb, 0xa7, + 0x5a, 0x68, 0x4f, 0x28, 0x0f, 0xef, 0x68, 0x4d, 0x6f, 0x0d, 0x8b, 0x60, 0x91, 0x96, 0x9d, 0xfc, 0x84, 0x5e, 0x38, + 0x02, 0x93, 0xb2, 0xad, 0x01, 0xfc, 0x01, 0xf5, 0xc3, 0x59, 0x33, 0x35, 0x52, 0x0e, 0x47, 0xe1, 0x4b, 0x36, 0x20, + 0x57, 0x50, 0x8b, 0x35, 0x66, 0x27, 0x31, 0xe8, 0xa0, 0x76, 0x76, 0x87, 0x33, 0x29, 0x88, 0x64, 0x44, 0x73, 0x0b, + 0xf1, 0xf4, 0x0f, 0xd0, 0xf4, 0x41, 0x5a, 0x98, 0xd2, 0x35, 0x0a, 0x78, 0x40, 0xdf, 0xd4, 0xef, 0xe7, 0xf8, 0xdc, + 0x38, 0x96, 0x58, 0xd8, 0xe3, 0x25, 0xa1, 0x4b, 0x27, 0x6e, 0x14, 0x48, 0x9b, 0x2d, 0xab, 0x00, 0xac, 0x48, 0x02, + 0x8d, 0x48, 0xd0, 0x52, 0xc7, 0x8a, 0xcb, 0x36, 0x68, 0x40, 0x12, 0x15, 0x14, 0xb2, 0x44, 0x02, 0xf8, 0x61, 0x04, + 0x21, 0x8a, 0x62, 0x10, 0xf7, 0xaa, 0xe5, 0x15, 0x37, 0x54, 0x83, 0x13, 0x45, 0x30, 0xc1, 0x2a, 0x9d, 0x02, 0xb1, + 0x2d, 0xd6, 0x2b, 0xf0, 0xbc, 0xb4, 0x17, 0x49, 0x64, 0x09, 0xd0, 0x20, 0xcd, 0x95, 0x4e, 0xdb, 0xe5, 0xed, 0x88, + 0x96, 0x6a, 0x36, 0x77, 0x5e, 0x2c, 0x0c, 0xf7, 0x58, 0xbb, 0xdb, 0x81, 0xf6, 0xc2, 0x7a, 0x47, 0x44, 0x0d, 0x56, + 0x76, 0x6d, 0xbb, 0x36, 0x94, 0x86, 0xaa, 0x5e, 0x59, 0x26, 0xa0, 0xa6, 0xab, 0xb8, 0x5e, 0x46, 0xd9, 0x08, 0xfe, + 0x6c, 0xb7, 0xc1, 0x61, 0x00, 0x16, 0x90, 0xbf, 0xbc, 0xff, 0x29, 0xc2, 0xf0, 0x4c, 0xbf, 0xbc, 0xff, 0x69, 0xbb, + 0x7d, 0x3a, 0x1e, 0x6b, 0xae, 0xc0, 0xaa, 0x75, 0x80, 0x3f, 0xd0, 0x6c, 0x83, 0x59, 0xb2, 0xdb, 0xed, 0x53, 0xe0, + 0x20, 0x24, 0xdb, 0xa0, 0x77, 0xb1, 0x74, 0xe4, 0x92, 0xac, 0x86, 0xda, 0x91, 0x80, 0x55, 0xb7, 0xc3, 0x52, 0xec, + 0x52, 0x1f, 0x19, 0x82, 0x51, 0x2d, 0xfa, 0x17, 0x9d, 0x02, 0x4b, 0x0a, 0xa6, 0xab, 0xc1, 0xb2, 0xae, 0x57, 0x55, + 0x74, 0x78, 0x18, 0xaf, 0xb2, 0x51, 0x95, 0xc1, 0x36, 0xaf, 0x6e, 0xae, 0x00, 0x50, 0x21, 0xa0, 0xde, 0xbb, 0x75, + 0x91, 0xe9, 0x17, 0x0b, 0xba, 0xcc, 0x70, 0x4d, 0x82, 0xd9, 0x41, 0xce, 0x8d, 0x6e, 0x72, 0x4a, 0xcc, 0x03, 0xd8, + 0x1c, 0x6e, 0xb7, 0x1e, 0xbf, 0x70, 0x32, 0x7a, 0x3a, 0x5b, 0x66, 0xca, 0xa0, 0x93, 0xeb, 0xfd, 0x4f, 0x22, 0x27, + 0x0d, 0x15, 0x9f, 0x64, 0xfa, 0x22, 0x03, 0x3e, 0x8f, 0xbd, 0xa9, 0x42, 0x97, 0xe5, 0xf2, 0x5a, 0x03, 0x6c, 0x6c, + 0x76, 0x79, 0x3f, 0x4a, 0x39, 0x44, 0xa4, 0x08, 0x8c, 0xba, 0x66, 0x99, 0x11, 0xd7, 0xa6, 0xe2, 0xbe, 0xa5, 0x0a, + 0x7b, 0x53, 0x39, 0xce, 0x2a, 0x5c, 0x3b, 0xca, 0xf4, 0x26, 0x51, 0xf8, 0x02, 0x85, 0xa8, 0x1c, 0x8d, 0xe9, 0xac, + 0x40, 0x2a, 0x73, 0x98, 0x50, 0xcc, 0x61, 0xdf, 0xfd, 0x92, 0x5a, 0x73, 0x19, 0x57, 0xb8, 0xf7, 0xc2, 0x95, 0x99, + 0xdc, 0x09, 0x00, 0x45, 0x92, 0xb5, 0xff, 0xfc, 0x09, 0xa9, 0xf1, 0x3f, 0x53, 0xa5, 0x01, 0xe8, 0xfd, 0x0c, 0x35, + 0x59, 0x82, 0x80, 0xad, 0x98, 0xba, 0x68, 0xfa, 0x46, 0x32, 0xff, 0x01, 0x75, 0x3b, 0x15, 0xdb, 0xc8, 0xf8, 0x39, + 0x51, 0x4d, 0x4b, 0x9e, 0xae, 0x8b, 0x34, 0x2e, 0x92, 0xfb, 0x88, 0x37, 0x53, 0x2c, 0x89, 0x55, 0x9a, 0x02, 0xfd, + 0xec, 0x77, 0xe1, 0xa7, 0xd2, 0x33, 0x01, 0xa7, 0x85, 0xbb, 0xad, 0x9c, 0xcd, 0x64, 0x18, 0x67, 0x64, 0xca, 0x25, + 0x62, 0xb7, 0xd1, 0xf7, 0xe8, 0x13, 0xfc, 0xc9, 0xd1, 0x13, 0x42, 0xef, 0xc4, 0xb4, 0x40, 0x50, 0xba, 0x22, 0x35, + 0xae, 0x9a, 0xd8, 0xaf, 0x29, 0x44, 0x71, 0xa8, 0x18, 0x84, 0xee, 0xd4, 0xed, 0x93, 0x7c, 0x9f, 0x29, 0xfb, 0x8d, + 0x2e, 0x5b, 0x90, 0x4d, 0x05, 0x1d, 0x13, 0xd6, 0xdb, 0xd3, 0xd9, 0xb3, 0x33, 0xe7, 0x37, 0x68, 0xc2, 0x41, 0x75, + 0x03, 0xed, 0x2a, 0xc9, 0x34, 0x46, 0xb1, 0x59, 0x8c, 0xb5, 0x1b, 0x13, 0x11, 0x04, 0x9d, 0x2e, 0x66, 0x61, 0xbb, + 0x9d, 0x10, 0x5f, 0x02, 0x09, 0x14, 0xb8, 0x72, 0x51, 0x4e, 0x42, 0x22, 0x2f, 0x64, 0x6a, 0xb2, 0x6e, 0x04, 0x0b, + 0xd4, 0x1a, 0x3b, 0x0a, 0xe8, 0x29, 0x37, 0x4f, 0x01, 0x7d, 0x5f, 0xb2, 0x53, 0x3e, 0x08, 0x86, 0x18, 0x5f, 0x35, + 0xa0, 0xb7, 0x42, 0x3e, 0x82, 0x87, 0x30, 0xb0, 0x5c, 0xf4, 0x65, 0xc9, 0x10, 0x56, 0xe8, 0xcf, 0x94, 0x4d, 0xbe, + 0xfe, 0xc6, 0xce, 0xef, 0xb5, 0x12, 0xb3, 0x83, 0x50, 0xdc, 0x5c, 0x4f, 0x80, 0xf8, 0xd5, 0xfc, 0x1a, 0xac, 0xab, + 0x95, 0xc4, 0xdb, 0x91, 0x3c, 0x54, 0xae, 0x1c, 0xdd, 0x7c, 0x52, 0xe9, 0x4f, 0x20, 0x48, 0x8d, 0x95, 0x94, 0xdb, + 0xef, 0x3e, 0x0a, 0x5b, 0x11, 0x8c, 0x16, 0x20, 0xd6, 0xed, 0xad, 0xe4, 0xc2, 0x17, 0xfe, 0x63, 0x9d, 0xef, 0x31, + 0x76, 0x88, 0x38, 0xc3, 0xe9, 0xf7, 0xc1, 0xb0, 0xbd, 0x5b, 0x99, 0x36, 0x24, 0xba, 0x96, 0x1f, 0x01, 0xfd, 0x1f, + 0xab, 0xf1, 0x3b, 0x45, 0x49, 0x5f, 0x12, 0xe7, 0x08, 0x57, 0xc4, 0x2b, 0x34, 0xd5, 0xeb, 0x8d, 0x1b, 0xfa, 0xa6, + 0xd4, 0x2f, 0x94, 0x82, 0xc3, 0xbc, 0xd5, 0x0a, 0x0f, 0x3c, 0xf3, 0xfe, 0xa8, 0x3c, 0x41, 0xf7, 0x6f, 0xb8, 0x37, + 0xfe, 0xa8, 0x58, 0x86, 0x37, 0xe5, 0x2c, 0xd3, 0x77, 0xb8, 0xdb, 0xac, 0x48, 0xc5, 0x2d, 0x63, 0xc1, 0xba, 0x90, + 0xe6, 0xab, 0x69, 0x30, 0xdb, 0x34, 0x91, 0x4c, 0xb6, 0xdf, 0xff, 0xe5, 0x9d, 0xb0, 0xd9, 0x20, 0x38, 0xab, 0x45, + 0x19, 0x5f, 0xf1, 0x60, 0xaa, 0x54, 0x14, 0x59, 0xd6, 0xef, 0x67, 0x80, 0x0c, 0x63, 0xb5, 0x77, 0xf0, 0x64, 0xa8, + 0x99, 0x0e, 0x71, 0x6d, 0x74, 0x16, 0xf0, 0x56, 0x8f, 0xe6, 0x69, 0x0d, 0xbb, 0xcc, 0x55, 0x52, 0xfc, 0xd1, 0x92, + 0x64, 0x63, 0xfd, 0x9e, 0x0c, 0xdb, 0xc8, 0x67, 0xae, 0x01, 0x63, 0xe6, 0x56, 0xc8, 0x20, 0x77, 0x3d, 0x60, 0x84, + 0x90, 0x08, 0x54, 0xd6, 0x62, 0xe2, 0xbc, 0xd2, 0xe1, 0x1f, 0x6d, 0x60, 0x9c, 0x18, 0x03, 0xe3, 0x7c, 0x14, 0x21, + 0xa7, 0xa7, 0x7c, 0x90, 0x78, 0xb3, 0xf5, 0x97, 0x2c, 0x91, 0xde, 0x08, 0x42, 0x2f, 0xe0, 0xf7, 0xb8, 0xc5, 0x03, + 0x79, 0xc1, 0x29, 0xed, 0xcd, 0xe9, 0xf0, 0x65, 0x49, 0x86, 0x7f, 0x82, 0x77, 0x57, 0x6c, 0x2e, 0xcb, 0x09, 0x2c, + 0xee, 0xd8, 0x29, 0x9e, 0xe6, 0xb2, 0xc5, 0x09, 0x71, 0x88, 0x45, 0xee, 0x12, 0x0b, 0x18, 0x51, 0xcd, 0x68, 0xfc, + 0x78, 0xf6, 0xf6, 0x8d, 0xc2, 0x6c, 0xca, 0xdd, 0x0f, 0x60, 0x44, 0x95, 0xb4, 0xdd, 0x0c, 0xf8, 0x72, 0x84, 0x06, + 0xdb, 0xa9, 0x1d, 0xec, 0x7e, 0x5f, 0xa7, 0x9d, 0x14, 0x4e, 0x36, 0x2b, 0x06, 0xdd, 0x51, 0xda, 0x2c, 0xa5, 0x41, + 0x6d, 0x57, 0xe1, 0x68, 0x3e, 0xab, 0xc5, 0xaa, 0xce, 0x87, 0xe1, 0x92, 0xc6, 0x46, 0x56, 0x6e, 0x76, 0x13, 0x8e, + 0x6c, 0x02, 0x5c, 0x9f, 0x84, 0xb2, 0xf2, 0xe7, 0xa0, 0x05, 0x9d, 0x09, 0x1c, 0xd1, 0x76, 0x1b, 0x42, 0x04, 0x8e, + 0x72, 0x38, 0x99, 0x85, 0xe5, 0x70, 0x28, 0x07, 0xbe, 0x24, 0x24, 0x7a, 0x53, 0xce, 0xb3, 0x85, 0x44, 0xec, 0x71, + 0x77, 0xd2, 0xaf, 0xa5, 0xe4, 0x94, 0x7b, 0x7f, 0x54, 0x64, 0xf3, 0x5b, 0x8a, 0x31, 0x07, 0xad, 0x66, 0x33, 0x03, + 0x09, 0xeb, 0x69, 0x4d, 0xe4, 0x3a, 0x32, 0xb3, 0x01, 0xaa, 0x58, 0x34, 0x85, 0x05, 0x75, 0x8b, 0x23, 0xd6, 0xd3, + 0x7a, 0x0f, 0x2a, 0x40, 0x54, 0x0b, 0x76, 0x63, 0xb8, 0xd6, 0x5e, 0x56, 0xa1, 0xa0, 0x9c, 0xf8, 0xcc, 0x8c, 0x11, + 0x0d, 0x96, 0x20, 0x24, 0x8d, 0xab, 0xfa, 0xb5, 0x48, 0xb3, 0xcb, 0x0c, 0x10, 0x13, 0xac, 0xff, 0x9c, 0xf0, 0xde, + 0x3c, 0x93, 0xf3, 0xd2, 0x95, 0x38, 0x33, 0x30, 0x1f, 0x5d, 0x6f, 0x69, 0x49, 0xa2, 0x12, 0x68, 0x94, 0xab, 0xe5, + 0xf9, 0xfb, 0x8e, 0x55, 0xc8, 0xee, 0x87, 0x53, 0x69, 0x3b, 0xc4, 0x4f, 0x58, 0x4d, 0x9c, 0xd3, 0xba, 0x96, 0x22, + 0x8d, 0x8e, 0xb6, 0x01, 0x31, 0x6c, 0xd9, 0xb7, 0xc8, 0xe1, 0x03, 0x83, 0x0a, 0x2b, 0xf9, 0x29, 0x70, 0xf8, 0x8c, + 0x81, 0xa4, 0xab, 0x45, 0x70, 0x35, 0x3a, 0xc2, 0x8a, 0x32, 0xb5, 0xc4, 0x14, 0x12, 0xdd, 0x7a, 0xa1, 0x35, 0x86, + 0x51, 0x76, 0x15, 0xf9, 0xdf, 0xab, 0xee, 0xfd, 0x51, 0x6d, 0xb7, 0x30, 0xc9, 0x8e, 0xc7, 0x15, 0x6c, 0x6a, 0xd4, + 0x0a, 0xe1, 0xec, 0x9c, 0xd6, 0xa8, 0x1d, 0xeb, 0x85, 0x05, 0x90, 0x07, 0xb0, 0x15, 0xf1, 0x28, 0x83, 0x60, 0x6f, + 0xca, 0x79, 0xb5, 0xb0, 0xa2, 0x1c, 0x21, 0xf1, 0xbe, 0xc4, 0x28, 0xe5, 0x70, 0x15, 0x0b, 0x4b, 0x86, 0xfc, 0xea, + 0xe8, 0xb2, 0x14, 0xd7, 0x20, 0x29, 0xd1, 0x0c, 0x95, 0xe1, 0x75, 0x71, 0xd5, 0x16, 0x84, 0xf6, 0x2e, 0x2a, 0x50, + 0x47, 0x82, 0xe0, 0xc5, 0xab, 0x21, 0x66, 0x1b, 0xb9, 0xbb, 0xa2, 0xbd, 0xe4, 0x80, 0x5a, 0xdd, 0xb5, 0x5d, 0x6f, + 0xd2, 0x36, 0xdb, 0x88, 0x0b, 0xff, 0x82, 0xd2, 0x4f, 0xf9, 0xa0, 0x74, 0xa9, 0x04, 0x6e, 0x7c, 0xb9, 0xc9, 0xb2, + 0xcb, 0x7b, 0x5c, 0xfa, 0xb5, 0x37, 0x7e, 0xfd, 0x7e, 0x4f, 0x2e, 0x04, 0x2f, 0x15, 0x98, 0x6f, 0x97, 0x99, 0xaa, + 0xb5, 0xa6, 0xd4, 0x5c, 0x82, 0x6b, 0x6b, 0x3f, 0x82, 0x8a, 0xb8, 0xae, 0xc8, 0x64, 0x72, 0x80, 0x0e, 0x9c, 0xac, + 0x70, 0x2b, 0x0b, 0xf0, 0xd8, 0x09, 0xc8, 0x76, 0xcb, 0xc3, 0x40, 0x1d, 0x3a, 0x81, 0xbb, 0x25, 0xcf, 0x90, 0x59, + 0x33, 0x8f, 0x3f, 0x2b, 0xc1, 0x3f, 0xb6, 0xe0, 0x27, 0x14, 0x77, 0x1a, 0x99, 0x7f, 0x2b, 0xad, 0x5b, 0xdc, 0xbf, + 0x93, 0x69, 0x42, 0x51, 0x99, 0xd0, 0xb8, 0x95, 0xfe, 0x4b, 0x07, 0x4b, 0x92, 0xd9, 0x3f, 0x08, 0xf8, 0x60, 0xe6, + 0x3d, 0x31, 0xef, 0x49, 0x73, 0xba, 0xb5, 0x82, 0x21, 0x40, 0xa1, 0x9f, 0x93, 0xb9, 0xa6, 0xea, 0xf9, 0xe7, 0x35, + 0x5f, 0x73, 0xbf, 0xc5, 0x26, 0xe9, 0x81, 0x06, 0x3b, 0x79, 0x14, 0xa5, 0xb0, 0x12, 0x75, 0xae, 0x25, 0xea, 0x55, + 0xc3, 0x32, 0x54, 0x27, 0x38, 0x35, 0x4f, 0xd5, 0xb0, 0xfb, 0x89, 0x68, 0xad, 0x24, 0x2d, 0x31, 0x60, 0xad, 0x23, + 0x0f, 0xc9, 0xed, 0x5a, 0xc7, 0x9d, 0x86, 0xba, 0x34, 0x89, 0x12, 0x60, 0x84, 0x0b, 0x70, 0x04, 0xfd, 0x54, 0x86, + 0x1c, 0xae, 0xa9, 0x52, 0xbf, 0xa0, 0x28, 0x79, 0xe2, 0x28, 0x6a, 0x95, 0x22, 0xdd, 0x7c, 0x94, 0x63, 0x37, 0x5c, + 0xe3, 0x84, 0x9c, 0x68, 0xa1, 0xbf, 0x3d, 0x96, 0x72, 0x86, 0x16, 0x0f, 0xf2, 0x04, 0xeb, 0xe5, 0x2d, 0x05, 0x8a, + 0x3e, 0xba, 0x8c, 0xba, 0xe6, 0x15, 0xda, 0xbe, 0x2c, 0xfb, 0xfd, 0xdc, 0xd4, 0x93, 0xb2, 0x93, 0xcd, 0x52, 0xef, + 0x43, 0x54, 0x4c, 0xe1, 0xae, 0x4f, 0x14, 0x7f, 0x15, 0xaa, 0xab, 0xb6, 0xc8, 0xf9, 0x88, 0x23, 0x2e, 0x46, 0x4e, + 0x9a, 0x9f, 0xe5, 0xd4, 0x4b, 0x71, 0xbf, 0xac, 0xe4, 0xd7, 0x4a, 0x5b, 0x31, 0x5a, 0xa0, 0xfe, 0x54, 0xaa, 0xbc, + 0x5f, 0x94, 0x00, 0xf7, 0x54, 0xb1, 0x37, 0x60, 0x5f, 0xa1, 0x10, 0x7e, 0x5b, 0x02, 0xfe, 0x8d, 0xe4, 0x06, 0x8c, + 0x02, 0x03, 0x8c, 0x26, 0xdb, 0x73, 0x9a, 0xc0, 0x01, 0x57, 0x29, 0x15, 0x05, 0xad, 0xf4, 0xd0, 0x50, 0x53, 0x18, + 0x3d, 0x43, 0x19, 0xb7, 0xcc, 0xec, 0xdc, 0x18, 0x3b, 0x2d, 0xf0, 0x3c, 0x7f, 0x3e, 0x27, 0xf4, 0xb0, 0x56, 0x07, + 0xa9, 0xd1, 0x49, 0x74, 0x7f, 0xec, 0xc2, 0xc9, 0xf5, 0xc2, 0x59, 0x36, 0x2c, 0x81, 0xee, 0xc0, 0x05, 0x31, 0xee, + 0xf7, 0x73, 0x38, 0x32, 0xf5, 0xc8, 0x97, 0x2c, 0xa7, 0x31, 0x5b, 0x52, 0xe5, 0x69, 0x77, 0x55, 0x87, 0x39, 0x5d, + 0x1a, 0x19, 0x6f, 0xca, 0x8a, 0x79, 0x0e, 0x1a, 0x49, 0xf8, 0xd3, 0x6d, 0xed, 0x92, 0xce, 0x97, 0x90, 0x01, 0xfe, + 0x80, 0x44, 0x14, 0xb1, 0xaf, 0xff, 0xad, 0xc6, 0x49, 0x3d, 0x51, 0xda, 0xb0, 0x84, 0xae, 0x99, 0xaa, 0x9f, 0x5e, + 0xb2, 0xb5, 0xb7, 0x14, 0xb6, 0xdb, 0xd0, 0x4f, 0x60, 0x8a, 0x73, 0x25, 0xd3, 0x4b, 0xd4, 0x49, 0x01, 0x15, 0x0b, + 0x2f, 0x71, 0xf9, 0xa5, 0x84, 0x42, 0x73, 0xe7, 0xcb, 0x85, 0x56, 0x62, 0x42, 0xab, 0xc4, 0xe7, 0x0f, 0x95, 0xfe, + 0x5a, 0x7b, 0xc4, 0xfd, 0x2b, 0x0d, 0x13, 0x5d, 0x24, 0x2a, 0x44, 0x67, 0xbf, 0x82, 0x2c, 0xa7, 0x02, 0x1c, 0xcb, + 0x33, 0xd1, 0xd0, 0x1f, 0x53, 0x88, 0x83, 0x0e, 0x0d, 0x7a, 0x57, 0x8a, 0xeb, 0xac, 0xe2, 0x21, 0xde, 0x13, 0x1c, + 0xcd, 0xe8, 0x7e, 0x83, 0x0f, 0x65, 0xed, 0xd1, 0xab, 0xc8, 0xc6, 0x51, 0xee, 0x37, 0xbf, 0x56, 0xe1, 0x1c, 0xa2, + 0x55, 0x2e, 0xa8, 0x52, 0x57, 0x5b, 0x00, 0x2a, 0xc7, 0xf6, 0xea, 0x11, 0x9c, 0x6e, 0xea, 0xfa, 0x56, 0x87, 0xd6, + 0x1c, 0x40, 0x98, 0x43, 0xb2, 0x69, 0xb8, 0xda, 0x01, 0xf6, 0x48, 0xac, 0xd7, 0x40, 0x63, 0xed, 0xd6, 0xec, 0xb4, + 0x47, 0x71, 0x98, 0xc8, 0x4c, 0x5b, 0xa4, 0x68, 0x73, 0xb7, 0x4e, 0x8b, 0xa2, 0x0d, 0x9a, 0x21, 0xec, 0xde, 0x75, + 0xf8, 0xba, 0x15, 0x61, 0x7d, 0xbf, 0xed, 0x0b, 0x8c, 0x86, 0x36, 0xd7, 0xee, 0x39, 0x86, 0x6e, 0xd8, 0x60, 0x13, + 0x39, 0x0f, 0x91, 0x0f, 0x33, 0x79, 0x20, 0x8a, 0xc6, 0x18, 0xb0, 0x3d, 0xe2, 0x6a, 0xd3, 0x4a, 0x7e, 0x5e, 0xc6, + 0x9c, 0xed, 0x19, 0xe3, 0x94, 0xd6, 0xd7, 0xb8, 0xe6, 0xb8, 0x2c, 0xa4, 0x6a, 0x8c, 0x67, 0x3c, 0x0c, 0x3b, 0x5f, + 0xe0, 0xce, 0xac, 0x31, 0x78, 0x11, 0x96, 0x4a, 0x76, 0x2a, 0x57, 0x9f, 0xc3, 0x16, 0x47, 0xb3, 0x31, 0xa7, 0xbf, + 0xff, 0x72, 0xc5, 0x17, 0xe8, 0xa6, 0x66, 0xfd, 0x08, 0x82, 0xac, 0x40, 0x87, 0x2c, 0xa9, 0x7a, 0xfc, 0xae, 0x04, + 0x6a, 0x0f, 0xf3, 0xf0, 0x5d, 0xc9, 0x8a, 0xf8, 0x26, 0xbb, 0x8a, 0x6b, 0x51, 0x8e, 0x6e, 0x78, 0x91, 0x8a, 0xd2, + 0x48, 0x8d, 0x83, 0xd3, 0xd5, 0x2a, 0xe7, 0x01, 0x98, 0xca, 0x1b, 0x46, 0xd9, 0x54, 0x96, 0xa9, 0xc1, 0x55, 0xf2, + 0xf4, 0x5a, 0x89, 0xce, 0xab, 0x9b, 0xab, 0x20, 0xc2, 0x5f, 0x17, 0xfa, 0xc7, 0x75, 0x5c, 0x7d, 0x0c, 0x22, 0x63, + 0x53, 0xa7, 0x7f, 0xa0, 0x54, 0x1e, 0xfc, 0xa7, 0x40, 0xa6, 0xfb, 0x5d, 0x09, 0x96, 0xd9, 0xa6, 0xe2, 0xe3, 0x18, + 0x6b, 0x1d, 0x4e, 0xc8, 0x4c, 0x96, 0xe8, 0xbc, 0x4b, 0xd6, 0x25, 0x58, 0xfb, 0x49, 0x2c, 0x63, 0x99, 0x6b, 0x86, + 0x95, 0xc9, 0x8a, 0xf4, 0xac, 0xac, 0xd9, 0x61, 0x68, 0x9c, 0x68, 0xe6, 0xe8, 0x2d, 0xa0, 0x1e, 0xc8, 0xe1, 0x15, + 0x2d, 0xd6, 0xcc, 0xf1, 0xb1, 0xf1, 0x5e, 0x3f, 0x3a, 0xbc, 0x72, 0x04, 0x4a, 0xe6, 0x4e, 0x8e, 0xc2, 0x44, 0xf0, + 0xac, 0xd5, 0xe3, 0x8b, 0x3c, 0x2b, 0x60, 0xe5, 0x4c, 0xc6, 0x63, 0xea, 0x2c, 0xad, 0xd6, 0xcd, 0xd1, 0x22, 0xb9, + 0x66, 0x8f, 0xeb, 0xc7, 0x9c, 0x1c, 0xf2, 0x96, 0xa9, 0x6d, 0xdb, 0x3a, 0xce, 0xd1, 0xe4, 0x4b, 0xd3, 0xfd, 0x6a, + 0x6d, 0x22, 0xa2, 0x4b, 0xe7, 0x3e, 0xeb, 0x15, 0xdc, 0xfa, 0xa6, 0xd0, 0xf4, 0x5a, 0x00, 0x10, 0x9d, 0x32, 0xe0, + 0x2f, 0x59, 0xb1, 0x1e, 0xd5, 0xbc, 0xaa, 0x41, 0xc2, 0x82, 0x22, 0xbc, 0x29, 0xf6, 0xa6, 0xb4, 0x37, 0x4e, 0xc7, + 0x61, 0x07, 0x2e, 0xa6, 0xe8, 0x8e, 0x03, 0x76, 0xfd, 0x5a, 0x2b, 0x1a, 0xa9, 0x5f, 0xb6, 0x2f, 0xb1, 0xea, 0x8b, + 0x52, 0xe6, 0x99, 0x9c, 0x12, 0x8b, 0xdd, 0x56, 0x2e, 0xac, 0xa8, 0xdf, 0x30, 0xe1, 0xd2, 0x95, 0x20, 0x20, 0xd3, + 0x92, 0xf5, 0x4a, 0xbd, 0x8b, 0xc4, 0x1a, 0x08, 0x19, 0x18, 0xbe, 0x06, 0xeb, 0xa2, 0xe2, 0xda, 0x0a, 0xd6, 0xb9, + 0xe7, 0xab, 0x84, 0x42, 0x14, 0x3c, 0xb0, 0x13, 0xf4, 0x43, 0xeb, 0xe6, 0x6d, 0x29, 0x51, 0x06, 0xf1, 0xb8, 0x95, + 0x53, 0x0e, 0x12, 0x08, 0xc0, 0x3d, 0x95, 0x21, 0x38, 0x24, 0xc8, 0x3a, 0xb8, 0x9a, 0x71, 0x04, 0x57, 0x97, 0xce, + 0x5c, 0x5c, 0x03, 0xac, 0x4b, 0x7f, 0x2e, 0x13, 0x5c, 0x58, 0x8d, 0xa8, 0x34, 0x67, 0x9c, 0x62, 0x10, 0x23, 0x43, + 0xd0, 0x57, 0x86, 0xd2, 0x5e, 0x81, 0xa6, 0xf1, 0x9a, 0xad, 0xa4, 0x0f, 0x00, 0xbd, 0x60, 0x2b, 0x69, 0xec, 0x8f, + 0x5f, 0x9f, 0xb3, 0x95, 0x92, 0x06, 0x4f, 0xaf, 0x67, 0x17, 0xb3, 0xf3, 0x01, 0x3b, 0x8a, 0x42, 0x65, 0xc0, 0x10, + 0x58, 0x24, 0xfe, 0x60, 0x10, 0x16, 0xb2, 0x11, 0x83, 0x42, 0x46, 0xc1, 0x72, 0x78, 0x6c, 0xc4, 0xcd, 0x0a, 0xc7, + 0xc3, 0x02, 0x43, 0x5e, 0x79, 0x2f, 0x48, 0x40, 0xa8, 0x2e, 0x0d, 0x5d, 0x1e, 0xc3, 0xe1, 0xe4, 0x60, 0x02, 0xa9, + 0x98, 0x99, 0xc9, 0xc2, 0xd8, 0x98, 0x44, 0x10, 0xef, 0xb4, 0xb3, 0x5e, 0x28, 0xb7, 0xbb, 0xc6, 0x42, 0x8d, 0xc3, + 0xe0, 0xb3, 0x2a, 0x9e, 0x1c, 0x0c, 0xbb, 0x2a, 0xc6, 0x51, 0xb8, 0xd1, 0xca, 0xb7, 0xf3, 0x63, 0x00, 0xaf, 0x3d, + 0x1f, 0xba, 0x72, 0x89, 0xf3, 0xc3, 0x27, 0xe4, 0xf1, 0x13, 0x42, 0xcf, 0xd9, 0xf9, 0x17, 0x4f, 0xe8, 0xb9, 0x24, + 0x27, 0x07, 0x93, 0xe8, 0x86, 0xe9, 0x06, 0x1c, 0x1e, 0xc9, 0x26, 0xd0, 0xab, 0xd1, 0xba, 0x90, 0x0b, 0x4c, 0x39, + 0x34, 0x85, 0xdf, 0x8e, 0x0f, 0x82, 0xc1, 0x4d, 0xbb, 0xe9, 0x37, 0xed, 0xb6, 0x3a, 0x5e, 0x5d, 0x07, 0x47, 0xd1, + 0x6e, 0x31, 0x93, 0x6f, 0xc6, 0x07, 0x76, 0x0e, 0xb0, 0xbe, 0x87, 0xc7, 0x44, 0x37, 0x69, 0x67, 0x54, 0xdc, 0x9a, + 0xbe, 0xc2, 0x3e, 0xf8, 0x45, 0x76, 0xf4, 0x61, 0xf8, 0x6f, 0x75, 0xa2, 0x39, 0xff, 0xe2, 0x08, 0xc8, 0x11, 0xc8, + 0x40, 0xb1, 0x44, 0x30, 0xc3, 0x81, 0xa6, 0x80, 0x82, 0x4c, 0x8d, 0x3b, 0x55, 0xc3, 0x2f, 0x47, 0x4d, 0xce, 0xc8, + 0x0d, 0x4c, 0x0d, 0xb6, 0x05, 0x3f, 0x90, 0xdd, 0x50, 0xdf, 0x28, 0x74, 0x23, 0xe5, 0x64, 0xa6, 0x5f, 0x52, 0xfd, + 0x83, 0xdd, 0x40, 0x00, 0x63, 0x0b, 0x2f, 0x28, 0xd8, 0x97, 0xc7, 0x57, 0x07, 0xb8, 0x8a, 0x00, 0x25, 0x8b, 0x05, + 0x5f, 0x0e, 0xae, 0xd4, 0xe6, 0x3e, 0x08, 0xc8, 0xe0, 0xcb, 0xe0, 0xe4, 0xcb, 0x81, 0x18, 0x04, 0xc7, 0x87, 0x57, + 0x27, 0x81, 0x35, 0xee, 0x87, 0x10, 0x8f, 0xb2, 0xa2, 0x98, 0x69, 0x34, 0x1f, 0xf4, 0x82, 0x92, 0x89, 0xb9, 0xa9, + 0x57, 0x1a, 0x9f, 0xd1, 0x74, 0x6a, 0x90, 0xbf, 0xc3, 0x94, 0xc5, 0xfa, 0x77, 0x30, 0xe1, 0xd7, 0x41, 0x64, 0x83, + 0xa0, 0xce, 0xf2, 0x28, 0xa6, 0x4b, 0x76, 0x5f, 0x85, 0x29, 0x4d, 0x0e, 0x73, 0x42, 0xa2, 0x70, 0x29, 0xc1, 0xf3, + 0xe4, 0xeb, 0x04, 0xe2, 0xb8, 0xda, 0xcf, 0x01, 0xd7, 0x8d, 0xe6, 0x87, 0x09, 0x69, 0x15, 0x61, 0x23, 0xb2, 0x6c, + 0x1a, 0x7a, 0xc9, 0xc2, 0x15, 0xbd, 0x02, 0x66, 0x4a, 0xac, 0xc3, 0x2b, 0xe0, 0xf2, 0xd6, 0xf3, 0xd5, 0x82, 0x5d, + 0x79, 0xd2, 0x37, 0xcd, 0x17, 0x5f, 0x1a, 0x9f, 0x3c, 0xe0, 0x21, 0xad, 0x1f, 0x5e, 0x0a, 0x36, 0x00, 0x37, 0x19, + 0xbf, 0xfd, 0x4e, 0xdc, 0xa9, 0x79, 0x69, 0x4f, 0x31, 0xce, 0x4c, 0x3b, 0x31, 0x69, 0x27, 0xe4, 0xee, 0x7d, 0x7b, + 0x13, 0xeb, 0x93, 0xbd, 0x8a, 0xd6, 0xd2, 0x65, 0xd5, 0x30, 0x24, 0xe5, 0x9a, 0x21, 0x7f, 0x8f, 0x92, 0x53, 0x23, + 0xf0, 0x64, 0x97, 0xbc, 0x4e, 0x96, 0xee, 0x41, 0x65, 0xac, 0x06, 0xcc, 0x31, 0x62, 0x58, 0x28, 0x1c, 0xfb, 0xd7, + 0x19, 0x2b, 0xd7, 0xae, 0x40, 0x23, 0x46, 0xee, 0xed, 0x75, 0xc6, 0x9c, 0x98, 0xab, 0xc9, 0xda, 0x09, 0x55, 0xe7, + 0xa4, 0xe7, 0x2d, 0xde, 0xcb, 0x2a, 0x35, 0xb4, 0x44, 0xf4, 0x60, 0x2c, 0xcd, 0x28, 0x65, 0xa2, 0xd2, 0xa0, 0x91, + 0x8a, 0x8d, 0x6d, 0xf0, 0x4b, 0x70, 0x42, 0xe5, 0x8e, 0x3a, 0xdb, 0xb5, 0x53, 0x2a, 0x1c, 0x60, 0x59, 0xaa, 0x55, + 0xe5, 0x76, 0x99, 0x09, 0x56, 0x0f, 0x82, 0xd1, 0x1f, 0x95, 0x28, 0x66, 0x78, 0x67, 0x64, 0xc1, 0x14, 0xac, 0x04, + 0x65, 0x2d, 0xc3, 0x62, 0xc8, 0x51, 0x8b, 0xa7, 0x7d, 0x52, 0x85, 0xfa, 0xd1, 0x11, 0x24, 0x77, 0xb9, 0x6e, 0x05, + 0xc9, 0x7d, 0x32, 0x7e, 0xa2, 0x06, 0x3a, 0x5d, 0x2b, 0xc7, 0x43, 0x97, 0xdf, 0x46, 0x7c, 0x6d, 0xd5, 0x7b, 0xaa, + 0xb4, 0x0a, 0x55, 0xa0, 0xc4, 0x8a, 0xd2, 0x95, 0x5a, 0xd0, 0xfd, 0x2e, 0x02, 0x60, 0x11, 0x1b, 0xb3, 0xf1, 0xae, + 0x6d, 0x56, 0x08, 0x1a, 0x5d, 0x76, 0xb2, 0x89, 0x07, 0x2c, 0x51, 0xad, 0x1d, 0x4c, 0x68, 0x7c, 0xc2, 0x8a, 0x7e, + 0x3f, 0x3f, 0x01, 0x7a, 0xaa, 0x8c, 0x98, 0x4a, 0x38, 0xf2, 0x3f, 0xb7, 0x22, 0x5d, 0x14, 0xd8, 0xac, 0xc9, 0xbb, + 0x35, 0x96, 0x91, 0xa8, 0xcb, 0x94, 0x2a, 0xaf, 0x72, 0x0c, 0x98, 0xd6, 0xeb, 0x96, 0xe3, 0xca, 0xae, 0xe2, 0xc8, + 0x51, 0x61, 0x19, 0x71, 0x5e, 0x8d, 0xe3, 0xad, 0xc6, 0x37, 0x38, 0xd4, 0x6c, 0xda, 0xa5, 0x3b, 0x84, 0xb0, 0x10, + 0x5e, 0x67, 0x70, 0x1b, 0x51, 0x76, 0x12, 0xa8, 0xbc, 0xd1, 0xd7, 0x09, 0x69, 0x73, 0xbb, 0x5e, 0x3b, 0x06, 0xe9, + 0x44, 0x1f, 0x28, 0xf5, 0x08, 0x5a, 0xa3, 0x58, 0x50, 0x39, 0xe2, 0x91, 0xe5, 0xe1, 0xad, 0x41, 0xac, 0x92, 0x2f, + 0x29, 0x2a, 0x45, 0x03, 0xf4, 0xbf, 0xe4, 0xb7, 0x07, 0xbf, 0xbc, 0xff, 0xe9, 0x8c, 0xc7, 0x65, 0xb2, 0x7c, 0x17, + 0x97, 0xf1, 0x75, 0x15, 0x6e, 0xe4, 0x18, 0xc5, 0x0d, 0x99, 0x56, 0x03, 0x26, 0xf4, 0x4a, 0xf2, 0x77, 0xa5, 0x22, + 0xc4, 0x58, 0x67, 0xb2, 0xae, 0x6a, 0x71, 0xed, 0x55, 0xba, 0x2e, 0x33, 0xfc, 0xb8, 0xe5, 0x73, 0x7a, 0x08, 0x40, + 0x9e, 0xda, 0x85, 0x34, 0x12, 0xaa, 0x10, 0x6d, 0x2e, 0xe2, 0x74, 0x7d, 0x3c, 0xf6, 0xba, 0x5e, 0xb0, 0xa7, 0xe3, + 0xaf, 0xa6, 0xaf, 0xb3, 0x30, 0x1b, 0x54, 0x64, 0x54, 0x2f, 0x79, 0xd1, 0x32, 0xe5, 0x94, 0x26, 0x01, 0xe8, 0xe3, + 0xd9, 0x63, 0xec, 0x68, 0x3c, 0x26, 0x9b, 0xb6, 0x78, 0x80, 0x87, 0xcb, 0x75, 0x58, 0x92, 0x99, 0xaa, 0x23, 0x0a, + 0x0a, 0x7e, 0x57, 0x07, 0x80, 0xe4, 0x68, 0xaa, 0xd2, 0x5c, 0x1a, 0x7b, 0x3a, 0x9e, 0x50, 0x81, 0xdd, 0x0e, 0x49, + 0xe3, 0x54, 0x68, 0x67, 0x5e, 0xb8, 0x1e, 0x45, 0x42, 0xbb, 0x2c, 0xed, 0x54, 0x2a, 0xe4, 0x9e, 0x99, 0xd9, 0xae, + 0x41, 0x0c, 0x86, 0x50, 0xd5, 0x5d, 0x38, 0x75, 0xef, 0x36, 0xd7, 0x98, 0xed, 0x80, 0xf7, 0x1a, 0x34, 0x43, 0xca, + 0x5b, 0xf4, 0x5b, 0x5b, 0x44, 0x43, 0x57, 0x6b, 0x30, 0x2b, 0x46, 0xd9, 0x52, 0x94, 0xae, 0x29, 0x28, 0x05, 0xa3, + 0xcb, 0xb5, 0xb3, 0x70, 0x5f, 0x0b, 0xef, 0xc2, 0x92, 0xa9, 0xd5, 0x22, 0xa5, 0x84, 0xf2, 0xa6, 0xa2, 0xa5, 0x84, + 0x91, 0xd4, 0xf0, 0xd4, 0xae, 0x17, 0x78, 0x9c, 0xe7, 0x41, 0xd4, 0xf2, 0x02, 0x3b, 0xad, 0xc9, 0x29, 0x38, 0x7a, + 0xe9, 0x9c, 0x9a, 0x02, 0xff, 0x98, 0x49, 0x10, 0xd3, 0xa1, 0xbc, 0xdf, 0xe0, 0xe6, 0xff, 0x47, 0xc9, 0x02, 0x87, + 0x6f, 0xbd, 0xc2, 0x6d, 0xf4, 0x8f, 0xd2, 0xa5, 0xa5, 0xcf, 0x84, 0xeb, 0xea, 0xe2, 0x48, 0x7b, 0xb3, 0x51, 0xb2, + 0xcc, 0xf2, 0xf4, 0x8d, 0x48, 0x79, 0x45, 0xa4, 0x09, 0x46, 0xc5, 0x4e, 0x2a, 0xef, 0x86, 0x07, 0x46, 0x8c, 0xde, + 0x8d, 0xef, 0xc7, 0x0c, 0x64, 0xc3, 0x60, 0xf5, 0xcd, 0x52, 0x91, 0xac, 0xaf, 0x01, 0x53, 0x44, 0xca, 0x4f, 0x5e, + 0xe4, 0x1c, 0x9e, 0x42, 0x75, 0xfd, 0x02, 0xb7, 0xb9, 0xca, 0xf5, 0x39, 0xff, 0x31, 0xa3, 0x3f, 0x22, 0xd0, 0x49, + 0xbc, 0x02, 0xb9, 0xc7, 0x33, 0xa8, 0x1b, 0x61, 0x6a, 0x39, 0x06, 0x07, 0x42, 0x34, 0x90, 0xa2, 0x66, 0x81, 0x84, + 0xba, 0xd0, 0xc0, 0x1a, 0xf2, 0x82, 0x39, 0xbc, 0xc8, 0x45, 0xf2, 0x71, 0xaa, 0x7d, 0xe6, 0x87, 0x31, 0xc6, 0x4c, + 0x0e, 0x06, 0x61, 0x3d, 0x0b, 0x86, 0xe3, 0xd1, 0xe4, 0xe8, 0x29, 0x9c, 0xdb, 0xc1, 0x38, 0x20, 0x83, 0xa0, 0xa9, + 0x56, 0x71, 0x41, 0xab, 0x9b, 0x2b, 0x53, 0x06, 0x7e, 0xdc, 0x04, 0x83, 0x7f, 0x94, 0x8e, 0xe2, 0x1d, 0x34, 0x27, + 0xe7, 0x22, 0x04, 0x1b, 0xfb, 0x35, 0x01, 0x49, 0x59, 0x4f, 0xf1, 0x93, 0xea, 0x70, 0x63, 0x52, 0xfb, 0xa7, 0x0f, + 0x2f, 0x38, 0xec, 0x90, 0x40, 0x81, 0x34, 0x9e, 0x66, 0xa3, 0x57, 0x52, 0x91, 0xfb, 0xae, 0xe4, 0x70, 0x67, 0xee, + 0x59, 0xd3, 0x23, 0xab, 0x90, 0xf0, 0xb3, 0x80, 0x1b, 0xf9, 0xab, 0xe2, 0x26, 0xce, 0xb3, 0xf4, 0xc0, 0x7f, 0x73, + 0x50, 0xdd, 0x17, 0x75, 0x7c, 0x37, 0x0a, 0xb4, 0x35, 0x21, 0x77, 0x55, 0x4f, 0x80, 0x9e, 0x00, 0x5b, 0x00, 0x0c, + 0x88, 0x77, 0xcc, 0x4c, 0x66, 0x3c, 0x02, 0x8f, 0x40, 0xdf, 0x07, 0xb2, 0xbc, 0xb7, 0x2e, 0x49, 0xee, 0x66, 0x2a, + 0xcc, 0x55, 0xaf, 0xd8, 0x29, 0xc8, 0x78, 0xb5, 0x15, 0xbb, 0x6e, 0x7d, 0xe6, 0x4d, 0x87, 0x57, 0xe0, 0x85, 0x00, + 0xb7, 0xc8, 0x7e, 0xdf, 0x17, 0x54, 0x56, 0x5a, 0x45, 0xbc, 0x93, 0xdc, 0xa0, 0x7f, 0xbb, 0x33, 0x36, 0x92, 0xe4, + 0x56, 0x0f, 0x0f, 0xa0, 0xca, 0xe4, 0x5c, 0x71, 0x3b, 0x87, 0xa8, 0xad, 0xbb, 0x71, 0xc0, 0x6a, 0x83, 0x76, 0x59, + 0x73, 0x04, 0x17, 0x5e, 0x1c, 0x64, 0x90, 0x13, 0x67, 0x65, 0x24, 0xd5, 0xb8, 0x9a, 0xd4, 0x82, 0x4f, 0xf2, 0x74, + 0x0f, 0x59, 0xea, 0x09, 0x50, 0xe4, 0x38, 0x16, 0x43, 0xba, 0xf1, 0x26, 0xf0, 0xf8, 0xbd, 0x08, 0x41, 0x9a, 0xb6, + 0xdd, 0xfa, 0x23, 0x50, 0x74, 0x0f, 0x4c, 0x41, 0x9a, 0x46, 0x9b, 0x1a, 0x28, 0xa8, 0x3d, 0xd4, 0x48, 0x45, 0x9c, + 0x9d, 0xbc, 0x06, 0x1d, 0x22, 0xf8, 0x7e, 0xa7, 0x59, 0xd5, 0xf1, 0x62, 0x42, 0xf0, 0xe4, 0x7d, 0x71, 0x97, 0x55, + 0x75, 0x15, 0xbd, 0x4f, 0xd1, 0x10, 0x2a, 0x11, 0x45, 0xf4, 0x12, 0xe2, 0xe9, 0x55, 0xf8, 0xbb, 0x8a, 0x7e, 0x4a, + 0x69, 0x9c, 0xa6, 0x98, 0xfe, 0xbc, 0x84, 0x9f, 0xcf, 0x00, 0xd5, 0x11, 0x77, 0x42, 0x74, 0x21, 0xc0, 0x5e, 0x0d, + 0xa2, 0x59, 0xd5, 0x1c, 0x30, 0x34, 0xa3, 0xfb, 0x8a, 0x22, 0x46, 0x1b, 0x66, 0xff, 0xa1, 0x44, 0xa1, 0x90, 0x2c, + 0xe6, 0xd7, 0xca, 0x3c, 0x44, 0x3f, 0x62, 0x91, 0xa7, 0xef, 0x5e, 0xe9, 0x21, 0x8d, 0xee, 0x05, 0x55, 0x5b, 0x1b, + 0x8f, 0x2d, 0x0c, 0x5c, 0x16, 0x5d, 0xad, 0xe9, 0x79, 0xbc, 0xca, 0xa2, 0x0d, 0xe0, 0x4f, 0xbc, 0x7b, 0xf5, 0x4c, + 0x5a, 0x98, 0x3c, 0xcf, 0x40, 0x71, 0x70, 0xfa, 0xee, 0xd5, 0x6b, 0x91, 0xae, 0x73, 0x1e, 0x9d, 0x0b, 0x24, 0xad, + 0xa7, 0xef, 0x5e, 0xfd, 0x8c, 0xe6, 0x5e, 0x3f, 0x95, 0xf0, 0xfe, 0x25, 0xf0, 0x96, 0x51, 0xbc, 0x86, 0x3e, 0xc9, + 0xdf, 0xc9, 0x1a, 0x3b, 0xe5, 0xd4, 0x5a, 0x45, 0xbf, 0xa4, 0x8d, 0x23, 0xad, 0xfa, 0x67, 0xe9, 0x52, 0x3b, 0x47, + 0xc0, 0x73, 0x97, 0x67, 0xc5, 0xc7, 0xc8, 0x88, 0x76, 0x82, 0xe8, 0xcb, 0x83, 0xbb, 0xeb, 0xbc, 0xa8, 0x22, 0x7c, + 0xc1, 0xd0, 0x2e, 0x28, 0x3a, 0x3c, 0xbc, 0xbd, 0xbd, 0x1d, 0xdd, 0x7e, 0x35, 0x12, 0xe5, 0xd5, 0xe1, 0xe4, 0xdb, + 0x6f, 0xbf, 0x3d, 0xc4, 0xb7, 0xc1, 0x97, 0x6d, 0xb7, 0xf7, 0x9a, 0xf0, 0x01, 0x0b, 0x10, 0xa1, 0xfa, 0x4b, 0xb8, + 0xa2, 0x80, 0x16, 0x6e, 0xf0, 0x65, 0xf0, 0xa5, 0x3a, 0x74, 0xbe, 0x3c, 0xae, 0x6e, 0xae, 0x64, 0xf9, 0x5d, 0x25, + 0x1f, 0x8d, 0xc7, 0xe3, 0x43, 0x90, 0x40, 0x7d, 0x39, 0xe0, 0x83, 0xe0, 0x24, 0x18, 0x64, 0x70, 0xa1, 0xa9, 0x6e, + 0xae, 0x4e, 0x02, 0xc7, 0x34, 0xd7, 0x63, 0x11, 0x2d, 0x88, 0x4b, 0x70, 0x78, 0x45, 0x83, 0x2f, 0x03, 0x62, 0x53, + 0xbe, 0x80, 0x94, 0x2f, 0x8e, 0x9e, 0xba, 0x69, 0xff, 0x4b, 0xa6, 0x7d, 0xe5, 0xa6, 0x1d, 0x63, 0xda, 0x57, 0xcf, + 0xdc, 0xb4, 0x13, 0x99, 0xf6, 0xc2, 0x4d, 0xfb, 0xdf, 0xd5, 0x00, 0x52, 0x0f, 0x5c, 0xeb, 0xbf, 0x0b, 0xa7, 0x35, + 0x78, 0x0a, 0x45, 0xd9, 0x75, 0x7c, 0xc5, 0xa1, 0xd1, 0x83, 0xbb, 0xeb, 0x9c, 0x06, 0x03, 0x6c, 0xaf, 0x63, 0xe4, + 0xe1, 0x7c, 0xf0, 0xe5, 0xba, 0xcc, 0xc3, 0xe0, 0xcb, 0x01, 0x16, 0x32, 0xf8, 0x32, 0x20, 0x5f, 0xaa, 0x23, 0xed, + 0xae, 0x62, 0x9b, 0xc0, 0x86, 0x22, 0x1d, 0x9a, 0x00, 0x61, 0xae, 0x34, 0xae, 0xa1, 0x7f, 0x96, 0xdd, 0xd9, 0xf0, + 0x96, 0x28, 0xdd, 0x74, 0x83, 0x86, 0xbe, 0x05, 0xef, 0x04, 0x68, 0x54, 0x14, 0xdc, 0xc4, 0x65, 0x38, 0x1c, 0x56, + 0x37, 0x57, 0x04, 0xec, 0x32, 0x57, 0x3c, 0xae, 0xa3, 0xa0, 0x10, 0x43, 0xf9, 0x33, 0x90, 0x91, 0xaf, 0x02, 0x04, + 0x44, 0x82, 0xff, 0x82, 0x86, 0xbe, 0x13, 0x6c, 0x13, 0x0c, 0x6f, 0xf9, 0xc5, 0xc7, 0xac, 0x1e, 0x4a, 0xd1, 0xe2, + 0x5d, 0x45, 0xe1, 0x07, 0xfc, 0xb5, 0x55, 0x47, 0x7f, 0x82, 0x1b, 0xb7, 0xaf, 0x61, 0x7f, 0x27, 0x2c, 0x8b, 0xfa, + 0x4e, 0xcc, 0xb3, 0xc5, 0xb4, 0x75, 0xa0, 0xbf, 0x15, 0xa4, 0x9e, 0x67, 0x83, 0x60, 0x18, 0x0c, 0xf8, 0x82, 0xbd, + 0x15, 0x73, 0xee, 0x98, 0x4f, 0x3d, 0x12, 0xee, 0x34, 0xcf, 0xb2, 0x01, 0xf8, 0xa6, 0x20, 0x3f, 0x72, 0xf8, 0xdf, + 0xf3, 0x21, 0x0a, 0x0f, 0x07, 0x8f, 0x0e, 0xc9, 0x2c, 0x58, 0xdd, 0xa1, 0x47, 0x67, 0x14, 0x64, 0xc5, 0x92, 0x97, + 0x59, 0xed, 0x2c, 0x95, 0xfb, 0x75, 0xdb, 0xcb, 0x63, 0xef, 0xd9, 0xbc, 0x8a, 0x8b, 0x40, 0x9e, 0x73, 0xa0, 0x78, + 0x43, 0xd9, 0x53, 0xe1, 0x4b, 0x48, 0x95, 0x21, 0x6f, 0x58, 0x0c, 0x58, 0x70, 0xdc, 0x1b, 0x0e, 0x0f, 0x82, 0x81, + 0x55, 0xe7, 0x0e, 0x82, 0x83, 0xe1, 0xf0, 0x24, 0xb0, 0xf7, 0xa1, 0x6c, 0x64, 0xef, 0x8c, 0xb4, 0x64, 0xff, 0x2c, + 0xc3, 0x82, 0x82, 0x78, 0x4c, 0x28, 0xf1, 0x97, 0x02, 0x97, 0x19, 0x00, 0xf4, 0x91, 0x94, 0x80, 0x69, 0x58, 0x99, + 0x01, 0x84, 0xe6, 0xa6, 0x31, 0x3b, 0x07, 0xe6, 0x91, 0x26, 0x20, 0xde, 0x03, 0x8a, 0x01, 0x88, 0x25, 0x01, 0xce, + 0x5d, 0x10, 0xc5, 0xaa, 0x90, 0x47, 0x00, 0x7a, 0x8f, 0x3f, 0x89, 0x2e, 0x05, 0x93, 0x54, 0xac, 0x42, 0x10, 0xc4, + 0xf1, 0xd9, 0x5d, 0xd5, 0x9a, 0x9c, 0x25, 0x3a, 0x98, 0x91, 0x04, 0xd8, 0x10, 0x0d, 0x3b, 0x07, 0xf7, 0x73, 0x50, + 0x7a, 0x18, 0xbd, 0x13, 0x72, 0xc1, 0xf7, 0xdc, 0xb2, 0x50, 0x77, 0x70, 0xf5, 0x84, 0x83, 0xe0, 0x9e, 0x2b, 0x16, + 0x60, 0x54, 0x97, 0xeb, 0xaa, 0xe6, 0xe9, 0x87, 0xfb, 0x15, 0xc4, 0xbe, 0xc3, 0x01, 0x7d, 0x27, 0xf2, 0x2c, 0xb9, + 0x0f, 0xad, 0x3d, 0xd7, 0x46, 0xa6, 0xff, 0xf0, 0xe1, 0xf5, 0x4f, 0x11, 0x88, 0x1c, 0x1b, 0x4d, 0xe9, 0xef, 0x39, + 0x9e, 0x4d, 0x6e, 0x84, 0x27, 0x77, 0x63, 0xdf, 0x73, 0x73, 0x7a, 0xf4, 0xfb, 0x50, 0x37, 0xbd, 0xe7, 0xb3, 0x7b, + 0x3e, 0xb2, 0xc5, 0xa1, 0xba, 0xc2, 0x7e, 0x7d, 0xbb, 0x76, 0x8d, 0x90, 0x1e, 0x9e, 0x67, 0xca, 0xbd, 0xf9, 0x51, + 0x0e, 0x86, 0x41, 0x30, 0x55, 0x42, 0x49, 0x88, 0xba, 0xc1, 0xa4, 0x80, 0x21, 0x3a, 0x50, 0xcb, 0x6a, 0x8a, 0x9c, + 0x9b, 0x1c, 0x59, 0x78, 0x3f, 0x60, 0x4a, 0xe8, 0xe0, 0xe5, 0x90, 0x7e, 0x70, 0x38, 0x61, 0xcc, 0xc0, 0x6f, 0x15, + 0x30, 0xfd, 0x72, 0x51, 0x59, 0x07, 0xd1, 0x03, 0x30, 0xc6, 0x2d, 0x78, 0x09, 0x5d, 0x61, 0x37, 0x6b, 0x19, 0x15, + 0x03, 0xc1, 0xe3, 0x90, 0x03, 0x74, 0xb0, 0x0b, 0x5a, 0x56, 0x96, 0xf2, 0x56, 0x65, 0x2d, 0x55, 0xe4, 0x65, 0x28, + 0xab, 0x62, 0x89, 0xf9, 0x5e, 0xb0, 0x1f, 0x4a, 0xf4, 0x2c, 0x9f, 0x56, 0x5d, 0xf0, 0x42, 0x28, 0xc1, 0xb2, 0x5d, + 0xef, 0x44, 0x20, 0xea, 0x4c, 0x75, 0xae, 0xfa, 0x0a, 0xc7, 0x8e, 0xa7, 0xaf, 0x45, 0xca, 0x95, 0x09, 0x85, 0xe2, + 0xf3, 0x85, 0xab, 0x98, 0x28, 0xd9, 0x2d, 0xf4, 0xab, 0x6d, 0xa3, 0xcf, 0xee, 0xd7, 0x6a, 0x33, 0x48, 0xd1, 0x31, + 0x6f, 0x50, 0x70, 0x2d, 0x15, 0x0a, 0x5a, 0x7b, 0x1b, 0x7f, 0x82, 0x23, 0x37, 0xba, 0x3d, 0xf4, 0x7e, 0xab, 0xe3, + 0xab, 0x37, 0xe8, 0xdb, 0x69, 0x7e, 0x8e, 0x6a, 0xf1, 0xcb, 0x6a, 0x05, 0x3e, 0x54, 0x10, 0x59, 0xc4, 0xe0, 0xd2, + 0x42, 0x3d, 0x67, 0xef, 0x4e, 0xdf, 0x80, 0x1f, 0x25, 0xfe, 0xfe, 0xf5, 0xfb, 0xa0, 0x21, 0xd3, 0x78, 0x56, 0xea, + 0x0f, 0x4d, 0x0e, 0x08, 0x4d, 0x62, 0xd3, 0xcc, 0xfb, 0x59, 0xec, 0xb3, 0xef, 0x8a, 0xad, 0xa7, 0xa5, 0x8f, 0x24, + 0xa5, 0xb9, 0x7d, 0x30, 0x20, 0x50, 0x07, 0x88, 0xe4, 0xec, 0x4b, 0x1a, 0x43, 0x9a, 0xcb, 0xec, 0xbb, 0x11, 0xf1, + 0x5e, 0xec, 0x84, 0x10, 0xe3, 0x12, 0x8b, 0x46, 0x0d, 0xf9, 0x8c, 0x47, 0xd2, 0xb0, 0xe8, 0x3d, 0x26, 0x10, 0x6b, + 0x38, 0x2d, 0xdf, 0x23, 0xe6, 0x31, 0xde, 0x0d, 0x94, 0xec, 0x21, 0xca, 0xa8, 0xcd, 0xee, 0x59, 0x7c, 0x7f, 0x5c, + 0x87, 0x99, 0xb1, 0xbc, 0x1c, 0xc2, 0xdf, 0x40, 0x19, 0x80, 0x53, 0x8e, 0x2c, 0x5f, 0xad, 0x37, 0xba, 0x5c, 0x62, + 0x6a, 0x13, 0x41, 0x2c, 0x1e, 0x95, 0x0e, 0x6b, 0x57, 0xa5, 0xaa, 0x5d, 0x6d, 0x7d, 0x26, 0x7a, 0x35, 0x68, 0xe5, + 0xda, 0xf6, 0x78, 0x08, 0x77, 0xa9, 0xa4, 0x15, 0x26, 0xb0, 0x5e, 0x65, 0x15, 0x2a, 0xd8, 0x9c, 0x80, 0x06, 0xd7, + 0x22, 0x05, 0xe0, 0x2c, 0xa5, 0x46, 0xa3, 0x5a, 0xd8, 0x67, 0xe4, 0x7c, 0x16, 0x5b, 0x0b, 0xf1, 0xb4, 0x00, 0x0c, + 0xd7, 0xc7, 0xa0, 0xe4, 0xdd, 0x18, 0x94, 0xd3, 0x8f, 0x12, 0xde, 0x3a, 0x38, 0xaf, 0x96, 0x71, 0x2a, 0x6e, 0x01, + 0x8b, 0x31, 0x70, 0x53, 0xb1, 0x54, 0x27, 0x21, 0x59, 0xf2, 0xe4, 0x23, 0x5a, 0x6d, 0xa4, 0x01, 0x70, 0x95, 0x53, + 0x6d, 0xb9, 0x27, 0x41, 0x42, 0x6d, 0x29, 0x32, 0x21, 0xae, 0xeb, 0x38, 0x59, 0x9e, 0x61, 0x6a, 0xb8, 0x81, 0x5e, + 0x44, 0x81, 0x58, 0xf1, 0x02, 0x48, 0x7a, 0xce, 0xfe, 0x95, 0x29, 0xac, 0xf1, 0x67, 0x02, 0x05, 0x4c, 0x0a, 0x35, + 0x18, 0x2b, 0x65, 0x2f, 0x84, 0x8e, 0xf6, 0x16, 0x04, 0x8d, 0x7d, 0xf9, 0x27, 0xd4, 0xfd, 0x0c, 0x5a, 0x11, 0x7a, + 0x60, 0x88, 0xe2, 0x02, 0x77, 0x68, 0x6a, 0x96, 0x9c, 0x03, 0x8c, 0x58, 0x18, 0xef, 0xb3, 0xc6, 0x6c, 0xf5, 0x67, + 0x4b, 0xc0, 0x36, 0x4d, 0xb5, 0x4f, 0x61, 0x98, 0x10, 0x1d, 0x1b, 0xd8, 0x28, 0x2b, 0xcd, 0x86, 0xd2, 0xed, 0xa4, + 0x4b, 0xe6, 0xb4, 0x70, 0x9a, 0xf7, 0x18, 0x5b, 0x8e, 0x64, 0xee, 0x7e, 0x3f, 0xd4, 0x3f, 0x59, 0x4e, 0x9f, 0xa9, + 0x90, 0xcd, 0xce, 0x78, 0xd0, 0x9c, 0x28, 0x75, 0x55, 0x47, 0x3f, 0xa0, 0x03, 0x30, 0xd3, 0x06, 0x20, 0xd3, 0x06, + 0x9b, 0x76, 0x95, 0xa8, 0xb8, 0x24, 0x61, 0xa9, 0x24, 0xb0, 0xb3, 0x7d, 0xc9, 0xce, 0x26, 0x20, 0x8e, 0xe1, 0xae, + 0xa3, 0xc5, 0x4e, 0x88, 0x0f, 0x6f, 0x71, 0x90, 0x80, 0xa8, 0x43, 0x56, 0x97, 0x90, 0x8d, 0x36, 0x74, 0x71, 0x2f, + 0x4a, 0x61, 0xc2, 0x5a, 0x26, 0x55, 0x89, 0x0e, 0x82, 0x54, 0xed, 0xb6, 0x08, 0x2c, 0x51, 0xb0, 0x03, 0xd8, 0x7b, + 0x3b, 0xea, 0x7a, 0xd4, 0x64, 0x75, 0xf2, 0x25, 0xf8, 0x38, 0xcd, 0xba, 0x0a, 0xd2, 0x0b, 0xbb, 0x2e, 0xd7, 0x3c, + 0x50, 0xb1, 0xa9, 0xa4, 0x31, 0x71, 0x97, 0x16, 0x19, 0xe2, 0x01, 0x63, 0x2c, 0x5d, 0x08, 0xe4, 0x9b, 0xed, 0x8e, + 0x9b, 0x9a, 0x20, 0xf4, 0x13, 0xd6, 0x94, 0xc0, 0x4e, 0x67, 0x7b, 0x6a, 0xfc, 0x7c, 0x40, 0xc4, 0x61, 0x40, 0x81, + 0x64, 0xe3, 0x90, 0xe6, 0x48, 0x5f, 0x90, 0x34, 0x61, 0x60, 0x68, 0xc9, 0x73, 0x82, 0xac, 0x28, 0x74, 0x6c, 0x5d, + 0x95, 0x71, 0xae, 0x08, 0x73, 0xb4, 0xe4, 0x94, 0xf8, 0x9c, 0x20, 0x13, 0xdb, 0xd3, 0x36, 0x3d, 0x19, 0x96, 0x92, + 0x05, 0xfa, 0x57, 0x10, 0x25, 0xf6, 0x4c, 0x33, 0x2a, 0x07, 0xed, 0x02, 0x16, 0x28, 0xe5, 0x7b, 0xd0, 0x78, 0x6b, + 0x68, 0xa3, 0x60, 0x88, 0xed, 0xfe, 0x04, 0xfb, 0xb5, 0x76, 0x5a, 0x97, 0x29, 0x96, 0x93, 0x29, 0x44, 0x7b, 0x21, + 0xfd, 0x1b, 0x45, 0xa2, 0x3b, 0x45, 0x68, 0x12, 0xd6, 0x51, 0xf6, 0xa4, 0x4d, 0x0d, 0xa0, 0xa7, 0x4e, 0xc0, 0xf3, + 0xce, 0xb5, 0x0c, 0xbb, 0x48, 0xf5, 0x57, 0x06, 0x9f, 0x52, 0x0d, 0x82, 0x14, 0xb5, 0x49, 0xc1, 0x9c, 0xd7, 0xa1, + 0xa4, 0xce, 0x9c, 0xb6, 0xcc, 0xa8, 0x3a, 0x2a, 0x42, 0xca, 0x09, 0xfe, 0x93, 0x57, 0x42, 0x11, 0x9b, 0x30, 0xc1, + 0x03, 0x1f, 0xe6, 0x19, 0x36, 0xf0, 0x76, 0xfb, 0x2e, 0x0d, 0x93, 0x36, 0xdb, 0x90, 0x82, 0xb4, 0x42, 0xc7, 0xc5, + 0x80, 0xca, 0x5e, 0xe1, 0x7e, 0xc1, 0x76, 0xd2, 0x14, 0x3c, 0x08, 0xbd, 0x06, 0x26, 0x76, 0x75, 0xf1, 0x75, 0x98, + 0xd0, 0x70, 0x49, 0x95, 0xb3, 0x93, 0x92, 0x34, 0xb7, 0xd7, 0xe5, 0xa5, 0xe9, 0x83, 0x8a, 0x1d, 0xd6, 0x35, 0x3c, + 0xd0, 0x3c, 0xbf, 0x8b, 0x2b, 0xa6, 0x68, 0xa2, 0xb6, 0x1e, 0x92, 0x96, 0x1c, 0xeb, 0x66, 0xba, 0xc2, 0xd5, 0x32, + 0x53, 0xc0, 0xee, 0x02, 0x2f, 0xf4, 0x80, 0x87, 0x1d, 0xae, 0x48, 0x74, 0x89, 0xcd, 0x66, 0xab, 0x86, 0x4c, 0xf3, + 0x7d, 0xd9, 0x72, 0x1d, 0x10, 0xce, 0x50, 0xdf, 0xdc, 0x25, 0xc7, 0x8a, 0xb6, 0xb9, 0x49, 0x80, 0xe3, 0xed, 0x14, + 0x90, 0x74, 0x2c, 0x41, 0x1b, 0xdf, 0xd2, 0x1d, 0x44, 0xaa, 0xa7, 0x82, 0xee, 0x9d, 0x2f, 0xd2, 0xf8, 0x5f, 0x80, + 0x6d, 0xd4, 0x46, 0x9b, 0x66, 0x65, 0xeb, 0x30, 0x91, 0x16, 0xd6, 0xc8, 0x42, 0x2e, 0xc1, 0x07, 0x73, 0xb7, 0xa9, + 0xd3, 0xd3, 0x0e, 0x22, 0xec, 0x76, 0xd1, 0xe1, 0x11, 0xc6, 0x92, 0x35, 0x48, 0x34, 0xab, 0xb0, 0xa6, 0xfe, 0x72, + 0x88, 0x72, 0xaa, 0x97, 0x4c, 0xb4, 0xa4, 0x2e, 0xa5, 0x88, 0x52, 0x30, 0x37, 0x9e, 0x16, 0x9e, 0x29, 0x21, 0x42, + 0x56, 0x08, 0x0b, 0x54, 0x6b, 0xa0, 0xa5, 0x7c, 0xd0, 0xeb, 0xd0, 0xc9, 0x42, 0x63, 0x0a, 0xa2, 0x8f, 0x48, 0x73, + 0x23, 0x96, 0x8c, 0xee, 0x8e, 0x51, 0x4c, 0x20, 0x54, 0xb5, 0x93, 0x17, 0x56, 0x9f, 0x92, 0x6d, 0x75, 0x10, 0xd7, + 0x98, 0x26, 0x7b, 0x08, 0x6a, 0x8c, 0x82, 0x36, 0xab, 0x1b, 0xfd, 0xa5, 0x0c, 0x5d, 0xbb, 0x70, 0xec, 0x46, 0x49, + 0x04, 0x44, 0x60, 0x75, 0x9a, 0x8a, 0x01, 0x59, 0xe7, 0xb1, 0x8d, 0xd0, 0xa4, 0xba, 0x85, 0x28, 0x6f, 0x54, 0x34, + 0x1f, 0xd7, 0x21, 0xd9, 0x6e, 0xb1, 0x2c, 0xf0, 0x65, 0x3f, 0x5b, 0xef, 0x81, 0xfc, 0x7e, 0xbd, 0xfe, 0x24, 0xe4, + 0xf7, 0xab, 0xec, 0x73, 0x20, 0xbf, 0x5f, 0xaf, 0xff, 0xa7, 0x21, 0xbf, 0xcf, 0xd6, 0x0e, 0xe4, 0xb7, 0x1c, 0x8c, + 0xdf, 0x4a, 0x16, 0xbc, 0x7d, 0x13, 0xd0, 0xe7, 0x82, 0x05, 0x6f, 0x5f, 0xbe, 0x74, 0x84, 0xe9, 0xdf, 0xe9, 0x38, + 0x2f, 0x5a, 0x16, 0x8c, 0xb8, 0x2d, 0xf0, 0x0a, 0xb5, 0x4e, 0x2e, 0x50, 0x51, 0x06, 0xc0, 0xeb, 0xd5, 0x3f, 0xb2, + 0x7a, 0x19, 0x06, 0x87, 0x01, 0x99, 0x59, 0x48, 0xd0, 0xe1, 0x04, 0x6e, 0x6f, 0x68, 0x64, 0x59, 0x7f, 0x16, 0x7c, + 0xf8, 0x68, 0x34, 0x8a, 0xcb, 0x2b, 0xbc, 0xd4, 0xe9, 0x8d, 0x84, 0x80, 0xc7, 0x19, 0xaf, 0x4c, 0x88, 0x88, 0x65, + 0x5c, 0x9d, 0xab, 0xd8, 0x2c, 0x95, 0xd9, 0x8a, 0x10, 0x71, 0xfe, 0x1c, 0x70, 0xea, 0xcd, 0xde, 0x8c, 0xb1, 0x1f, + 0x92, 0x23, 0x56, 0x01, 0x64, 0x9f, 0xad, 0xd5, 0xbb, 0x8b, 0xb8, 0xe2, 0xef, 0xe2, 0x7a, 0xc9, 0xa0, 0x97, 0x70, + 0x17, 0x29, 0x78, 0x52, 0x3b, 0x6c, 0x93, 0x04, 0x2a, 0xcf, 0x14, 0x50, 0x79, 0xc7, 0x7b, 0x1a, 0x9a, 0x61, 0x51, + 0x3e, 0xc0, 0x5a, 0xba, 0x9c, 0x81, 0xd1, 0xe2, 0x8b, 0x1b, 0x5e, 0xd4, 0x3f, 0x01, 0x9e, 0x7a, 0xc1, 0x4b, 0xb8, + 0x25, 0x20, 0x17, 0xeb, 0x39, 0x21, 0xd0, 0xca, 0xf5, 0xec, 0x90, 0x51, 0x63, 0xb4, 0x68, 0xc2, 0xeb, 0x37, 0xde, + 0x84, 0xd0, 0xbb, 0x13, 0x74, 0x45, 0x18, 0x09, 0xef, 0xcf, 0x35, 0x3f, 0xcf, 0xc0, 0x7c, 0xbe, 0x02, 0x28, 0x0d, + 0x84, 0x43, 0x65, 0x52, 0x6e, 0x81, 0x09, 0x1b, 0x6d, 0xae, 0x94, 0xa5, 0x0e, 0x52, 0x29, 0x95, 0x70, 0xba, 0x15, + 0x4d, 0x05, 0xe0, 0x70, 0x47, 0x02, 0xc0, 0x4c, 0x4d, 0x61, 0x10, 0xdd, 0x36, 0xa5, 0x59, 0x1a, 0x59, 0x45, 0x9a, + 0xc5, 0x27, 0xa5, 0x12, 0x74, 0xfa, 0x3c, 0x89, 0x6b, 0x7e, 0x25, 0x4a, 0x08, 0x85, 0xdb, 0x4a, 0x69, 0x0c, 0x16, + 0x80, 0x3c, 0xee, 0xac, 0xcd, 0xd6, 0xac, 0x94, 0x29, 0xe7, 0xc5, 0xfa, 0x9a, 0x97, 0x59, 0x72, 0xbe, 0xcc, 0xaa, + 0x5a, 0x94, 0xf7, 0x6c, 0xae, 0xb2, 0x2e, 0xa2, 0x6a, 0xa4, 0x24, 0x5e, 0xe7, 0x35, 0xbf, 0x5e, 0x41, 0xe8, 0x87, + 0x75, 0x09, 0xac, 0xe7, 0xde, 0x2f, 0x65, 0x64, 0xd2, 0xb0, 0xf3, 0x3b, 0xb2, 0x00, 0xbd, 0x2b, 0xac, 0x11, 0xb9, + 0x00, 0x98, 0x5e, 0x33, 0xa7, 0x92, 0xaa, 0x94, 0xfe, 0x6b, 0x8d, 0x33, 0xef, 0x2f, 0xaa, 0x71, 0x6b, 0xed, 0x19, + 0xad, 0xad, 0x9f, 0x2a, 0xe1, 0x94, 0x80, 0x12, 0xb1, 0x13, 0x5c, 0x31, 0x29, 0x5d, 0x1b, 0xb4, 0x16, 0xb0, 0xac, + 0xc0, 0x1c, 0x59, 0x71, 0x75, 0x7e, 0x2b, 0x65, 0x35, 0x3d, 0x49, 0xcd, 0xd2, 0x28, 0x96, 0x28, 0x42, 0x4b, 0x16, + 0xae, 0x59, 0xb2, 0x27, 0xd7, 0x3a, 0x4a, 0x3c, 0x3c, 0xb0, 0xb8, 0x3d, 0xe8, 0xc7, 0x49, 0x3b, 0x65, 0xbb, 0xdd, + 0xc9, 0x04, 0x6c, 0x48, 0x2b, 0x09, 0x22, 0x83, 0x2c, 0x67, 0xc3, 0x49, 0x04, 0x70, 0x2d, 0x8a, 0x84, 0xfe, 0xb9, + 0xe6, 0x1a, 0xd0, 0x3e, 0x54, 0x5e, 0x85, 0x72, 0x15, 0xcd, 0xc1, 0xce, 0xcb, 0xed, 0x35, 0x04, 0x96, 0xe9, 0x9c, + 0x97, 0xc5, 0xfe, 0x35, 0xa0, 0xcc, 0x91, 0xd5, 0x0b, 0xb2, 0x6f, 0xc6, 0x55, 0xb6, 0x07, 0xa7, 0xb7, 0x35, 0x07, + 0x7b, 0x5b, 0xa3, 0x50, 0x7a, 0x13, 0x1e, 0x0e, 0x9f, 0x8e, 0x8d, 0x3f, 0x03, 0xae, 0x72, 0xf3, 0x5b, 0xee, 0x04, + 0xfb, 0x6c, 0x76, 0x03, 0xf5, 0x5d, 0x22, 0xda, 0x35, 0xd2, 0x6a, 0xcf, 0xb8, 0x35, 0xa4, 0xb1, 0x2b, 0xcd, 0x88, + 0xb9, 0x7e, 0x97, 0x47, 0xeb, 0xf9, 0xa3, 0x4d, 0xa6, 0xaa, 0x6c, 0x7e, 0xcf, 0x4c, 0x50, 0x3c, 0x8f, 0x4c, 0x35, + 0x6a, 0x0d, 0xba, 0x98, 0x74, 0x1d, 0xd9, 0xd4, 0x8c, 0xb2, 0xac, 0x93, 0xd6, 0x8d, 0xe4, 0x23, 0x97, 0x31, 0xc2, + 0xba, 0xb3, 0xf0, 0x3b, 0x9e, 0x84, 0x5d, 0x0d, 0x93, 0xd7, 0x10, 0xdd, 0x05, 0x84, 0xe9, 0x04, 0xe5, 0x43, 0xf8, + 0xfb, 0xa3, 0x8d, 0x4f, 0x3b, 0x9b, 0x43, 0xe7, 0x33, 0xfc, 0x9d, 0xa5, 0xf0, 0xb7, 0x6e, 0x7e, 0xa7, 0x9b, 0x6b, + 0x5e, 0x2f, 0x45, 0x1a, 0x05, 0xef, 0xde, 0x9e, 0x7d, 0x08, 0x14, 0xfc, 0x3b, 0x5e, 0x82, 0xb4, 0xda, 0x5b, 0x03, + 0x4d, 0x81, 0x1a, 0x28, 0x17, 0x57, 0x88, 0x80, 0xa8, 0x20, 0xf4, 0xcf, 0x96, 0xe2, 0xf6, 0x34, 0xcf, 0x5d, 0x4e, + 0x5d, 0x53, 0x77, 0xc5, 0xbc, 0x7a, 0xa4, 0x31, 0x04, 0x81, 0xe3, 0x28, 0xab, 0xce, 0x95, 0x8a, 0x28, 0x3d, 0xbf, + 0xb8, 0x3f, 0x57, 0x62, 0x28, 0x03, 0x41, 0xf9, 0xec, 0xf7, 0xe3, 0x34, 0xbb, 0x39, 0xc0, 0x23, 0x88, 0x05, 0x60, + 0xbf, 0x9f, 0xf3, 0x8b, 0x75, 0x5d, 0x8b, 0x62, 0x58, 0x8a, 0xdb, 0xe0, 0xe4, 0x58, 0x3e, 0xe8, 0x0c, 0xb1, 0x7c, + 0x0c, 0x0e, 0xfe, 0x2b, 0xc9, 0xb3, 0xe4, 0x23, 0x0b, 0x1e, 0x6d, 0x32, 0x76, 0xd2, 0x3a, 0x68, 0xc6, 0x4d, 0x70, + 0x02, 0x6d, 0x3d, 0x38, 0xcd, 0xf3, 0xe3, 0x43, 0xf9, 0xc5, 0xc9, 0xf1, 0x61, 0x9a, 0xdd, 0x9c, 0x38, 0xd1, 0x00, + 0xac, 0x71, 0x2f, 0xe2, 0xae, 0xd9, 0xcb, 0x3b, 0x78, 0xf1, 0x26, 0x3c, 0x34, 0xec, 0x0e, 0x88, 0x8c, 0x74, 0x2c, + 0x15, 0x14, 0x33, 0x85, 0x31, 0x1c, 0xee, 0xdb, 0x6d, 0x68, 0x2c, 0x8f, 0x12, 0x07, 0x96, 0xa7, 0x04, 0x76, 0x08, + 0xb3, 0xd0, 0x84, 0xd0, 0xa4, 0x21, 0xa1, 0x06, 0x0f, 0x8a, 0x09, 0x2d, 0x1b, 0x0a, 0xe7, 0xdd, 0xeb, 0x78, 0xa5, + 0x25, 0x6d, 0x4a, 0x72, 0xa1, 0x5b, 0x3f, 0xf3, 0xc6, 0x31, 0x6a, 0x8f, 0xaa, 0x86, 0xf3, 0xea, 0x15, 0xfb, 0x06, + 0x16, 0x84, 0xab, 0x61, 0x4d, 0x83, 0x16, 0x69, 0x01, 0xe1, 0xa8, 0x2b, 0xd3, 0xe3, 0x34, 0x9c, 0x17, 0x54, 0x2c, + 0x08, 0x3b, 0x09, 0x37, 0xc8, 0xdb, 0x17, 0x54, 0xb2, 0xfa, 0xa2, 0xb1, 0xd8, 0x9a, 0x72, 0x76, 0x4e, 0x1e, 0x6d, + 0x64, 0xcc, 0xde, 0x82, 0x9d, 0xf8, 0xf3, 0x55, 0xc7, 0x17, 0xc3, 0x25, 0x07, 0x27, 0xa0, 0xe0, 0xe0, 0xbf, 0xd2, + 0x8b, 0xdc, 0x4c, 0x8a, 0x5c, 0x91, 0xcb, 0xb8, 0x48, 0x73, 0xfe, 0x21, 0xbe, 0xf8, 0x01, 0xf3, 0x3c, 0xbf, 0xc8, + 0x9f, 0x41, 0x86, 0x26, 0x38, 0x79, 0xb4, 0x49, 0xea, 0xd1, 0x8b, 0x37, 0x1f, 0x5e, 0x7d, 0xf8, 0xe7, 0xf9, 0xb3, + 0xd3, 0x0f, 0x2f, 0xbe, 0x7f, 0xfb, 0xfe, 0xd5, 0x8b, 0xb3, 0xb9, 0xf1, 0xba, 0x95, 0x60, 0x6e, 0x64, 0xb1, 0xdd, + 0xda, 0x7c, 0xbf, 0xbc, 0x79, 0xfe, 0xe2, 0xe5, 0xab, 0x37, 0x2f, 0x9e, 0x37, 0x72, 0x2e, 0xdb, 0x0d, 0x81, 0x1d, + 0x1a, 0x67, 0x05, 0x2f, 0xa1, 0x78, 0x75, 0xbb, 0xc3, 0x66, 0x2b, 0x0c, 0x42, 0xbf, 0xe9, 0x2a, 0x5c, 0x03, 0x2c, + 0xb2, 0x03, 0xb5, 0x59, 0xa0, 0xe1, 0x42, 0x6f, 0x1c, 0x77, 0x89, 0xb9, 0xbd, 0x79, 0x81, 0xdf, 0xbd, 0x17, 0xb7, + 0xba, 0x2b, 0x6a, 0x84, 0x24, 0xbc, 0xd8, 0xec, 0xd9, 0xef, 0xc7, 0xae, 0x48, 0x0f, 0xe5, 0x1e, 0xb2, 0x5c, 0xf8, + 0xd5, 0x04, 0x07, 0xca, 0xbc, 0x30, 0x80, 0xe8, 0x18, 0xc1, 0xc9, 0xf1, 0xa1, 0x9b, 0xfb, 0xe4, 0xf7, 0xe8, 0x27, + 0xa7, 0x73, 0x58, 0x2a, 0x8c, 0x83, 0x9f, 0xb6, 0x73, 0x2c, 0x02, 0x7d, 0xb6, 0x07, 0xa7, 0x5c, 0x41, 0x9a, 0x5c, + 0x09, 0x12, 0x99, 0x49, 0x94, 0x66, 0x33, 0xba, 0xb4, 0xdf, 0xd5, 0x5f, 0xdb, 0x67, 0x14, 0x43, 0xf0, 0xa2, 0x12, + 0x25, 0xd8, 0xb8, 0x38, 0x89, 0x49, 0x0e, 0x82, 0x0f, 0x1e, 0x40, 0xef, 0xda, 0xa1, 0x2e, 0x0e, 0x9c, 0x90, 0x32, + 0xd8, 0xcf, 0x4e, 0xa2, 0x0f, 0xe3, 0x74, 0xd8, 0xfe, 0xd4, 0xe9, 0xee, 0xef, 0xc4, 0xfe, 0x38, 0x50, 0x5d, 0x6c, + 0x11, 0x1d, 0xd3, 0xec, 0xfd, 0x21, 0x49, 0xe6, 0x6f, 0xff, 0x4f, 0x73, 0x4f, 0xbb, 0xdd, 0xb6, 0x71, 0xe5, 0xff, + 0x3e, 0x05, 0x0c, 0xbb, 0x0e, 0x60, 0x03, 0x10, 0x40, 0x8a, 0x92, 0x4c, 0x8a, 0x52, 0x13, 0xdb, 0x39, 0x51, 0xaa, + 0xd4, 0x39, 0x8e, 0xea, 0x6d, 0xa3, 0xe8, 0x98, 0x43, 0x70, 0x48, 0xa2, 0x02, 0x01, 0x1e, 0x00, 0x94, 0xa8, 0xd0, + 0xe8, 0x53, 0xec, 0xff, 0xed, 0x73, 0xec, 0xfe, 0xeb, 0x13, 0xed, 0x23, 0xec, 0xb9, 0x77, 0x3e, 0x30, 0xf8, 0x22, + 0xa9, 0xc4, 0x69, 0xf7, 0xa4, 0xaa, 0x89, 0xc1, 0xcc, 0x60, 0xe6, 0xce, 0xcc, 0x9d, 0xfb, 0x7d, 0x03, 0xeb, 0x3f, + 0x62, 0x6b, 0x46, 0xac, 0x05, 0xb1, 0x6e, 0xd3, 0x9b, 0xbc, 0x71, 0xcd, 0x64, 0xba, 0x1b, 0x4c, 0x89, 0x68, 0x18, + 0x10, 0x37, 0x83, 0x73, 0x33, 0x9c, 0xc6, 0x0f, 0xc4, 0x05, 0x77, 0x45, 0x92, 0x19, 0x15, 0x89, 0x66, 0xc4, 0xdb, + 0x8c, 0x43, 0xc6, 0x2c, 0xc1, 0xcb, 0x30, 0xe8, 0xe3, 0xba, 0xa1, 0x6a, 0x37, 0x02, 0xc2, 0x18, 0x43, 0xf3, 0x09, + 0xb7, 0xac, 0x08, 0x1c, 0x3f, 0x4b, 0xc2, 0x3f, 0xd2, 0x07, 0x20, 0x5e, 0xd3, 0x2c, 0x5e, 0x02, 0xcb, 0x42, 0x66, + 0x5c, 0x04, 0x65, 0x19, 0xe9, 0x7e, 0x1f, 0x84, 0x64, 0x59, 0xb8, 0xe9, 0x81, 0xee, 0x75, 0xb2, 0x78, 0x36, 0x0b, + 0xa9, 0xa1, 0x8b, 0x1c, 0x2a, 0xba, 0x25, 0x3f, 0x73, 0xfe, 0xc4, 0x15, 0x81, 0x4b, 0xcd, 0xbc, 0xed, 0xf0, 0x0a, + 0xe8, 0x51, 0x19, 0xd9, 0x8f, 0x11, 0xf0, 0x28, 0xa2, 0xbe, 0x43, 0x2d, 0x0f, 0x5f, 0xe3, 0x02, 0x39, 0xd8, 0x93, + 0x78, 0x35, 0x0e, 0xa9, 0x8d, 0x07, 0x0a, 0x3e, 0xb9, 0x19, 0xaf, 0xc6, 0x63, 0x48, 0x56, 0xf3, 0xc4, 0xb5, 0x20, + 0xfc, 0x4e, 0x9c, 0x22, 0x5b, 0x9c, 0x9b, 0x03, 0x80, 0xa2, 0x93, 0x95, 0x87, 0xcf, 0xb2, 0x77, 0x82, 0xc4, 0x8b, + 0x7d, 0x20, 0x03, 0x16, 0xb8, 0x01, 0x2f, 0x0c, 0xf5, 0x1f, 0x60, 0x7f, 0xa7, 0xfa, 0xa0, 0x09, 0xb9, 0x0c, 0xaf, + 0xf5, 0x1f, 0x70, 0xb1, 0x30, 0x89, 0xf3, 0x6b, 0x76, 0x3e, 0x74, 0x4b, 0x67, 0xba, 0xff, 0x15, 0xa6, 0x73, 0x00, + 0xd9, 0xf7, 0x9b, 0x80, 0xcc, 0xa2, 0x38, 0xcd, 0x02, 0x5f, 0xbf, 0x19, 0x5c, 0x04, 0xc6, 0xf5, 0x22, 0x33, 0xcc, + 0x1b, 0xcb, 0xcf, 0xd4, 0x4c, 0x30, 0x02, 0x25, 0x63, 0x22, 0x98, 0xb6, 0x4a, 0xea, 0x19, 0xdd, 0x5a, 0x51, 0x20, + 0x7f, 0xac, 0xe4, 0x67, 0x43, 0xa8, 0x57, 0x49, 0x2b, 0x83, 0xf9, 0xb1, 0x74, 0x6c, 0x69, 0x0e, 0x18, 0xc3, 0xf6, + 0x7a, 0xb5, 0x41, 0x62, 0x21, 0x2b, 0xee, 0x63, 0x0c, 0x85, 0x2c, 0xfc, 0x87, 0xd8, 0xf3, 0x13, 0xd5, 0xf6, 0xb5, + 0x74, 0xb3, 0x8f, 0xbe, 0x2c, 0x53, 0x1e, 0x40, 0x21, 0x80, 0xe1, 0x49, 0x14, 0x67, 0x1a, 0xc4, 0xf7, 0x81, 0x2f, + 0x8e, 0xaa, 0xb6, 0x72, 0xbc, 0x57, 0xc3, 0xcc, 0x39, 0xba, 0xf9, 0x0a, 0xaf, 0x57, 0x83, 0x47, 0x79, 0x2b, 0x05, + 0xf2, 0x60, 0x3c, 0x53, 0x0a, 0x0b, 0x98, 0xc5, 0x97, 0xf1, 0x7d, 0x55, 0x1d, 0xf4, 0x7a, 0xb4, 0xfb, 0x76, 0x37, + 0x04, 0x05, 0x2f, 0x92, 0x1b, 0x1a, 0x3c, 0x3f, 0xab, 0x20, 0xa5, 0x2a, 0xa7, 0x0a, 0xed, 0x5f, 0x04, 0x95, 0x94, + 0x81, 0xb9, 0x1c, 0xde, 0x36, 0x80, 0xf4, 0x38, 0x21, 0x30, 0xca, 0x91, 0x6c, 0x95, 0xc9, 0x9f, 0x84, 0xc3, 0x4b, + 0x79, 0xdc, 0xe9, 0x10, 0xa5, 0xb2, 0xf3, 0x60, 0x36, 0xd7, 0xcf, 0x33, 0xbe, 0x23, 0x55, 0x7a, 0xf7, 0x23, 0xbc, + 0xea, 0x37, 0xbd, 0x81, 0x84, 0x54, 0x0d, 0xf5, 0xc3, 0xf8, 0x1e, 0xbc, 0xd9, 0x8b, 0x5e, 0x39, 0x35, 0xdd, 0xda, + 0xb9, 0xf9, 0x52, 0xd6, 0x80, 0xac, 0xe2, 0x66, 0x7f, 0x4b, 0x83, 0xf6, 0x6f, 0x56, 0x7b, 0xb1, 0xe2, 0x47, 0x8d, + 0xc1, 0xfe, 0x2c, 0x63, 0xa8, 0xf4, 0x32, 0x88, 0x86, 0xd1, 0x99, 0x2c, 0x5a, 0x90, 0x35, 0x36, 0x30, 0xcf, 0xeb, + 0x45, 0xfd, 0xc8, 0x8a, 0x87, 0xf1, 0x9e, 0x75, 0x63, 0x6e, 0x78, 0x4c, 0xcf, 0x47, 0x29, 0xcd, 0xce, 0x1b, 0xc6, + 0x02, 0x1b, 0x61, 0xf8, 0x6c, 0x13, 0xe5, 0xa3, 0x7e, 0x4b, 0x15, 0xf6, 0xd6, 0x22, 0xbb, 0x3b, 0x89, 0xb7, 0x76, + 0x12, 0xe7, 0xa3, 0xc7, 0x6c, 0x73, 0x9f, 0xef, 0xf2, 0x70, 0xe0, 0x37, 0x61, 0xfa, 0xb0, 0x71, 0xcf, 0x43, 0x30, + 0xda, 0xd2, 0x6e, 0x4f, 0x70, 0xb7, 0xff, 0xef, 0x7f, 0xfd, 0xe7, 0x7f, 0x17, 0x64, 0xef, 0x38, 0x39, 0x3b, 0xc5, + 0x8c, 0x64, 0x40, 0xc5, 0xe5, 0xa7, 0x07, 0xec, 0x37, 0x16, 0xff, 0x8b, 0x46, 0x45, 0xc4, 0xa8, 0xfe, 0x47, 0x3d, + 0x83, 0x22, 0x8b, 0xbb, 0xc8, 0xa1, 0xae, 0x90, 0xe0, 0x40, 0x43, 0x45, 0xcb, 0x55, 0x86, 0x51, 0xbf, 0x61, 0x1c, + 0x34, 0xd7, 0x35, 0x8c, 0x22, 0x0c, 0xb4, 0x58, 0xc1, 0x0c, 0xe6, 0xba, 0x16, 0x4c, 0xea, 0x65, 0x9c, 0xc9, 0x05, + 0x62, 0x04, 0xa9, 0x38, 0x94, 0x99, 0xc3, 0x63, 0xc2, 0xa7, 0xe3, 0x5b, 0x45, 0xca, 0x0c, 0xc3, 0x47, 0xdd, 0x72, + 0xc3, 0xfd, 0xec, 0xb3, 0x7e, 0x06, 0xfb, 0x4e, 0x73, 0x04, 0xf0, 0x3d, 0x87, 0xed, 0x33, 0x7c, 0xb6, 0x21, 0xc0, + 0xaf, 0xe5, 0x3a, 0x4c, 0xb4, 0xf0, 0x19, 0x2c, 0xa6, 0x07, 0x88, 0x9d, 0x95, 0x6b, 0x68, 0x34, 0x34, 0xe4, 0xa6, + 0x41, 0xcb, 0x24, 0x58, 0x90, 0xe4, 0x81, 0x59, 0x12, 0x59, 0xaa, 0xb9, 0x91, 0xa9, 0x6b, 0x8c, 0x7a, 0x63, 0xf3, + 0x65, 0x84, 0x9c, 0xae, 0xfd, 0x41, 0x96, 0x51, 0x3e, 0x39, 0x81, 0xbe, 0x74, 0xf8, 0xd6, 0x47, 0xfd, 0x25, 0x75, + 0x26, 0x34, 0x23, 0x41, 0xc8, 0x9a, 0x0c, 0x8c, 0xa8, 0x65, 0x36, 0x51, 0x79, 0x36, 0x69, 0x19, 0x65, 0xe3, 0x64, + 0x18, 0x05, 0xc7, 0xc6, 0x8d, 0x33, 0x43, 0x14, 0xda, 0xbc, 0x80, 0xec, 0x9d, 0xb2, 0x97, 0x00, 0xf8, 0x49, 0x7d, + 0x17, 0xe5, 0xed, 0x4b, 0xd4, 0x50, 0xfb, 0xb7, 0x59, 0x36, 0x0a, 0xcb, 0x96, 0xc2, 0xb2, 0xd1, 0xc8, 0x8f, 0x27, + 0xf4, 0xcf, 0xef, 0x2f, 0x64, 0xa6, 0x3f, 0x10, 0x5a, 0x8f, 0xf8, 0x25, 0x12, 0x21, 0x37, 0x91, 0x20, 0x27, 0xc1, + 0x72, 0xf2, 0x69, 0x72, 0xab, 0x25, 0xb9, 0xae, 0x9d, 0xb3, 0x49, 0xd3, 0x09, 0x9b, 0xc9, 0x30, 0xc6, 0x56, 0x49, + 0x7e, 0x7a, 0xc0, 0x6a, 0x33, 0x2a, 0x97, 0x55, 0x02, 0xf8, 0x25, 0x30, 0xeb, 0x02, 0x7c, 0x90, 0x94, 0x78, 0xe8, + 0x15, 0xe2, 0x05, 0x67, 0x81, 0xaa, 0x41, 0xef, 0xbc, 0xcc, 0xb8, 0x60, 0x2b, 0xbd, 0x38, 0xd4, 0x31, 0x04, 0x26, + 0x12, 0xe7, 0x5a, 0xab, 0x9c, 0x9c, 0xa2, 0x13, 0x21, 0xf2, 0xe9, 0xf3, 0x0e, 0x1e, 0x75, 0xa4, 0x00, 0x6b, 0x43, + 0x29, 0xc9, 0x75, 0x6d, 0xc1, 0x19, 0x25, 0x1e, 0x01, 0x0d, 0xc2, 0xa3, 0xb8, 0x70, 0xcf, 0xea, 0xda, 0x82, 0xac, + 0x71, 0xe6, 0xe2, 0x0d, 0x59, 0x1b, 0x1e, 0x7f, 0x55, 0x9c, 0xc9, 0xa8, 0xbc, 0xe0, 0x02, 0xc5, 0x80, 0xef, 0x93, + 0x14, 0xd0, 0xcd, 0xd1, 0xa6, 0xa4, 0x61, 0x71, 0xe7, 0x62, 0x71, 0x27, 0x2d, 0x8b, 0x3b, 0xd9, 0xb2, 0xb8, 0x21, + 0x5f, 0x48, 0x4d, 0x82, 0x2e, 0x41, 0x7f, 0xd6, 0x02, 0x29, 0x32, 0x06, 0xa3, 0xcf, 0x0f, 0x28, 0xc2, 0xc9, 0x4e, + 0x43, 0xb0, 0xe7, 0x6c, 0x81, 0x55, 0x13, 0x5c, 0x14, 0x40, 0xd4, 0x27, 0x2e, 0x8f, 0xab, 0x44, 0xad, 0xd6, 0x1c, + 0xb6, 0xaa, 0x5f, 0xa5, 0x79, 0x43, 0xd6, 0xd0, 0x32, 0xe6, 0x2d, 0x33, 0x9d, 0x6f, 0x99, 0xa9, 0x5f, 0x3a, 0xf3, + 0x7c, 0xda, 0xec, 0xf4, 0xaa, 0x93, 0x62, 0xa4, 0xd0, 0x3a, 0xc3, 0x2d, 0x53, 0xde, 0x87, 0xed, 0xb8, 0x58, 0xd9, + 0x51, 0x4b, 0x92, 0xa6, 0xf7, 0x71, 0x02, 0x4a, 0x62, 0xe8, 0xe6, 0x71, 0x5b, 0x6a, 0x11, 0x44, 0x3c, 0xfe, 0x54, + 0xeb, 0x66, 0x2a, 0xde, 0xab, 0x5b, 0xaa, 0xd3, 0xeb, 0xb1, 0x1a, 0x4b, 0x92, 0x65, 0x34, 0x41, 0xa0, 0x13, 0x48, + 0x54, 0xf0, 0xff, 0x64, 0x9b, 0x35, 0xe0, 0x90, 0xd0, 0x2c, 0xae, 0x03, 0x44, 0xed, 0x4b, 0xe0, 0x83, 0x12, 0x41, + 0x34, 0x2b, 0xb1, 0x2c, 0x13, 0x09, 0x78, 0x4e, 0xdf, 0x24, 0x8a, 0xb7, 0xa5, 0x77, 0x64, 0x3a, 0x4b, 0x32, 0xf9, + 0x01, 0x6c, 0x11, 0x8c, 0x8e, 0x05, 0x7e, 0x05, 0x6a, 0xe4, 0xca, 0x84, 0x31, 0x66, 0x7e, 0x81, 0x24, 0x11, 0x4b, + 0x72, 0xab, 0x4d, 0x70, 0xf8, 0x26, 0xf6, 0xf4, 0x66, 0xd3, 0xc9, 0x0f, 0x66, 0x81, 0x59, 0xc3, 0x9a, 0x80, 0xda, + 0xc2, 0xe1, 0x99, 0x94, 0xc0, 0x84, 0x96, 0x77, 0x64, 0x82, 0xb2, 0xea, 0x1a, 0x52, 0x30, 0xbb, 0x42, 0xbc, 0x35, + 0x4a, 0xe0, 0x76, 0xbb, 0x76, 0x6f, 0xf2, 0xe7, 0x33, 0xfc, 0xe5, 0xdd, 0xe4, 0xcf, 0xc7, 0xf8, 0xab, 0x73, 0x83, + 0xc9, 0x36, 0x1b, 0xc4, 0x7a, 0xca, 0x9c, 0xf5, 0xb3, 0xd2, 0x7e, 0x62, 0x26, 0xb3, 0x8f, 0xd8, 0x36, 0x7c, 0x81, + 0x9f, 0x3e, 0xdb, 0x44, 0xe0, 0x24, 0xae, 0xce, 0x21, 0x75, 0x12, 0x33, 0x6f, 0x2c, 0x9f, 0xb5, 0x94, 0x8f, 0xcd, + 0x7f, 0x31, 0x81, 0x80, 0xbb, 0x24, 0x2e, 0xee, 0x94, 0xb2, 0x50, 0xf2, 0xe3, 0x38, 0x88, 0x48, 0xf2, 0xf0, 0x91, + 0xc9, 0x14, 0x0c, 0xc1, 0x67, 0x4b, 0x61, 0x2b, 0x63, 0x05, 0xcb, 0x1a, 0xfa, 0x4c, 0xd1, 0x49, 0x3d, 0x70, 0x0a, + 0x61, 0xf8, 0x97, 0x44, 0xa1, 0x3d, 0x4b, 0xe2, 0x28, 0xbe, 0x20, 0xa5, 0x0f, 0x7d, 0x7c, 0xb6, 0x31, 0x68, 0xbd, + 0x9b, 0x9a, 0xb8, 0xa2, 0x44, 0x10, 0xc0, 0xf2, 0xa0, 0x68, 0x6b, 0x31, 0x09, 0xfa, 0xa8, 0x82, 0x1f, 0xc7, 0x6b, + 0xfb, 0xd9, 0x26, 0x3b, 0xd7, 0x17, 0x24, 0xb9, 0xa5, 0x13, 0xdb, 0x0f, 0x12, 0x3f, 0xa4, 0x7a, 0x5f, 0x1f, 0x87, + 0x24, 0xba, 0xe5, 0x8f, 0x76, 0xbc, 0xca, 0xd0, 0xa8, 0x66, 0xa7, 0x24, 0x4c, 0xc0, 0x84, 0x09, 0xf0, 0x91, 0x41, + 0x6b, 0x80, 0x82, 0xf6, 0x5a, 0x8a, 0xbf, 0x0b, 0x82, 0xb2, 0xa8, 0x65, 0x81, 0x4d, 0x38, 0xd8, 0xf9, 0x80, 0x93, + 0xbd, 0xa5, 0xe3, 0x7a, 0xe9, 0x96, 0x3a, 0x55, 0xa6, 0xf7, 0x90, 0x59, 0x62, 0x3f, 0x62, 0x0f, 0xbf, 0xfc, 0x73, + 0x50, 0xf2, 0x98, 0xcf, 0x71, 0xe2, 0xb0, 0xfd, 0x83, 0x6a, 0x63, 0x92, 0xa6, 0xab, 0x05, 0x9d, 0x30, 0x7b, 0x82, + 0xf3, 0x62, 0x28, 0x65, 0x46, 0x5c, 0x1d, 0xce, 0x4f, 0xab, 0xce, 0xf1, 0xe1, 0x6b, 0xb0, 0x73, 0x02, 0x62, 0x30, + 0x9e, 0x4e, 0xf5, 0x42, 0xbc, 0xb6, 0xa3, 0x99, 0x77, 0xf8, 0xd3, 0xea, 0xeb, 0xb7, 0xee, 0xd7, 0xb2, 0x71, 0xa4, + 0x9b, 0xf9, 0x48, 0x18, 0x6d, 0x70, 0x9a, 0x56, 0x19, 0xaf, 0x98, 0xd1, 0x94, 0x44, 0xed, 0xd3, 0xb9, 0x2e, 0xed, + 0xb2, 0x25, 0xa5, 0x13, 0xb0, 0xe7, 0xb7, 0x6a, 0xa5, 0x1f, 0x43, 0x7a, 0x47, 0xa5, 0x41, 0x48, 0xfd, 0x63, 0x0d, + 0x2d, 0x30, 0x62, 0x25, 0x37, 0x34, 0xe1, 0x84, 0x95, 0x32, 0xa5, 0x11, 0xce, 0x81, 0xcf, 0x5c, 0xdd, 0xe5, 0x95, + 0x5d, 0x3d, 0xb2, 0x74, 0x65, 0x00, 0xad, 0x23, 0x3b, 0x6f, 0x29, 0xef, 0x63, 0xba, 0xfa, 0xe6, 0xb1, 0x59, 0x9e, + 0xd9, 0x87, 0x08, 0xff, 0x1c, 0x4e, 0x21, 0x6c, 0x7e, 0x43, 0x4a, 0x22, 0x07, 0x6d, 0x10, 0x6b, 0x92, 0x5a, 0xeb, + 0x4c, 0xf0, 0x29, 0x6c, 0xa4, 0xd1, 0x59, 0x40, 0x08, 0x86, 0x1b, 0xd7, 0x46, 0x2b, 0xcf, 0x7c, 0x8c, 0x69, 0xa0, + 0x23, 0x9a, 0xa6, 0xad, 0x00, 0x93, 0x8b, 0x6e, 0xe9, 0x45, 0xed, 0x32, 0x3c, 0x8a, 0x72, 0xcb, 0xb5, 0xe0, 0x56, + 0xc6, 0x09, 0x56, 0xbf, 0x85, 0x18, 0xfe, 0xe3, 0x82, 0x5b, 0xb9, 0x25, 0xb3, 0xb1, 0xce, 0x2d, 0x90, 0xda, 0xde, + 0xdf, 0xeb, 0x7c, 0x50, 0xa5, 0x9b, 0xb2, 0x71, 0x68, 0x46, 0x09, 0xfb, 0xd5, 0xc4, 0xb4, 0xd8, 0x81, 0x18, 0x53, + 0x05, 0xc5, 0xd1, 0xe9, 0x94, 0xfa, 0x59, 0x6a, 0x0a, 0x59, 0xab, 0x8c, 0x39, 0x0d, 0xbe, 0x86, 0x4f, 0x86, 0xfa, + 0x9f, 0x20, 0xf2, 0x86, 0x08, 0xcd, 0xc6, 0x07, 0x24, 0xf8, 0x9d, 0x66, 0x30, 0xb1, 0x1e, 0xcb, 0x20, 0xe2, 0x5f, + 0xf9, 0xf4, 0x49, 0x98, 0x48, 0x94, 0xca, 0x71, 0x68, 0xfc, 0x0a, 0x28, 0xf6, 0x45, 0x2c, 0x6d, 0xe1, 0xb6, 0x23, + 0xa0, 0x6d, 0xc7, 0x77, 0xe3, 0x7d, 0xdd, 0xf3, 0xdc, 0x5c, 0xb7, 0xc0, 0xe3, 0xf3, 0x76, 0xdf, 0x43, 0x8f, 0xad, + 0xba, 0xd0, 0x6a, 0x15, 0x3d, 0xa6, 0x5d, 0xc7, 0x7b, 0xe5, 0xe9, 0x16, 0x33, 0xb4, 0x55, 0x70, 0x9b, 0x1f, 0xdf, + 0xd1, 0xe4, 0x57, 0x4f, 0xa5, 0xdc, 0xf9, 0x7e, 0xe3, 0x39, 0xf2, 0x5c, 0x40, 0xc2, 0x59, 0xbc, 0x7c, 0xc4, 0x14, + 0xba, 0xba, 0xa5, 0xfb, 0x61, 0x9c, 0x52, 0x75, 0x0e, 0x4c, 0x5e, 0xf1, 0x2b, 0x27, 0xf1, 0xfd, 0xfb, 0xb7, 0x3f, + 0xfc, 0xa0, 0x5b, 0x98, 0x3f, 0x38, 0x55, 0x7b, 0xe7, 0x1b, 0x6a, 0x07, 0xf6, 0x6f, 0xdc, 0x77, 0xec, 0x86, 0x61, + 0x7c, 0x65, 0x79, 0xcf, 0xb1, 0xb2, 0xda, 0x96, 0xe3, 0x37, 0x0f, 0xff, 0x32, 0x63, 0x06, 0xf7, 0x9a, 0x57, 0x03, + 0x6e, 0xd8, 0x7e, 0xbd, 0x95, 0x4a, 0x16, 0x41, 0xf4, 0xb1, 0xa1, 0x94, 0xac, 0x1b, 0x4a, 0x51, 0x36, 0x58, 0xc5, + 0x1f, 0xab, 0x78, 0xa1, 0xdc, 0xce, 0x90, 0xfe, 0x7d, 0x17, 0xb8, 0x14, 0x96, 0xe6, 0x57, 0x0c, 0x9a, 0xe7, 0x7f, + 0xa8, 0x8e, 0xba, 0xa1, 0x98, 0xf3, 0x21, 0x12, 0xb6, 0x5c, 0x97, 0xa3, 0xaa, 0xc9, 0xcb, 0x94, 0x9b, 0xda, 0xb8, + 0x59, 0x60, 0xfa, 0xa4, 0x70, 0xbe, 0xd9, 0x51, 0x19, 0x84, 0xb4, 0xb2, 0x76, 0x41, 0x13, 0x6c, 0xed, 0x3d, 0xff, + 0xe7, 0x3f, 0x1c, 0xe7, 0x9f, 0xff, 0xd8, 0x59, 0x15, 0xfa, 0xce, 0x81, 0x1d, 0xde, 0x55, 0x33, 0x1f, 0xa1, 0xd0, + 0x29, 0x1b, 0xbe, 0x1e, 0x8d, 0x06, 0x46, 0x09, 0x64, 0xe0, 0x33, 0x72, 0x5e, 0x2b, 0xe1, 0x78, 0xb5, 0xef, 0x9a, + 0x18, 0xcc, 0x01, 0x1a, 0xca, 0xeb, 0xab, 0x75, 0xb3, 0x33, 0xa7, 0x84, 0x5a, 0x5f, 0xb5, 0x9d, 0x0e, 0xa5, 0x98, + 0xb8, 0x2e, 0x1f, 0x99, 0x3c, 0xc7, 0x00, 0x8c, 0x8b, 0xab, 0x0f, 0x4a, 0x2b, 0x07, 0x7e, 0x36, 0x59, 0x79, 0x7c, + 0xbc, 0xac, 0x32, 0x42, 0xba, 0xd7, 0x08, 0x59, 0xdb, 0xf2, 0x18, 0x79, 0x7f, 0xb5, 0x51, 0xb2, 0x72, 0x31, 0x4e, + 0x0b, 0x45, 0x66, 0x3c, 0xca, 0x08, 0x67, 0x9a, 0xb8, 0x4a, 0xb0, 0xa4, 0xf6, 0xad, 0xa8, 0x2e, 0x94, 0x21, 0xec, + 0x5e, 0xf6, 0x73, 0x3d, 0x8c, 0xef, 0xd1, 0x59, 0x4f, 0xd5, 0x27, 0x73, 0x61, 0xc8, 0x69, 0x9a, 0x25, 0x71, 0x34, + 0x3b, 0xab, 0x5c, 0xde, 0x75, 0x3b, 0x1f, 0x90, 0x60, 0xb1, 0xaa, 0x65, 0xb1, 0x89, 0x3a, 0xca, 0xed, 0x5b, 0xea, + 0x7c, 0xc7, 0x4c, 0x98, 0x6a, 0x42, 0xb9, 0x1c, 0x45, 0xd7, 0x0d, 0x6a, 0x70, 0x95, 0x94, 0x2b, 0xbf, 0x96, 0x8f, + 0x07, 0x1c, 0xae, 0x67, 0xa3, 0x1c, 0x93, 0x1d, 0xbd, 0x6b, 0x33, 0x10, 0xfd, 0x7e, 0xb7, 0x81, 0xe8, 0xd5, 0x5e, + 0x06, 0xa2, 0xdf, 0x7f, 0x76, 0x03, 0xd1, 0x77, 0xaa, 0x81, 0x28, 0x6c, 0xe9, 0xb7, 0x74, 0x2f, 0xab, 0x4d, 0x61, + 0x0d, 0x15, 0xdf, 0xa7, 0x43, 0x8f, 0x53, 0xa6, 0xa9, 0x3f, 0xa7, 0xc0, 0x6d, 0xf3, 0x6d, 0x1a, 0xc6, 0x33, 0xb0, + 0xe0, 0xfc, 0xed, 0x6d, 0x2d, 0xc3, 0x78, 0xa6, 0x5a, 0x5a, 0xa6, 0x3c, 0xdc, 0x73, 0x11, 0xc2, 0x8d, 0x59, 0x37, + 0xba, 0x96, 0x38, 0x7b, 0xf6, 0xa1, 0xa9, 0xa4, 0xb4, 0x97, 0xa6, 0xab, 0x1d, 0x61, 0xff, 0xd8, 0x47, 0xd3, 0x49, + 0xd9, 0xb0, 0xf3, 0x32, 0x96, 0x49, 0x7b, 0x8a, 0x1e, 0xa4, 0x8b, 0x00, 0x0b, 0x12, 0xb3, 0xd1, 0x7f, 0x5a, 0x7b, + 0x5f, 0x5d, 0x7b, 0x83, 0xae, 0x07, 0x91, 0x19, 0x80, 0x57, 0xc3, 0x02, 0x77, 0xd0, 0xed, 0x42, 0xc1, 0xbd, 0x52, + 0xd0, 0x81, 0x82, 0x40, 0x29, 0xe8, 0x41, 0x81, 0xaf, 0x14, 0x1c, 0x41, 0xc1, 0x44, 0x29, 0x38, 0x86, 0x82, 0x3b, + 0x3d, 0xbf, 0x2e, 0x12, 0x39, 0x1d, 0x9b, 0x37, 0x16, 0xe3, 0x0d, 0x44, 0xd9, 0xb1, 0xe5, 0x81, 0x19, 0x23, 0x99, + 0xf5, 0x63, 0x8b, 0xc9, 0xe9, 0xfa, 0x89, 0x75, 0x3f, 0xa7, 0x2c, 0x4a, 0xfc, 0x1b, 0xbc, 0x3a, 0x9c, 0x2c, 0x06, + 0xa7, 0x09, 0x11, 0x7d, 0x45, 0xc0, 0x41, 0xd3, 0x4d, 0x10, 0xbd, 0x0c, 0xe4, 0xca, 0x89, 0x08, 0x36, 0xca, 0x5a, + 0x16, 0xef, 0xd8, 0xe7, 0x6c, 0xb9, 0x05, 0x0a, 0x4b, 0x2e, 0x43, 0x95, 0xef, 0x7d, 0x0e, 0x7b, 0x9e, 0x37, 0x74, + 0xbc, 0x9a, 0x69, 0x97, 0xf1, 0x6c, 0xa7, 0x69, 0x8e, 0xfa, 0x0a, 0x46, 0xa9, 0x33, 0x0d, 0x88, 0x2d, 0xb6, 0x25, + 0xff, 0x16, 0x7b, 0xcc, 0xcb, 0xf5, 0x33, 0x18, 0x9b, 0x96, 0x31, 0xc3, 0x30, 0xf8, 0x0e, 0xc0, 0x48, 0x39, 0xf5, + 0x97, 0x00, 0x67, 0xe5, 0xf9, 0x8a, 0x28, 0xe3, 0x39, 0xfb, 0x8e, 0xa6, 0x29, 0x99, 0x89, 0xfa, 0xf5, 0x71, 0x82, + 0x31, 0x9c, 0x64, 0xa3, 0x10, 0x80, 0x20, 0x13, 0x0b, 0x6a, 0x36, 0x4f, 0x49, 0x7c, 0xaf, 0x81, 0x55, 0x1d, 0x6c, + 0xa8, 0xc2, 0xfe, 0x27, 0x70, 0x60, 0x09, 0xcb, 0x38, 0x08, 0x0e, 0xff, 0x1d, 0x0d, 0xab, 0x85, 0x19, 0x99, 0x55, + 0x8b, 0xd8, 0x3e, 0xc8, 0xd5, 0xb1, 0x49, 0x93, 0x98, 0x52, 0xe1, 0xaf, 0xb1, 0xcb, 0x08, 0xe3, 0xd9, 0x6f, 0x6a, + 0x94, 0xb1, 0xc5, 0x30, 0xe7, 0x36, 0xb5, 0x82, 0x6c, 0xe4, 0x20, 0x8c, 0x35, 0x07, 0x40, 0xd8, 0x8f, 0xb2, 0xb9, + 0x8d, 0x7e, 0xa5, 0x46, 0x27, 0x32, 0x2d, 0x07, 0xd7, 0x76, 0x53, 0xf5, 0xa6, 0xef, 0x27, 0xb3, 0x31, 0x31, 0xbc, + 0xce, 0xb1, 0x25, 0xfe, 0x1c, 0xb7, 0x67, 0xe6, 0xd8, 0x83, 0x36, 0x09, 0xee, 0x36, 0xd3, 0x38, 0xca, 0xec, 0x29, + 0x59, 0x04, 0xe1, 0x43, 0x7f, 0x11, 0x47, 0x71, 0xba, 0x24, 0x3e, 0x1d, 0x14, 0x7c, 0xf1, 0x00, 0xe3, 0xb4, 0x70, + 0x57, 0x61, 0xcf, 0xe9, 0x24, 0x74, 0xc1, 0x5a, 0xcb, 0x30, 0x2c, 0xd3, 0x90, 0xae, 0x73, 0xfe, 0xf9, 0x52, 0x65, + 0x56, 0x15, 0xb7, 0x1c, 0x6b, 0x01, 0x84, 0x25, 0x8f, 0xf1, 0x02, 0x91, 0xcd, 0x06, 0x4b, 0x32, 0xc1, 0xb0, 0xa4, + 0x4e, 0xa7, 0x97, 0xd0, 0x85, 0xe6, 0xf4, 0x5a, 0x3b, 0x4f, 0xe2, 0xfb, 0x33, 0x18, 0x2d, 0x36, 0xb6, 0x53, 0x1a, + 0x4e, 0xf1, 0x8d, 0x8d, 0x6e, 0x65, 0xa2, 0x1f, 0x1b, 0xf9, 0x69, 0xe8, 0x8d, 0x2e, 0x06, 0xf0, 0xba, 0xdf, 0xd1, + 0xdc, 0xc1, 0x22, 0x88, 0x6c, 0x36, 0x9d, 0x63, 0x77, 0xa9, 0xf4, 0xa5, 0xc2, 0xcf, 0xdc, 0x60, 0x75, 0x4f, 0x73, + 0x07, 0xc0, 0x73, 0x4d, 0xc3, 0xf8, 0xbe, 0x3f, 0x0f, 0x26, 0x13, 0x1a, 0x0d, 0x70, 0xcc, 0xb2, 0x90, 0x86, 0x61, + 0xb0, 0x4c, 0x83, 0x74, 0xb0, 0x20, 0x6b, 0xde, 0xeb, 0x61, 0x5b, 0xaf, 0x5d, 0xde, 0x6b, 0x77, 0xef, 0x5e, 0x95, + 0x6e, 0xc0, 0x85, 0x8d, 0xf5, 0xc3, 0x87, 0xd6, 0xd3, 0xdc, 0xca, 0x3c, 0xf7, 0xee, 0x75, 0x99, 0xd0, 0xcd, 0x82, + 0x24, 0xb3, 0x20, 0xea, 0xbb, 0xb9, 0x73, 0xb7, 0x61, 0x1b, 0xe3, 0xe9, 0xc9, 0xc9, 0x49, 0xee, 0x4c, 0xc4, 0x93, + 0x3b, 0x99, 0xe4, 0x8e, 0x2f, 0x9e, 0xa6, 0x53, 0xd7, 0x9d, 0x4e, 0x73, 0x27, 0x10, 0x05, 0xdd, 0x8e, 0x3f, 0xe9, + 0x76, 0x72, 0xe7, 0x5e, 0xa9, 0x91, 0x3b, 0x94, 0x3f, 0x25, 0x74, 0x32, 0xc0, 0x8d, 0xc4, 0xec, 0xbc, 0xfb, 0xc7, + 0xae, 0x9b, 0x23, 0x06, 0xb8, 0x2e, 0xe1, 0x26, 0x14, 0xd9, 0xdc, 0x6c, 0xf6, 0xae, 0xa9, 0x15, 0x9f, 0xf3, 0xfd, + 0xc6, 0x7a, 0x13, 0x92, 0xdc, 0xde, 0x68, 0xca, 0x2c, 0x08, 0x61, 0xd5, 0x36, 0x02, 0x0c, 0xf6, 0xba, 0x0f, 0xf1, + 0xfa, 0x06, 0xe3, 0x38, 0x81, 0x33, 0x9b, 0x90, 0x49, 0xb0, 0x4a, 0xfb, 0x5e, 0x67, 0xb9, 0x16, 0x45, 0x7c, 0xaf, + 0x17, 0x05, 0x78, 0xf6, 0xfa, 0x69, 0x1c, 0x06, 0x13, 0x51, 0xd4, 0x76, 0x96, 0xbc, 0x8e, 0x39, 0xc0, 0x68, 0x15, + 0x01, 0xc6, 0x5c, 0x21, 0x61, 0xa8, 0x39, 0xdd, 0x54, 0xa3, 0x24, 0x45, 0x49, 0xad, 0xe6, 0xa6, 0x0c, 0x2e, 0x18, + 0x99, 0xc2, 0x3b, 0x5c, 0xae, 0xe5, 0x9e, 0xf7, 0x8e, 0x96, 0xeb, 0xfc, 0x0f, 0x0b, 0x3a, 0x09, 0x88, 0x66, 0x14, + 0xbb, 0xc9, 0x73, 0x41, 0x9a, 0x6b, 0x6e, 0x5a, 0xb6, 0xa9, 0x38, 0x16, 0x10, 0xd7, 0xf4, 0x49, 0xb0, 0x58, 0xc6, + 0x49, 0x46, 0xa2, 0x2c, 0xcf, 0x47, 0x37, 0x79, 0x3e, 0xb8, 0x0a, 0x8c, 0xeb, 0xbf, 0x1a, 0xec, 0x9e, 0x66, 0xda, + 0x8f, 0xdc, 0xbc, 0xb1, 0xde, 0x52, 0xd5, 0x52, 0x0a, 0xae, 0x31, 0xb4, 0x92, 0x52, 0x2b, 0xb3, 0x5b, 0xb2, 0x5e, + 0x99, 0x01, 0x59, 0x56, 0x67, 0x96, 0x57, 0xe5, 0x2a, 0x78, 0x03, 0x41, 0x85, 0xb7, 0x74, 0x78, 0xa5, 0x58, 0x5d, + 0x01, 0xb1, 0x82, 0x95, 0x99, 0x53, 0xd1, 0xb3, 0x36, 0x9a, 0xf1, 0xcb, 0xdd, 0x34, 0xe3, 0x8f, 0xd9, 0x3e, 0x34, + 0xe3, 0x97, 0x9f, 0x9d, 0x66, 0x7c, 0x56, 0x77, 0x2a, 0xba, 0x88, 0x87, 0xba, 0x94, 0xd5, 0xc3, 0xd5, 0x94, 0xb0, + 0x70, 0x5d, 0x17, 0xbf, 0xd8, 0x07, 0x48, 0xf4, 0xc6, 0x12, 0x50, 0xb2, 0x9b, 0x1b, 0x68, 0xf1, 0x77, 0xd1, 0xf0, + 0x2f, 0x89, 0xfa, 0x3c, 0x9d, 0x0e, 0xdf, 0xc4, 0x4a, 0x81, 0x7c, 0xe2, 0xf6, 0x0f, 0xa5, 0xd0, 0x2a, 0xec, 0x8d, + 0xb0, 0x6e, 0xc6, 0xe4, 0x33, 0x10, 0x99, 0x81, 0x59, 0xf3, 0x4f, 0xa4, 0xfd, 0xe6, 0xa0, 0x3c, 0x04, 0x43, 0x9a, + 0x52, 0x0b, 0xff, 0xbb, 0x9a, 0x44, 0x70, 0x46, 0x33, 0xee, 0x30, 0xff, 0xd5, 0xc3, 0xc5, 0xc4, 0xb8, 0x88, 0xcd, + 0x3c, 0x48, 0xdf, 0x55, 0xbd, 0xdf, 0xb8, 0x16, 0x65, 0xa8, 0x4e, 0x27, 0xe7, 0x76, 0x93, 0x6a, 0x76, 0x79, 0x78, + 0xcd, 0x9a, 0x9f, 0x97, 0x66, 0xda, 0x57, 0x1b, 0x72, 0x0e, 0xb4, 0x76, 0x19, 0x73, 0xd7, 0xa3, 0x0d, 0xa7, 0x00, + 0x31, 0x71, 0x1f, 0x06, 0x0d, 0x98, 0xb0, 0xe6, 0xc1, 0x24, 0xcf, 0xcd, 0x81, 0x00, 0x84, 0x72, 0xd1, 0xd2, 0x5d, + 0x44, 0x5c, 0x7a, 0x2f, 0xad, 0x03, 0xb8, 0xae, 0x8d, 0x29, 0xd2, 0x2e, 0x40, 0x35, 0xcd, 0xd5, 0x6e, 0x1c, 0x66, + 0xba, 0xc6, 0xc0, 0xc7, 0x4c, 0x16, 0x94, 0x09, 0x81, 0x2e, 0x55, 0xc2, 0x5f, 0xbc, 0x12, 0x05, 0x75, 0xdb, 0x68, + 0x06, 0x1c, 0xd4, 0xad, 0x43, 0x88, 0x0f, 0x21, 0x9e, 0x66, 0x68, 0x87, 0xd7, 0xc1, 0x87, 0x5c, 0x97, 0xb4, 0x1f, + 0x6e, 0x3f, 0x60, 0xcf, 0x96, 0x24, 0xaa, 0xf0, 0x92, 0xbb, 0x6c, 0x7c, 0x81, 0x94, 0x48, 0xef, 0x2d, 0x27, 0xbd, + 0xd7, 0x5e, 0x6c, 0x44, 0x78, 0x9c, 0x8c, 0x2c, 0x6d, 0xe0, 0x1c, 0x11, 0xf7, 0x72, 0x8c, 0xa7, 0x44, 0xe2, 0x19, + 0xac, 0x52, 0xc0, 0x8d, 0xc8, 0x70, 0x22, 0xfe, 0x19, 0xf8, 0xab, 0x24, 0x8d, 0x93, 0xfe, 0x32, 0x0e, 0xa2, 0x8c, + 0x26, 0x39, 0x82, 0xea, 0x1a, 0xe1, 0x23, 0xc0, 0x73, 0xb3, 0x89, 0x97, 0xc4, 0x0f, 0xb2, 0x87, 0xbe, 0xcb, 0x49, + 0x0a, 0x77, 0xc0, 0xa9, 0x03, 0xb7, 0xb1, 0x7e, 0x9f, 0x43, 0xf3, 0x25, 0x12, 0x7e, 0x49, 0x9d, 0x9c, 0x51, 0xb7, + 0xf9, 0x40, 0x79, 0xcb, 0x02, 0x04, 0x01, 0xf9, 0x41, 0x12, 0x7b, 0x06, 0x58, 0x1e, 0x96, 0xda, 0x9d, 0xd0, 0x99, + 0x85, 0x58, 0x1b, 0xc4, 0xeb, 0xe2, 0xcf, 0xe9, 0x99, 0x9a, 0xdb, 0x5c, 0x0c, 0x14, 0x8f, 0xb9, 0xcf, 0xc8, 0xfa, + 0x04, 0xd2, 0xe9, 0x59, 0xfb, 0xd4, 0x1c, 0xd3, 0x69, 0x9c, 0x50, 0x16, 0x4c, 0xda, 0x3b, 0x59, 0xae, 0xf7, 0xef, + 0x7e, 0xfb, 0xf4, 0x9b, 0xfb, 0x89, 0xe2, 0xcc, 0x10, 0x9d, 0x99, 0x3b, 0x7a, 0xab, 0xdf, 0x67, 0x40, 0x1a, 0x32, + 0xc8, 0xfb, 0x2c, 0x6e, 0x5f, 0x5f, 0xd7, 0x07, 0x8d, 0x31, 0xfb, 0x96, 0x31, 0xbf, 0xf3, 0x12, 0x1a, 0x92, 0x2c, + 0xb8, 0x13, 0x34, 0x63, 0xf7, 0x68, 0xb9, 0x16, 0x6b, 0x8c, 0x17, 0xde, 0x23, 0x16, 0xa9, 0x32, 0x14, 0xb1, 0x48, + 0xd5, 0x62, 0x5c, 0xa4, 0x41, 0x6d, 0x36, 0x22, 0x8c, 0x4d, 0xe5, 0xa6, 0xef, 0x2d, 0xd7, 0xea, 0x15, 0x5d, 0x34, + 0x93, 0x37, 0x75, 0x35, 0xfe, 0xe0, 0x22, 0x98, 0x4c, 0x42, 0x9a, 0x97, 0x16, 0xba, 0xbc, 0x96, 0x0a, 0x70, 0x24, + 0x1c, 0xc8, 0x38, 0x8d, 0xc3, 0x55, 0x46, 0x9b, 0xc1, 0xc5, 0x80, 0xd3, 0x71, 0x0b, 0xe0, 0xe0, 0xef, 0xf2, 0x58, + 0x7b, 0x40, 0x6e, 0xc3, 0x36, 0x71, 0x07, 0x10, 0x6e, 0xdc, 0xee, 0x96, 0x87, 0x0e, 0xaf, 0xe4, 0xa0, 0xad, 0x86, + 0x89, 0x58, 0x70, 0x2d, 0x31, 0xec, 0xad, 0x39, 0x1e, 0x2f, 0x93, 0x21, 0x97, 0x65, 0x51, 0x5e, 0x9e, 0xcc, 0x6f, + 0x73, 0xc6, 0x5e, 0x35, 0x9f, 0xb1, 0x57, 0xe2, 0x8c, 0x6d, 0xdf, 0x99, 0x4f, 0xa7, 0x1e, 0xfc, 0x37, 0x28, 0x26, + 0xd4, 0x77, 0xb5, 0xee, 0x72, 0xad, 0x79, 0xcb, 0xb5, 0x66, 0x77, 0x96, 0x6b, 0x0d, 0xbb, 0x46, 0xcb, 0x0a, 0xcb, + 0xe9, 0x98, 0x96, 0xab, 0x41, 0x21, 0xfc, 0xb9, 0xa5, 0x57, 0xde, 0x21, 0xbc, 0x83, 0x56, 0xbd, 0xfa, 0xbb, 0xce, + 0xf6, 0xa3, 0xce, 0xce, 0x92, 0x40, 0xda, 0xa6, 0x93, 0x91, 0xf1, 0x98, 0x4e, 0xfa, 0xd3, 0xd8, 0x5f, 0xa5, 0x7f, + 0xe7, 0xe3, 0xe7, 0x40, 0xdc, 0x8a, 0x08, 0x2a, 0xfd, 0x88, 0xa6, 0xa0, 0xef, 0xb8, 0xa3, 0xa2, 0x87, 0x8d, 0x5c, + 0xa7, 0x3e, 0x8b, 0x8d, 0xde, 0x71, 0x0e, 0x1b, 0x36, 0x79, 0x33, 0xa0, 0x7f, 0xb3, 0x55, 0x6a, 0x47, 0x31, 0xbf, + 0x02, 0x2c, 0x5b, 0xc1, 0xf1, 0x78, 0x68, 0xf0, 0xd5, 0x74, 0x4f, 0x9a, 0x87, 0x7b, 0x2d, 0xbe, 0x74, 0x23, 0x2e, + 0x15, 0x7e, 0x6f, 0x71, 0x87, 0xaf, 0xed, 0xbd, 0xb6, 0xed, 0x91, 0x5a, 0xaf, 0x5b, 0x2e, 0x84, 0xa2, 0xee, 0x9e, + 0x58, 0xfe, 0xe9, 0xab, 0x43, 0xf8, 0x8f, 0x51, 0xf5, 0x3f, 0x66, 0x4d, 0x84, 0xfa, 0x45, 0xd9, 0xff, 0x81, 0x91, + 0x4a, 0x48, 0x88, 0xef, 0x5f, 0x7f, 0x3a, 0x7d, 0x5c, 0x83, 0xbd, 0x6b, 0x33, 0xa3, 0xa4, 0x6a, 0xed, 0xaf, 0xe2, + 0x18, 0xf2, 0xf6, 0xd6, 0xab, 0x0b, 0xf0, 0x30, 0x17, 0x8f, 0x6c, 0x08, 0x8d, 0x04, 0x1f, 0xc1, 0x94, 0xf1, 0x3a, + 0xb6, 0x61, 0xac, 0xc4, 0xdb, 0x36, 0x56, 0xe2, 0xcd, 0x6e, 0x56, 0xe2, 0xdb, 0xbd, 0x58, 0x89, 0x37, 0x9f, 0x9d, + 0x95, 0x78, 0x5b, 0x67, 0x25, 0xae, 0x62, 0x61, 0x89, 0x6a, 0x5d, 0xac, 0xf8, 0xcf, 0x0f, 0x4c, 0xb7, 0x76, 0x19, + 0x0f, 0x7b, 0x2e, 0x8b, 0x77, 0x7e, 0xf5, 0x8b, 0x19, 0x0b, 0xdc, 0x88, 0xef, 0xd1, 0x30, 0xab, 0x60, 0x2d, 0x38, + 0x66, 0xc7, 0xef, 0x28, 0xc5, 0x61, 0x1c, 0xcd, 0xbe, 0x07, 0xdd, 0x2a, 0x88, 0x03, 0x13, 0xe5, 0x45, 0x90, 0x7e, + 0x1f, 0x2f, 0x57, 0xcb, 0x0b, 0xe8, 0xeb, 0x43, 0x90, 0x06, 0xe3, 0x90, 0xca, 0x30, 0x04, 0xcc, 0x90, 0x8c, 0xcb, + 0xc4, 0xc1, 0x76, 0x53, 0xfc, 0x24, 0x6b, 0xf1, 0x13, 0xad, 0x3b, 0xf9, 0x6f, 0x66, 0xa1, 0xa6, 0x37, 0x33, 0x22, + 0x10, 0xb0, 0xab, 0x32, 0xe8, 0xc7, 0x33, 0x23, 0x57, 0xb1, 0xd9, 0x30, 0x4b, 0x61, 0xb6, 0xd0, 0xda, 0x0f, 0xad, + 0x31, 0x35, 0x2b, 0xd3, 0x92, 0xf1, 0xf7, 0xea, 0x62, 0xf8, 0x45, 0xbc, 0x4a, 0xe9, 0x24, 0xbe, 0x8f, 0x74, 0x2b, + 0x95, 0xae, 0x35, 0xa0, 0xa8, 0x94, 0x6d, 0x30, 0x73, 0xac, 0xd4, 0xcc, 0xe8, 0x90, 0xb8, 0x78, 0xb5, 0xb4, 0x99, + 0xc6, 0xd8, 0xc6, 0x29, 0xea, 0x32, 0xc5, 0xd9, 0x13, 0xc3, 0x88, 0x87, 0x8f, 0x6b, 0x29, 0x2c, 0x2e, 0x62, 0x87, + 0x4b, 0x85, 0x53, 0x23, 0x15, 0xc2, 0x45, 0x11, 0x04, 0xa7, 0x61, 0xe1, 0xf8, 0x1b, 0xe6, 0x12, 0x5e, 0xbc, 0x85, + 0x10, 0x42, 0xf9, 0x8a, 0xaf, 0x07, 0x0f, 0x09, 0xc3, 0x1e, 0x5f, 0x2b, 0x60, 0x7c, 0x77, 0x47, 0x93, 0x90, 0x3c, + 0x18, 0x66, 0x1e, 0x47, 0xdf, 0x01, 0x00, 0xde, 0xc4, 0xf7, 0x91, 0x5a, 0x01, 0x33, 0x35, 0x35, 0xec, 0xa5, 0xc6, + 0xe0, 0x45, 0xe0, 0xae, 0xa5, 0x8c, 0x00, 0x72, 0x64, 0xcf, 0xe8, 0x5f, 0x2c, 0xf6, 0xef, 0x5f, 0xcd, 0xdc, 0xba, + 0x8c, 0xe5, 0x87, 0xfe, 0xbc, 0xdc, 0xe3, 0x33, 0xcf, 0x9f, 0x3f, 0x69, 0x9f, 0xb6, 0x01, 0xe9, 0xc2, 0x45, 0xe6, + 0x6b, 0xa3, 0xa1, 0xb5, 0xd9, 0x7a, 0x0a, 0x60, 0x14, 0x57, 0xf1, 0xca, 0x9f, 0xa3, 0xc9, 0xe8, 0xe7, 0x9b, 0x6f, + 0x06, 0x7d, 0x62, 0x8a, 0x62, 0x39, 0xf5, 0x4a, 0x51, 0x01, 0x05, 0xfc, 0xfe, 0x5b, 0x88, 0xbe, 0xfb, 0x6f, 0x04, + 0x43, 0x7d, 0xd7, 0x70, 0x2e, 0x3e, 0x78, 0xdc, 0xe6, 0x1d, 0x40, 0x26, 0x5d, 0x1e, 0xd7, 0x46, 0x28, 0xd7, 0x9a, + 0x91, 0x4c, 0x5e, 0x05, 0x9a, 0x1a, 0x43, 0xb2, 0x2d, 0x3c, 0xa6, 0xf8, 0x0a, 0x75, 0x18, 0x9b, 0xce, 0x4d, 0xf6, + 0x2d, 0xca, 0xb1, 0x55, 0x05, 0xc9, 0x70, 0xcb, 0x05, 0x8a, 0xe8, 0xab, 0xfa, 0x6e, 0x11, 0x44, 0x16, 0xa6, 0x80, + 0xa8, 0xbf, 0x21, 0x6b, 0x08, 0x82, 0x0e, 0xc8, 0xad, 0xfa, 0x0a, 0x0a, 0x2d, 0xaa, 0x78, 0x8b, 0x42, 0x9e, 0x37, + 0xbd, 0x11, 0x12, 0x42, 0x8b, 0x37, 0xfa, 0x9d, 0xa6, 0x69, 0x9a, 0x64, 0x23, 0x34, 0xc9, 0x47, 0x60, 0x39, 0xb2, + 0x03, 0xa0, 0x2d, 0xc9, 0x97, 0x6b, 0x56, 0x02, 0x9c, 0x01, 0x98, 0x78, 0xc8, 0x02, 0x1e, 0xe7, 0xb3, 0xe7, 0x8a, + 0x02, 0xc1, 0xd0, 0x43, 0x8c, 0x46, 0x92, 0x40, 0x38, 0xf0, 0xbe, 0x86, 0x0c, 0x3b, 0xbe, 0xe5, 0x92, 0x60, 0xcd, + 0x65, 0x8f, 0xa3, 0x01, 0x6d, 0x0e, 0x08, 0x99, 0x2a, 0x58, 0x10, 0xb4, 0x0e, 0x95, 0xf8, 0xee, 0x16, 0x6d, 0xc0, + 0x8d, 0xc8, 0x17, 0xad, 0xb3, 0x05, 0x8d, 0x56, 0x3a, 0x26, 0x84, 0xc3, 0xe0, 0xe2, 0x50, 0xe7, 0x0d, 0x23, 0xb6, + 0x00, 0xdb, 0x34, 0xb7, 0x9c, 0xb3, 0xbb, 0x30, 0xe2, 0x28, 0x95, 0x58, 0x3e, 0x57, 0x6c, 0x46, 0x1c, 0xb7, 0x55, + 0x6f, 0x08, 0xbe, 0xa4, 0x71, 0xd5, 0x7d, 0x91, 0xd9, 0x14, 0x43, 0x1f, 0x2c, 0x34, 0x0e, 0x17, 0x17, 0x09, 0xb0, + 0x1b, 0xa4, 0xba, 0x68, 0x52, 0x23, 0x43, 0x2a, 0x82, 0xa2, 0xc4, 0xac, 0x77, 0xc3, 0xc7, 0x09, 0x51, 0xc9, 0x5a, + 0xfb, 0xf1, 0x6b, 0xfd, 0xb4, 0x4c, 0xfa, 0x96, 0x3e, 0xb0, 0x8b, 0x84, 0x81, 0xea, 0x96, 0x3e, 0x80, 0x09, 0xdf, + 0x5b, 0x90, 0xa6, 0xe8, 0x5b, 0xd0, 0xb5, 0x05, 0x79, 0x3e, 0x7c, 0x88, 0x54, 0xb7, 0xe5, 0x00, 0xb9, 0xf9, 0x16, + 0x2c, 0x8e, 0x20, 0x86, 0x94, 0xee, 0xe2, 0x10, 0x73, 0x63, 0x79, 0xa3, 0x11, 0xc6, 0x76, 0xc3, 0xd1, 0x30, 0x5f, + 0x78, 0xae, 0x7b, 0x50, 0xab, 0x0f, 0x82, 0xec, 0xa6, 0xda, 0xa6, 0x95, 0x0d, 0x3d, 0xd7, 0x0e, 0x5e, 0x38, 0x9d, + 0x41, 0xed, 0x8e, 0x56, 0x02, 0xc9, 0x8e, 0x50, 0xfc, 0x75, 0xf6, 0x6c, 0x63, 0xa4, 0x2d, 0xe0, 0x2d, 0x8c, 0xcf, + 0x71, 0x6c, 0x39, 0x97, 0x7f, 0x8d, 0xea, 0x57, 0x3f, 0x0b, 0x63, 0xcb, 0x92, 0x1a, 0x8d, 0x20, 0x14, 0xba, 0x01, + 0xc7, 0xe8, 0xf7, 0xda, 0x4b, 0xcd, 0x60, 0xc7, 0xc7, 0x34, 0x47, 0x03, 0x81, 0x51, 0x84, 0x5b, 0x97, 0xda, 0x41, + 0xe5, 0x8b, 0x51, 0x15, 0xc3, 0xf1, 0xa0, 0xcb, 0xb4, 0xd0, 0xe8, 0x6d, 0xa5, 0x16, 0xb0, 0xff, 0x96, 0xeb, 0xd3, + 0x19, 0x43, 0xbc, 0x0f, 0xa8, 0x01, 0x89, 0x13, 0x76, 0x76, 0xb8, 0x5a, 0x96, 0xbb, 0x2b, 0x5f, 0x92, 0xfb, 0x77, + 0x86, 0x97, 0x0e, 0xea, 0xd0, 0x64, 0x7f, 0xcd, 0xd7, 0xdd, 0x23, 0xbb, 0xa4, 0xd1, 0xa4, 0xdc, 0x61, 0xe5, 0xfe, + 0xda, 0xbf, 0xbb, 0x12, 0x46, 0x81, 0x8c, 0x22, 0x71, 0x03, 0x46, 0xc9, 0xe3, 0x08, 0x37, 0x3f, 0x3b, 0x6e, 0xc1, + 0x5e, 0x54, 0x0c, 0x36, 0x60, 0xe1, 0x00, 0x65, 0x33, 0x45, 0x28, 0x0e, 0x61, 0xeb, 0xd1, 0x19, 0xde, 0x10, 0x84, + 0x68, 0xeb, 0x4e, 0xcc, 0x84, 0x69, 0x60, 0xd1, 0x26, 0xe0, 0x81, 0x68, 0xf7, 0x95, 0x5a, 0x07, 0xbb, 0xa5, 0xd6, + 0xd9, 0x2e, 0xa9, 0x35, 0x73, 0x4c, 0xba, 0x4f, 0xc8, 0x52, 0xf1, 0x6d, 0x13, 0xc4, 0xb9, 0xea, 0xe2, 0x56, 0x12, + 0x75, 0xa3, 0x1f, 0x93, 0x68, 0x55, 0xeb, 0x8d, 0x19, 0xfb, 0xa1, 0xf8, 0x5b, 0x61, 0x50, 0x84, 0x42, 0x5d, 0x95, + 0x8d, 0x5f, 0x15, 0xb2, 0x71, 0xc6, 0xd5, 0x14, 0x2e, 0x29, 0x82, 0xfa, 0x57, 0xdc, 0xbd, 0x24, 0x77, 0x50, 0xb8, + 0x7d, 0x15, 0x23, 0x55, 0x1c, 0x99, 0x0a, 0x46, 0x43, 0x71, 0x8f, 0x13, 0x5c, 0x46, 0xd9, 0x4b, 0xae, 0x5c, 0xb5, + 0xf0, 0x63, 0x2a, 0xca, 0x41, 0xea, 0x8e, 0x43, 0x96, 0xc5, 0xea, 0xb6, 0x29, 0x3b, 0xb2, 0xa8, 0xaf, 0x95, 0x4d, + 0x22, 0x3d, 0x4e, 0x18, 0x80, 0x85, 0x98, 0xbe, 0xa2, 0xd7, 0x96, 0x36, 0x10, 0x38, 0xc8, 0x06, 0x07, 0xb9, 0xdd, + 0xd2, 0x79, 0x96, 0x2c, 0xa5, 0xd0, 0xc2, 0xab, 0x32, 0x08, 0x84, 0xef, 0xcd, 0xa6, 0xe1, 0x96, 0xc7, 0x4b, 0x9e, + 0xdf, 0xef, 0x20, 0x5e, 0xd4, 0x5c, 0x55, 0x91, 0x8f, 0x27, 0xd3, 0x66, 0x56, 0xb6, 0x58, 0xb5, 0xde, 0x29, 0x13, + 0xe2, 0x6c, 0xb8, 0x8f, 0x49, 0x59, 0x46, 0xcf, 0x6b, 0xf4, 0xc5, 0x77, 0xf9, 0xd6, 0x49, 0x56, 0x11, 0x26, 0xb6, + 0xb0, 0xb3, 0x84, 0xf8, 0xb7, 0xca, 0x90, 0x85, 0x9c, 0x13, 0x64, 0xc0, 0x65, 0x4d, 0xc1, 0x80, 0x60, 0x1c, 0x58, + 0xda, 0x77, 0x3a, 0xa9, 0x22, 0x7d, 0xe9, 0x3f, 0x75, 0xbb, 0xe4, 0xd5, 0xf4, 0xb0, 0x22, 0x14, 0xed, 0xf4, 0xca, + 0x22, 0xf3, 0x96, 0x71, 0x64, 0xf3, 0xd5, 0x62, 0xbc, 0x51, 0x65, 0xab, 0x8a, 0xc8, 0xb5, 0x2e, 0x66, 0x55, 0x3f, + 0x3b, 0x9d, 0x4e, 0xcb, 0x82, 0x46, 0x57, 0x3b, 0x44, 0x61, 0xe1, 0x53, 0xd7, 0x75, 0xab, 0x63, 0xdf, 0x0e, 0x76, + 0x1b, 0xe5, 0xb6, 0x27, 0x8d, 0x23, 0x46, 0xd8, 0xee, 0x82, 0x5f, 0x1d, 0x1c, 0xb9, 0x53, 0x9c, 0xec, 0x92, 0x59, + 0xc4, 0x80, 0x19, 0x43, 0x04, 0x19, 0x5d, 0xa4, 0x7d, 0x9f, 0xa2, 0x0e, 0xc6, 0x51, 0x0e, 0x34, 0x1a, 0x0e, 0xd8, + 0x33, 0x30, 0x15, 0xf1, 0xc4, 0xae, 0x70, 0x35, 0x94, 0x87, 0xd7, 0x84, 0xf7, 0xe2, 0x23, 0x78, 0x50, 0x36, 0x75, + 0x99, 0x36, 0x4e, 0xab, 0xe7, 0xfe, 0xbe, 0x54, 0x4f, 0x83, 0x0b, 0x70, 0x23, 0x14, 0xda, 0x4c, 0x3e, 0x8b, 0xff, + 0x2f, 0xe5, 0xff, 0xaf, 0x96, 0xeb, 0xb2, 0xfd, 0xc8, 0x09, 0x48, 0xb4, 0x8b, 0xd3, 0xc2, 0x46, 0xdd, 0xb4, 0x07, + 0xa4, 0x95, 0xc1, 0x54, 0x55, 0xa0, 0x83, 0x92, 0xbe, 0x94, 0xfd, 0xa7, 0x41, 0xfc, 0x8e, 0x14, 0x33, 0x2c, 0x71, + 0x21, 0x42, 0x2c, 0x72, 0x57, 0xc2, 0x1c, 0xac, 0x97, 0x27, 0xa8, 0x3f, 0x28, 0xed, 0x09, 0xd0, 0xc6, 0xd7, 0xe6, + 0xb6, 0x97, 0xb8, 0xbf, 0xaa, 0xd7, 0x12, 0x1d, 0x03, 0xc8, 0x3c, 0x38, 0x84, 0x68, 0x48, 0xa0, 0x55, 0x36, 0x37, + 0x1b, 0xa5, 0x7c, 0xab, 0xea, 0xd9, 0xc4, 0xc0, 0xb0, 0xbb, 0xe6, 0x2a, 0xac, 0x6f, 0xa1, 0x2d, 0x80, 0xc9, 0xf2, + 0xed, 0x87, 0xcf, 0x36, 0x2c, 0xb1, 0xba, 0x1f, 0x3d, 0x5c, 0x72, 0xdc, 0xbf, 0x36, 0xde, 0x9d, 0x29, 0x3b, 0xff, + 0x28, 0x5f, 0xfc, 0xb6, 0x51, 0xa0, 0x77, 0x55, 0x92, 0xd0, 0x71, 0xa3, 0xef, 0x8e, 0xb9, 0x57, 0xed, 0x45, 0x10, + 0xed, 0x5f, 0x97, 0xac, 0xf7, 0xae, 0x0b, 0x17, 0xc6, 0xde, 0x95, 0xe1, 0xc6, 0x61, 0x96, 0x0b, 0xd9, 0xf0, 0x5b, + 0x45, 0xa0, 0xa8, 0xfa, 0xef, 0xea, 0xd8, 0x8a, 0x51, 0xf9, 0x57, 0x2b, 0x20, 0x3e, 0xf7, 0xca, 0xee, 0xa2, 0x89, + 0x04, 0x8d, 0xfa, 0xb1, 0x76, 0xa2, 0x1d, 0x77, 0xb5, 0x23, 0x57, 0x67, 0x5c, 0xd8, 0x50, 0xef, 0x75, 0x0a, 0xbf, + 0xbc, 0x43, 0x57, 0x3f, 0x3b, 0x9d, 0x89, 0x4b, 0x62, 0x1a, 0x84, 0x21, 0x43, 0x15, 0x60, 0xfe, 0x7b, 0x4b, 0xcb, + 0x6a, 0x16, 0x56, 0xc6, 0x8d, 0x40, 0x3a, 0xe2, 0x11, 0xce, 0x8e, 0x4f, 0x96, 0x7d, 0x3c, 0x1b, 0x6a, 0x21, 0x18, + 0x70, 0xb2, 0x52, 0xfc, 0x04, 0xdc, 0xc1, 0x63, 0xfd, 0xec, 0x14, 0xe2, 0x97, 0x6a, 0x93, 0xa1, 0xfe, 0x5d, 0xe7, + 0x58, 0xf3, 0x7a, 0x77, 0x76, 0xd7, 0x77, 0x6d, 0xcf, 0x39, 0xd4, 0x5c, 0xe7, 0xc8, 0xee, 0x38, 0xc7, 0x5a, 0xc7, + 0xe9, 0xc1, 0xbf, 0xbe, 0xe7, 0xbc, 0xd2, 0x5c, 0x78, 0xd2, 0x3c, 0xa7, 0x8b, 0xff, 0x76, 0x9c, 0xe3, 0xbb, 0x2e, + 0xbb, 0xe9, 0x89, 0xf4, 0x8e, 0xaa, 0x8c, 0x02, 0x7c, 0x39, 0xf4, 0x83, 0xb3, 0xd3, 0x55, 0x4a, 0xb5, 0xf5, 0x50, + 0x7f, 0xa5, 0x6b, 0xf3, 0x84, 0x4e, 0x87, 0xfa, 0x53, 0xa2, 0x94, 0x7a, 0x27, 0x8d, 0xc5, 0x9d, 0xe3, 0xc6, 0xe2, + 0xee, 0x51, 0x63, 0xf1, 0x61, 0xaf, 0x5c, 0x7c, 0x30, 0x63, 0xaf, 0x94, 0xf4, 0xa1, 0x0b, 0x92, 0x25, 0xc1, 0xda, + 0xf0, 0x34, 0x40, 0xd7, 0x36, 0xfc, 0x73, 0xdc, 0x31, 0x65, 0xab, 0x31, 0xb4, 0x92, 0xd0, 0x38, 0x3e, 0xd1, 0xbc, + 0xa3, 0x6f, 0x3a, 0x47, 0x3e, 0xd4, 0x83, 0x64, 0xb7, 0xf0, 0x77, 0xd7, 0x3d, 0xf1, 0x5d, 0x0d, 0x1a, 0x7a, 0xf0, + 0xdf, 0xbc, 0xd7, 0xf1, 0xd9, 0x83, 0x0b, 0xef, 0x3f, 0x78, 0xc7, 0xa9, 0x6b, 0x7b, 0xf0, 0xdf, 0xcf, 0x52, 0xe5, + 0x0e, 0x0a, 0x7f, 0xb5, 0xdf, 0x43, 0x57, 0xeb, 0x9e, 0xcc, 0x3b, 0xce, 0xab, 0xbb, 0x63, 0xe7, 0x64, 0xee, 0x1d, + 0x7f, 0x60, 0x4f, 0xa1, 0xdd, 0x71, 0x5e, 0xc1, 0xdf, 0x87, 0xae, 0x3b, 0xb7, 0x3d, 0xe7, 0xe4, 0xae, 0xeb, 0x74, + 0x43, 0xfb, 0xc8, 0x39, 0x81, 0xbf, 0x9f, 0x01, 0xbc, 0x00, 0x57, 0x9e, 0x9d, 0x58, 0x83, 0x8d, 0x51, 0xb1, 0xdf, + 0x50, 0x3f, 0xd2, 0x39, 0xd4, 0x7a, 0x87, 0xdf, 0x9c, 0xdc, 0xd9, 0x87, 0x73, 0xaf, 0x73, 0x67, 0xb7, 0xfe, 0xfc, + 0x00, 0x90, 0xdf, 0xbe, 0x70, 0x00, 0x46, 0x4c, 0x47, 0xf4, 0xbb, 0x91, 0x75, 0xd9, 0x26, 0x46, 0x7f, 0xbf, 0x5b, + 0x8c, 0xfe, 0xf5, 0x6a, 0x1f, 0x31, 0xfa, 0xfb, 0xcf, 0x2e, 0x46, 0xbf, 0xac, 0x5a, 0x71, 0xbf, 0xaf, 0xa6, 0x4d, + 0xf8, 0x71, 0x53, 0x25, 0x92, 0x03, 0x62, 0x5c, 0x5f, 0xad, 0x6e, 0x20, 0xe2, 0xd5, 0xfb, 0x78, 0xf8, 0xf5, 0xaa, + 0x64, 0xa2, 0x14, 0x03, 0x06, 0x78, 0x1f, 0x33, 0x0c, 0xf0, 0xa7, 0xd5, 0x10, 0xec, 0x22, 0xf8, 0xad, 0x19, 0x4c, + 0xec, 0x39, 0x09, 0xa7, 0xf2, 0xc6, 0x85, 0x92, 0x01, 0x16, 0x83, 0xdd, 0x3d, 0x5c, 0x26, 0xa0, 0xac, 0x59, 0x2d, + 0xa2, 0xb4, 0x7f, 0xe4, 0x02, 0x9a, 0xef, 0x4c, 0x93, 0xbc, 0xd2, 0xd8, 0x11, 0x31, 0xc2, 0x3e, 0x72, 0xcb, 0xfc, + 0xd6, 0xf7, 0x68, 0xb2, 0xd6, 0xdc, 0xbb, 0x57, 0xef, 0x57, 0x03, 0x5b, 0x10, 0x61, 0xd2, 0x07, 0xc4, 0x46, 0xd3, + 0xfb, 0xb2, 0xe1, 0x58, 0xc5, 0x54, 0xb0, 0x7d, 0xa4, 0x30, 0x92, 0x6a, 0x7b, 0xaf, 0x6c, 0x78, 0xb6, 0x6b, 0x9a, + 0x0d, 0x9f, 0x2f, 0x35, 0xdf, 0x62, 0xf5, 0x26, 0x3b, 0xae, 0x82, 0xaa, 0x92, 0xf4, 0xaf, 0x11, 0x20, 0x05, 0xed, + 0x59, 0x98, 0xc6, 0x15, 0x84, 0x8f, 0xab, 0xe1, 0x6d, 0xec, 0x2a, 0xef, 0x4a, 0x7d, 0xaa, 0xe6, 0x74, 0x2f, 0x36, + 0x48, 0x0f, 0x06, 0x3f, 0x03, 0x61, 0xc3, 0xef, 0xe3, 0x71, 0xac, 0xc2, 0x79, 0xa3, 0xf4, 0xcb, 0x48, 0xed, 0x7c, + 0xee, 0x6d, 0xea, 0xa4, 0x4d, 0xab, 0x21, 0xad, 0x47, 0x17, 0xe2, 0x8e, 0xc6, 0xcf, 0x33, 0xb3, 0xd5, 0x9c, 0x99, + 0x16, 0xa3, 0x65, 0xee, 0xb6, 0xce, 0x44, 0xbd, 0xa7, 0xb0, 0x89, 0x2d, 0xfe, 0xa0, 0xfa, 0x7f, 0x6f, 0xa6, 0x90, + 0xa0, 0xbd, 0x8f, 0x44, 0x84, 0x42, 0x41, 0x75, 0xd0, 0xc6, 0x76, 0xb0, 0xc5, 0xfc, 0x43, 0xed, 0x98, 0x77, 0x82, + 0xb6, 0xba, 0xdb, 0x2c, 0x46, 0xa4, 0x6b, 0xc3, 0xa6, 0xa4, 0x40, 0xf5, 0x7a, 0xc7, 0x96, 0x77, 0x64, 0x39, 0xc7, + 0x3d, 0x33, 0x17, 0x07, 0x4e, 0xed, 0xb2, 0x04, 0x10, 0x30, 0xd9, 0x95, 0xc3, 0x0c, 0xa2, 0x20, 0x0b, 0x48, 0x98, + 0x03, 0xaa, 0x2f, 0xd3, 0xbc, 0x7f, 0x5b, 0xa5, 0x19, 0xcc, 0x51, 0x90, 0x64, 0x68, 0xae, 0x6c, 0x8f, 0x69, 0x76, + 0x4f, 0x69, 0xd4, 0xa2, 0xca, 0xad, 0x5a, 0x3f, 0xff, 0x76, 0xb6, 0xa0, 0x39, 0xb3, 0xb3, 0x18, 0x67, 0x11, 0xdf, + 0x1f, 0xc2, 0x54, 0x37, 0x1f, 0x59, 0x3f, 0xb7, 0x21, 0xdc, 0xbf, 0xed, 0x46, 0xb8, 0x19, 0xdd, 0x07, 0xe1, 0xfe, + 0xed, 0xb3, 0x23, 0xdc, 0x9f, 0x55, 0x84, 0x5b, 0xf2, 0x54, 0x29, 0x64, 0xa2, 0x3f, 0xe0, 0xb3, 0x01, 0xf1, 0xc6, + 0x5f, 0xea, 0x07, 0x8c, 0xbc, 0xd4, 0x95, 0x3c, 0xd0, 0x1f, 0x4a, 0x89, 0xad, 0x90, 0x65, 0xc7, 0xd0, 0xc3, 0x2c, + 0x89, 0x0e, 0xe4, 0x48, 0x76, 0x85, 0xfb, 0x21, 0xf4, 0x79, 0x11, 0x65, 0xa1, 0xf3, 0x9e, 0xb3, 0x25, 0xa0, 0x82, + 0xf8, 0x3a, 0x4e, 0x16, 0x04, 0x83, 0x22, 0xea, 0x98, 0x10, 0x13, 0x1e, 0x5c, 0x70, 0x18, 0xf3, 0xe3, 0x68, 0x22, + 0xe5, 0xe8, 0x74, 0x78, 0xcd, 0xe8, 0x41, 0xfd, 0x81, 0x92, 0x44, 0xb7, 0xd8, 0x6b, 0x58, 0xdc, 0x17, 0x5d, 0xf7, + 0x45, 0xe7, 0xf0, 0xc5, 0x91, 0x0b, 0xff, 0xf3, 0x68, 0x37, 0xb7, 0x78, 0xc5, 0x45, 0x1c, 0x41, 0x4e, 0x1e, 0x51, + 0xb3, 0xad, 0xda, 0x3d, 0xa5, 0xb7, 0x45, 0xad, 0xe3, 0xe6, 0x4a, 0x13, 0xf2, 0x50, 0xd4, 0x69, 0xac, 0x31, 0x8f, + 0x57, 0xca, 0xb0, 0x1a, 0x46, 0x13, 0x44, 0x2b, 0x90, 0x0c, 0x29, 0x35, 0xd4, 0xd7, 0x7c, 0xba, 0xc5, 0xbc, 0x68, + 0x37, 0xbf, 0x29, 0x12, 0x7f, 0x89, 0x04, 0x44, 0x3b, 0x21, 0xc8, 0x85, 0xea, 0x2e, 0xa6, 0x0d, 0xc0, 0x68, 0x6f, + 0x1a, 0xa4, 0xdd, 0x14, 0x0b, 0x44, 0xd8, 0x02, 0x65, 0xc9, 0x2a, 0xf2, 0x0d, 0xfc, 0x49, 0xc6, 0xa9, 0x11, 0x1c, + 0x40, 0x4c, 0x5e, 0xfc, 0xb0, 0x89, 0xab, 0x46, 0xce, 0xdc, 0x22, 0x4b, 0x4a, 0x24, 0x56, 0x85, 0xbc, 0xc8, 0xac, + 0x84, 0xe5, 0x56, 0xc6, 0xa5, 0xb5, 0x87, 0xe4, 0x85, 0x6c, 0xf8, 0x22, 0xb3, 0x20, 0xbf, 0x31, 0x2c, 0xf7, 0xf3, + 0xe7, 0xac, 0x16, 0x64, 0x1c, 0x65, 0xd3, 0x3a, 0xf7, 0x65, 0x96, 0x42, 0x5d, 0x23, 0xb3, 0x58, 0xc7, 0x2c, 0x85, + 0x7d, 0xdf, 0x8a, 0x5f, 0xbe, 0x3c, 0x1b, 0x7a, 0x26, 0xcf, 0x97, 0x5b, 0x4a, 0xee, 0x76, 0xb9, 0x9f, 0x6a, 0xdc, + 0x6c, 0x74, 0xda, 0x5a, 0x06, 0xd1, 0x4c, 0x68, 0xa6, 0x25, 0xf6, 0x82, 0x64, 0x2b, 0x4c, 0x05, 0x46, 0x84, 0x8a, + 0x5a, 0xd4, 0xb9, 0xa3, 0x09, 0xe4, 0xfa, 0x1d, 0xea, 0x5d, 0xc7, 0x75, 0x5c, 0x5d, 0x36, 0x9c, 0x06, 0xb3, 0xe1, + 0x26, 0xce, 0x08, 0xa4, 0xad, 0x0a, 0xe3, 0x19, 0x78, 0x7e, 0x64, 0x41, 0x16, 0x42, 0x0e, 0x24, 0x70, 0x01, 0x59, + 0x30, 0xae, 0x31, 0xe7, 0xf6, 0xb8, 0x24, 0xb9, 0xc5, 0x3c, 0x98, 0xc2, 0xe9, 0x0b, 0x23, 0xcb, 0x7c, 0x07, 0x97, + 0xa1, 0xa1, 0x1b, 0x90, 0x85, 0x95, 0x26, 0xa9, 0xad, 0xda, 0xb7, 0xf7, 0x35, 0x68, 0x63, 0xea, 0x7c, 0x12, 0xd3, + 0x84, 0x2c, 0x20, 0x5d, 0xc0, 0x26, 0xb7, 0x38, 0xa6, 0xd5, 0x39, 0xaa, 0xd5, 0xbc, 0x57, 0x47, 0x96, 0xd6, 0xf1, + 0x2c, 0xcd, 0x05, 0x74, 0xab, 0xe7, 0xd6, 0x26, 0xbf, 0x19, 0xec, 0x52, 0xd1, 0x31, 0xfc, 0xf2, 0x94, 0xcd, 0x83, + 0x29, 0xe7, 0xb8, 0xf0, 0x33, 0x63, 0x41, 0x3d, 0x0d, 0x25, 0x90, 0x7f, 0xc0, 0xc4, 0xf4, 0x57, 0x74, 0x9d, 0x99, + 0x98, 0x23, 0x88, 0x57, 0x09, 0xcc, 0x0d, 0xba, 0xa6, 0x05, 0x91, 0x16, 0x7c, 0xfa, 0x64, 0x04, 0x60, 0x7e, 0x3f, + 0x54, 0xe0, 0x03, 0xcf, 0x66, 0x09, 0x60, 0x41, 0xa1, 0x58, 0x42, 0x60, 0x81, 0x6f, 0x0c, 0xfc, 0x5b, 0x14, 0x8b, + 0x1f, 0x5c, 0xb1, 0xe7, 0x84, 0x24, 0x9a, 0x01, 0x4a, 0x23, 0xd1, 0xac, 0x66, 0x40, 0xc0, 0xbc, 0xeb, 0x2a, 0xa5, + 0x45, 0x57, 0x85, 0x72, 0x3f, 0xfd, 0xea, 0xe1, 0x8a, 0xe5, 0x40, 0x33, 0x74, 0xb8, 0xe5, 0xd0, 0x15, 0xac, 0xd0, + 0x3d, 0xbc, 0x1c, 0x7e, 0x71, 0xba, 0xa0, 0x19, 0x61, 0x82, 0x4b, 0x60, 0xf1, 0x80, 0x1c, 0xd0, 0x7c, 0x91, 0xbf, + 0x98, 0x31, 0x78, 0x13, 0x7a, 0x17, 0xf8, 0x9c, 0x4f, 0xb3, 0x34, 0x7e, 0x4f, 0xd9, 0x68, 0xa3, 0x34, 0xf4, 0x2c, + 0x66, 0x22, 0xeb, 0x13, 0x0c, 0xfd, 0x39, 0x8c, 0x62, 0xfd, 0xec, 0x0b, 0xe9, 0x4d, 0xd4, 0xb6, 0x08, 0x90, 0x88, + 0xf4, 0x3a, 0xa1, 0xe1, 0xdf, 0x87, 0x5f, 0xc0, 0xc5, 0xfd, 0xc5, 0x8d, 0x6e, 0x0e, 0x32, 0x07, 0xf9, 0x98, 0x2f, + 0x1a, 0x12, 0x72, 0x22, 0x8f, 0xca, 0x99, 0xcd, 0xae, 0xc2, 0x6c, 0xc2, 0xef, 0xdd, 0xac, 0x2b, 0xb8, 0xa9, 0x3e, + 0x84, 0xf4, 0x0c, 0xb8, 0x8b, 0x4d, 0x89, 0xe7, 0xf4, 0x06, 0xc8, 0xa0, 0x8e, 0x43, 0xe2, 0xdf, 0x0a, 0x0e, 0x55, + 0x7d, 0xd8, 0x87, 0x17, 0x95, 0x94, 0x5d, 0xe3, 0x5e, 0xc6, 0xad, 0xbc, 0xc1, 0x2f, 0xe3, 0xa7, 0xee, 0xe7, 0x41, + 0x26, 0x99, 0x61, 0x7c, 0xc8, 0xd1, 0x37, 0x16, 0xc6, 0x57, 0xb0, 0x3f, 0xc0, 0xa0, 0x7a, 0x27, 0xdf, 0xf4, 0xee, + 0x3c, 0x77, 0xde, 0xf1, 0x1c, 0x60, 0x73, 0xe6, 0x5d, 0xe7, 0x38, 0xb4, 0xbb, 0xce, 0x31, 0xfc, 0x7d, 0x00, 0xd6, + 0xcb, 0xee, 0x38, 0x87, 0x1f, 0xbc, 0x4e, 0x68, 0x9f, 0x38, 0xc7, 0xf0, 0x77, 0xc9, 0x5a, 0xfd, 0x88, 0x4c, 0x0f, + 0x30, 0x3c, 0x5f, 0x94, 0xb0, 0x80, 0xf2, 0x5b, 0x6a, 0x11, 0xac, 0xd2, 0xf5, 0xd6, 0xa0, 0x89, 0x00, 0x94, 0xa1, + 0x5b, 0x22, 0x4a, 0x60, 0x3a, 0x30, 0xd2, 0x21, 0xcf, 0x6d, 0x21, 0x0c, 0x32, 0x84, 0xd7, 0xa4, 0xc8, 0xbc, 0xd0, + 0x78, 0x8c, 0x78, 0x9b, 0xe6, 0x30, 0xfb, 0x22, 0x69, 0x1a, 0x53, 0x5d, 0xfc, 0x79, 0x89, 0xf1, 0x71, 0xa8, 0x59, + 0xc3, 0x4a, 0x45, 0xe2, 0xce, 0x7c, 0xf7, 0xc0, 0xd1, 0x6f, 0x94, 0xca, 0xc4, 0x51, 0x9f, 0xb5, 0x6f, 0xae, 0xce, + 0x90, 0xbd, 0xff, 0xd2, 0x7e, 0x30, 0x5f, 0x32, 0xeb, 0x47, 0x44, 0xd8, 0x9d, 0x04, 0x89, 0x1c, 0x9e, 0x82, 0xa2, + 0xbd, 0xe6, 0xfc, 0x04, 0x26, 0x64, 0xd8, 0xb9, 0x00, 0x2a, 0xf9, 0x8e, 0x84, 0x8a, 0xe9, 0x85, 0xd2, 0xf2, 0x89, + 0xc4, 0xfc, 0xcf, 0x9f, 0x17, 0x83, 0xb3, 0x2b, 0xe3, 0x3e, 0xf5, 0x7a, 0x70, 0xed, 0xf6, 0x68, 0x77, 0xab, 0x15, + 0xd0, 0xee, 0x10, 0xcd, 0x45, 0x3c, 0x49, 0xa1, 0xe9, 0x17, 0x3a, 0xc6, 0x56, 0x53, 0xa4, 0x9a, 0x86, 0x11, 0xc2, + 0x5b, 0x57, 0x58, 0x1d, 0xdd, 0x1c, 0xa4, 0xfb, 0x84, 0xa5, 0xe6, 0xbc, 0x98, 0x0e, 0xa0, 0xd9, 0x32, 0x8f, 0x1d, + 0x2e, 0x8d, 0xff, 0xee, 0x49, 0xa0, 0x7b, 0x11, 0x68, 0xf8, 0x2a, 0xa7, 0xb5, 0xe4, 0x6e, 0x22, 0xef, 0x55, 0x76, + 0xa1, 0xd2, 0xf4, 0x5c, 0x87, 0x22, 0x48, 0xbd, 0x86, 0xd9, 0x16, 0xa5, 0x79, 0x93, 0xbc, 0x2d, 0x8a, 0x02, 0x2b, + 0x80, 0xc8, 0xef, 0x86, 0x70, 0x75, 0x32, 0x9f, 0x3f, 0x6f, 0xbd, 0x84, 0x98, 0x3a, 0x59, 0x4d, 0x3a, 0xab, 0xab, + 0xf8, 0x4d, 0x57, 0x51, 0x8c, 0xec, 0x17, 0xb1, 0x86, 0xb0, 0xca, 0x62, 0x7b, 0x0f, 0x7f, 0x8e, 0x29, 0xc9, 0x1c, + 0xae, 0x07, 0x31, 0x94, 0xcb, 0xdd, 0xf2, 0x68, 0x17, 0xec, 0xb1, 0x78, 0x18, 0x2d, 0x1e, 0x33, 0xee, 0xd9, 0xe6, + 0xc3, 0x8a, 0xfb, 0x21, 0x43, 0x1f, 0x9f, 0xdc, 0x22, 0x06, 0xca, 0xbb, 0x8c, 0xb0, 0x40, 0x19, 0xea, 0x95, 0x1b, + 0x67, 0x44, 0xa4, 0x38, 0x02, 0xba, 0x7c, 0xd0, 0xa8, 0x30, 0x54, 0x7c, 0x95, 0xcf, 0xde, 0x5d, 0x7d, 0xa9, 0xf1, + 0xfd, 0xcf, 0xf4, 0x5b, 0xc8, 0xc8, 0xb0, 0x5c, 0x17, 0x43, 0x96, 0xeb, 0x42, 0xe3, 0x59, 0x67, 0x20, 0x63, 0x44, + 0x7e, 0xc0, 0x20, 0xa8, 0x6b, 0x34, 0xf2, 0x99, 0xd6, 0x6f, 0xb1, 0x0a, 0xb3, 0x60, 0x49, 0x92, 0xec, 0x00, 0x9a, + 0xda, 0x80, 0xe4, 0xf4, 0x36, 0x0f, 0x66, 0xa6, 0x38, 0x14, 0x42, 0xb5, 0x2c, 0x12, 0x9a, 0xc3, 0x69, 0x10, 0x4a, + 0xc5, 0xa1, 0xf8, 0x00, 0xf1, 0x7d, 0xba, 0xcc, 0x86, 0x3a, 0x59, 0x42, 0xce, 0x13, 0x0c, 0x63, 0x7a, 0x10, 0xfb, + 0x19, 0xcd, 0xec, 0x34, 0x4b, 0x28, 0x59, 0xe8, 0x32, 0x64, 0x67, 0xbd, 0xbf, 0x74, 0x35, 0x5e, 0x04, 0x99, 0x8c, + 0x79, 0xc7, 0x26, 0x08, 0x2a, 0x3c, 0x18, 0x22, 0x84, 0x33, 0x60, 0x20, 0xbc, 0x8c, 0x67, 0x95, 0x1d, 0x55, 0x50, + 0x2e, 0xe7, 0x4a, 0x5c, 0x09, 0x10, 0x8e, 0xfa, 0x71, 0xf8, 0x91, 0x7b, 0x5d, 0xcb, 0xd0, 0x7c, 0xfa, 0xd9, 0x29, + 0x67, 0x6f, 0x35, 0x0c, 0x14, 0xa0, 0xf7, 0x5c, 0x08, 0x36, 0xdb, 0xe6, 0x8f, 0x7d, 0xc0, 0x2b, 0xab, 0x91, 0x15, + 0xfa, 0x97, 0x7c, 0x2c, 0x57, 0x40, 0x08, 0x95, 0x54, 0xbc, 0x73, 0xef, 0x4c, 0x3a, 0x00, 0xe1, 0xa8, 0x90, 0x56, + 0xfa, 0xf4, 0xe9, 0xf5, 0xe8, 0x9f, 0xff, 0x80, 0x14, 0x04, 0x73, 0x4f, 0x78, 0x41, 0x5f, 0xab, 0xb5, 0x38, 0xf5, + 0x69, 0x8d, 0x50, 0xbd, 0x4f, 0x27, 0x22, 0x2a, 0x8c, 0xd8, 0x5a, 0xf9, 0xe8, 0x46, 0x44, 0x6c, 0x82, 0x34, 0x23, + 0xa6, 0xf0, 0xd5, 0x1e, 0xc1, 0xf2, 0x8e, 0x44, 0x88, 0x00, 0xed, 0xa7, 0xf5, 0x57, 0xc7, 0x9a, 0x5e, 0xe4, 0x0e, + 0x68, 0xd0, 0x41, 0xb3, 0x3d, 0x74, 0x76, 0x4a, 0xb8, 0xf0, 0x15, 0xc8, 0x8f, 0xb4, 0x7f, 0x00, 0xd3, 0x9c, 0xc7, + 0x0b, 0xea, 0x04, 0xf1, 0xc1, 0x3d, 0x1d, 0xdb, 0x64, 0x19, 0x30, 0xf9, 0x32, 0xca, 0xdd, 0x34, 0x46, 0xf9, 0x49, + 0x05, 0x2d, 0xa3, 0xaf, 0xf3, 0x02, 0x94, 0x71, 0x01, 0x28, 0xf8, 0x49, 0xce, 0xca, 0x71, 0xf8, 0x1c, 0x91, 0x17, + 0xa2, 0x8c, 0xe5, 0xcf, 0x59, 0x38, 0x3d, 0x11, 0x39, 0xaf, 0x78, 0xb0, 0xe3, 0xe9, 0x54, 0x8d, 0x9d, 0xe7, 0x94, + 0xbf, 0x2f, 0xa1, 0x52, 0xec, 0xd9, 0x78, 0xc9, 0xbe, 0x54, 0xff, 0x84, 0xfc, 0x09, 0xb1, 0x40, 0x78, 0x98, 0x45, + 0x38, 0xcf, 0xb5, 0x18, 0x7c, 0x12, 0x24, 0x4f, 0x59, 0x25, 0x8e, 0x28, 0xaa, 0xd1, 0xd9, 0x5b, 0x48, 0x93, 0x27, + 0xc3, 0x21, 0xc3, 0x63, 0x55, 0x74, 0x06, 0x50, 0x6a, 0xc8, 0x91, 0x01, 0x93, 0xcd, 0xa0, 0xa1, 0xcd, 0x3c, 0xb8, + 0xb0, 0x51, 0x75, 0x3a, 0xf5, 0x31, 0x1e, 0x10, 0xb1, 0xbf, 0x4a, 0x3b, 0x10, 0x76, 0x16, 0x5f, 0x58, 0x40, 0xe0, + 0xa2, 0x9f, 0x0a, 0x1e, 0xd7, 0xfe, 0xc0, 0x50, 0xb6, 0x1d, 0x92, 0x87, 0x58, 0xd1, 0xac, 0x73, 0x27, 0xfb, 0x4b, + 0x2c, 0xbd, 0x12, 0xce, 0x6d, 0xb5, 0x93, 0x24, 0xb3, 0x00, 0xd4, 0x4f, 0x93, 0x1a, 0xb6, 0x7f, 0xd7, 0x61, 0x52, + 0xeb, 0x96, 0x27, 0x83, 0xd8, 0x31, 0x2f, 0x0e, 0x5a, 0xe9, 0x25, 0x9e, 0xfb, 0xfc, 0xf4, 0x00, 0xe6, 0x07, 0x81, + 0x01, 0x4a, 0x94, 0x51, 0x60, 0x42, 0xf4, 0x01, 0x92, 0x32, 0xeb, 0x80, 0x8b, 0x89, 0x20, 0xea, 0x90, 0x73, 0x94, + 0x41, 0x3e, 0x4b, 0x55, 0xea, 0xc4, 0x8a, 0xdb, 0x4c, 0xe5, 0xed, 0xce, 0xc0, 0xf1, 0xa7, 0x15, 0xa6, 0xdf, 0xc8, + 0x07, 0x19, 0x15, 0x7e, 0xb7, 0x97, 0x59, 0x8b, 0x6b, 0x6e, 0x5b, 0x15, 0x46, 0xb0, 0x6e, 0xa9, 0x50, 0xec, 0xe3, + 0x6d, 0xb5, 0x0a, 0xd2, 0x48, 0x56, 0x5b, 0x12, 0x43, 0x7f, 0x8a, 0x3b, 0xbe, 0x56, 0x1b, 0x4b, 0xa1, 0xde, 0x65, + 0x36, 0x84, 0xaa, 0x42, 0xd8, 0x4e, 0x96, 0x4b, 0x56, 0xd9, 0x1c, 0x9c, 0x1e, 0x30, 0xbe, 0xf3, 0x8c, 0xed, 0xb0, + 0xb3, 0x53, 0xb0, 0x2e, 0x64, 0x8b, 0x4e, 0x96, 0x4b, 0xbe, 0xa4, 0xec, 0x17, 0x7b, 0x73, 0x30, 0xcf, 0x16, 0xe1, + 0xd9, 0xff, 0x01, 0xff, 0xd7, 0x9f, 0x05, 0x1b, 0x5f, 0x03, 0x00}; + +} // namespace web_server +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 01057fead62d..d72307991f35 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -4,6 +4,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -26,7 +27,11 @@ #endif #ifdef USE_WEBSERVER_LOCAL -#include "server_index.h" +#if USE_WEBSERVER_VERSION == 2 +#include "server_index_v2.h" +#elif USE_WEBSERVER_VERSION == 3 +#include "server_index_v3.h" +#endif #endif namespace esphome { @@ -34,6 +39,13 @@ namespace web_server { static const char *const TAG = "web_server"; +#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 + #if USE_WEBSERVER_VERSION == 1 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { @@ -264,6 +276,32 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { } #endif +#ifdef USE_TEXT + for (auto *obj : App.get_texts()) { + if (this->include_internal_ || !obj->is_internal()) { + write_row(stream, obj, "text", "", [](AsyncResponseStream &stream, EntityBase *obj) { + text::Text *text = (text::Text *) obj; + auto mode = (int) text->traits.get_mode(); + stream.print(R"(traits.get_min_length()); + 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(R"(" value=")"); + stream.print(text->state.c_str()); + stream.print(R"("/>)"); + }); + } + } +#endif + #ifdef USE_SELECT for (auto *obj : App.get_selects()) { if (this->include_internal_ || !obj->is_internal()) { @@ -324,10 +362,22 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("")); request->send(stream); } -#elif USE_WEBSERVER_VERSION == 2 +#elif USE_WEBSERVER_VERSION >= 2 void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE); + // No gzip header here because the HTML file is so small + request->send(response); +} +#endif + +#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()); + std::string mac = get_mac_address_pretty(); + response->addHeader(HEADER_PNA_ID, mac.c_str()); request->send(response); } #endif @@ -336,6 +386,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); + response->addHeader("Content-Encoding", "gzip"); request->send(response); } #endif @@ -344,28 +395,33 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); + response->addHeader("Content-Encoding", "gzip"); request->send(response); } #endif #define set_json_id(root, obj, sensor, start_config) \ (root)["id"] = sensor; \ - if (((start_config) == DETAIL_ALL)) \ - (root)["name"] = (obj)->get_name(); + if (((start_config) == DETAIL_ALL)) { \ + (root)["name"] = (obj)->get_name(); \ + (root)["icon"] = (obj)->get_icon(); \ + (root)["entity_category"] = (obj)->get_entity_category(); \ + if ((obj)->is_disabled_by_default()) \ + (root)["is_disabled_by_default"] = (obj)->is_disabled_by_default(); \ + } #define set_json_value(root, obj, sensor, value, start_config) \ - set_json_id((root), (obj), sensor, start_config)(root)["value"] = value; - -#define set_json_state_value(root, obj, sensor, state, value, start_config) \ - set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; + set_json_id((root), (obj), sensor, start_config); \ + (root)["value"] = value; #define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \ - set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \ - if (((start_config) == DETAIL_ALL)) \ - (root)["icon"] = (obj)->get_icon(); + set_json_value(root, obj, sensor, value, start_config); \ + (root)["state"] = state; #ifdef USE_SENSOR void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { + if (this->events_.count() == 0) + return; this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -389,12 +445,18 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail state += " " + obj->get_unit_of_measurement(); } set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); + if (start_config == DETAIL_ALL) { + if (!obj->get_unit_of_measurement().empty()) + root["uom"] = obj->get_unit_of_measurement(); + } }); } #endif #ifdef USE_TEXT_SENSOR void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { + if (this->events_.count() == 0) + return; this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -417,6 +479,8 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: #ifdef USE_SWITCH void WebServer::on_switch_update(switch_::Switch *obj, bool state) { + if (this->events_.count() == 0) + return; this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { @@ -432,7 +496,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->switch_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -463,7 +527,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM for (button::Button *obj : App.get_buttons()) { if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_POST && match.method == "press") { + if (match.method == "press") { this->schedule_([obj]() { obj->press(); }); request->send(200); return; @@ -478,11 +542,14 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { + if (this->events_.count() == 0) + return; this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { - set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); + set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, + start_config); }); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -498,10 +565,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } +void WebServer::on_fan_update(fan::Fan *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); +} std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { return json::build_json([obj, start_config](JsonObject root) { - set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config); + set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, + start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; @@ -516,7 +588,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->fan_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -567,6 +639,8 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #ifdef USE_LIGHT void WebServer::on_light_update(light::LightState *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -574,7 +648,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->light_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { @@ -673,6 +747,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->events_.count() == 0) + return; this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -680,7 +756,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->cover_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); continue; @@ -693,6 +769,8 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa call.set_command_close(); } else if (match.method == "stop") { call.set_command_stop(); + } else if (match.method == "toggle") { + call.set_command_toggle(); } else if (match.method != "set") { request->send(404); return; @@ -726,10 +804,12 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa } std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { return json::build_json([obj, start_config](JsonObject root) { - set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", - obj->position, start_config); + set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); + if (obj->get_traits().get_supports_position()) + root["position"] = obj->position; if (obj->get_traits().get_supports_tilt()) root["tilt"] = obj->tilt; }); @@ -738,6 +818,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { #ifdef USE_NUMBER void WebServer::on_number_update(number::Number *obj, float state) { + if (this->events_.count() == 0) + return; this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -745,7 +827,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->number_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -773,16 +855,21 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail return json::build_json([obj, value, start_config](JsonObject root) { set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); if (start_config == DETAIL_ALL) { - root["min_value"] = obj->traits.get_min_value(); - root["max_value"] = obj->traits.get_max_value(); - root["step"] = obj->traits.get_step(); + root["min_value"] = + value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); + root["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(); + if (!obj->traits.get_unit_of_measurement().empty()) + root["uom"] = obj->traits.get_unit_of_measurement(); } if (std::isnan(value)) { root["value"] = "\"NaN\""; root["state"] = "NA"; } else { - root["value"] = value; + root["value"] = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); std::string state = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); if (!obj->traits.get_unit_of_measurement().empty()) state += " " + obj->traits.get_unit_of_measurement(); @@ -792,8 +879,209 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail } #endif +#ifdef USE_DATETIME_DATE +void WebServer::on_date_update(datetime::DateEntity *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_dates()) { + if (obj->get_object_id() != match.id) + continue; + if (request->method() == HTTP_GET) { + std::string data = this->date_json(obj, DETAIL_STATE); + request->send(200, "application/json", data.c_str()); + return; + } + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (!request->hasParam("value")) { + request->send(409); + return; + } + + if (request->hasParam("value")) { + std::string value = request->getParam("value")->value().c_str(); + call.set_date(value); + } + + this->schedule_([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); + std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); + root["value"] = value; + root["state"] = value; + }); +} +#endif // USE_DATETIME_DATE + +#ifdef USE_DATETIME_TIME +void WebServer::on_time_update(datetime::TimeEntity *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_times()) { + if (obj->get_object_id() != match.id) + continue; + if (request->method() == HTTP_GET && match.method.empty()) { + std::string data = this->time_json(obj, DETAIL_STATE); + request->send(200, "application/json", data.c_str()); + return; + } + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (!request->hasParam("value")) { + request->send(409); + return; + } + + if (request->hasParam("value")) { + std::string value = request->getParam("value")->value().c_str(); + call.set_time(value); + } + + this->schedule_([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} +std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "time-" + obj->get_object_id(), start_config); + std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); + root["value"] = value; + root["state"] = value; + }); +} +#endif // USE_DATETIME_TIME + +#ifdef USE_DATETIME_DATETIME +void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->datetime_json(obj, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_datetimes()) { + if (obj->get_object_id() != match.id) + continue; + if (request->method() == HTTP_GET && match.method.empty()) { + std::string data = this->datetime_json(obj, DETAIL_STATE); + request->send(200, "application/json", data.c_str()); + return; + } + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (!request->hasParam("value")) { + request->send(409); + return; + } + + if (request->hasParam("value")) { + std::string value = request->getParam("value")->value().c_str(); + call.set_datetime(value); + } + + this->schedule_([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} +std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config); + std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, + obj->minute, obj->second); + root["value"] = value; + root["state"] = value; + }); +} +#endif // USE_DATETIME_DATETIME + +#ifdef USE_TEXT +void WebServer::on_text_update(text::Text *obj, const std::string &state) { + if (this->events_.count() == 0) + return; + this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_texts()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET && match.method.empty()) { + std::string data = this->text_json(obj, obj->state, DETAIL_STATE); + request->send(200, "text/json", data.c_str()); + return; + } + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + call.set_value(value.c_str()); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_id(root, obj, "text-" + obj->get_object_id(), start_config); + if (start_config == DETAIL_ALL) { + root["mode"] = (int) obj->traits.get_mode(); + } + root["min_length"] = obj->traits.get_min_length(); + root["max_length"] = obj->traits.get_max_length(); + root["pattern"] = obj->traits.get_pattern(); + if (obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD) { + root["state"] = "********"; + } else { + root["state"] = value; + } + root["value"] = value; + }); +} +#endif + #ifdef USE_SELECT void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { + if (this->events_.count() == 0) + return; this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -801,8 +1089,13 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { - std::string data = this->select_json(obj, obj->state, DETAIL_STATE); + if (request->method() == HTTP_GET && match.method.empty()) { + auto detail = DETAIL_STATE; + auto *param = request->getParam("detail"); + if (param && param->value() == "all") { + detail = DETAIL_ALL; + } + std::string data = this->select_json(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -827,7 +1120,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { - set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); + set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root.createNestedArray("option"); for (auto &option : obj->traits.get_options()) { @@ -843,6 +1136,8 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value #ifdef USE_CLIMATE void WebServer::on_climate_update(climate::Climate *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); } @@ -851,7 +1146,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->climate_json(obj, DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -984,6 +1279,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->events_.count() == 0) + return; this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); } std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { @@ -997,7 +1294,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->lock_json(obj, obj->state, DETAIL_STATE); request->send(200, "application/json", data.c_str()); } else if (match.method == "lock") { @@ -1018,8 +1315,72 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat } #endif +#ifdef USE_VALVE +void WebServer::on_valve_update(valve::Valve *obj) { + if (this->events_.count() == 0) + return; + this->events_.send(this->valve_json(obj, DETAIL_STATE).c_str(), "state"); +} +void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (valve::Valve *obj : App.get_valves()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET && match.method.empty()) { + std::string data = this->valve_json(obj, DETAIL_STATE); + request->send(200, "application/json", data.c_str()); + continue; + } + + auto call = obj->make_call(); + if (match.method == "open") { + call.set_command_open(); + } else if (match.method == "close") { + call.set_command_close(); + } else if (match.method == "stop") { + call.set_command_stop(); + } else if (match.method == "toggle") { + call.set_command_toggle(); + } else if (match.method != "set") { + request->send(404); + return; + } + + auto traits = obj->get_traits(); + if (request->hasParam("position") && !traits.get_supports_position()) { + request->send(409); + return; + } + + if (request->hasParam("position")) { + auto position = parse_number(request->getParam("position")->value().c_str()); + if (position.has_value()) { + call.set_position(*position); + } + } + + this->schedule_([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} +std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); + root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); + + if (obj->get_traits().get_supports_position()) + root["position"] = obj->position; + }); +} +#endif + #ifdef USE_ALARM_CONTROL_PANEL void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { + if (this->events_.count() == 0) + return; this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state"); } std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, @@ -1036,7 +1397,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_GET) { + if (request->method() == HTTP_GET && match.method.empty()) { std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE); request->send(200, "application/json", data.c_str()); return; @@ -1046,6 +1407,28 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques } #endif +#ifdef USE_EVENT +void WebServer::on_event(event::Event *obj, const std::string &event_type) { + this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state"); +} + +std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { + return json::build_json([obj, event_type, start_config](JsonObject root) { + set_json_id(root, obj, "event-" + obj->get_object_id(), start_config); + if (!event_type.empty()) { + root["event_type"] = event_type; + } + if (start_config == DETAIL_ALL) { + JsonArray event_types = root.createNestedArray("event_types"); + for (auto const &event_type : obj->get_event_types()) { + event_types.add(event_type); + } + root["device_class"] = obj->get_device_class(); + } + }); +} +#endif + bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; @@ -1060,6 +1443,18 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS + if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { +#ifdef USE_ARDUINO + // Header needs to be added to interesting header list for it to not be + // nuked by the time we handle the request later. + // Only required in Arduino framework. + request->addInterestingHeader(HEADER_CORS_REQ_PNA); +#endif + return true; + } +#endif + UrlMatch match = match_url(request->url().c_str(), true); if (!match.valid) return false; @@ -1074,7 +1469,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_BUTTON - if (request->method() == HTTP_POST && match.domain == "button") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button") return true; #endif @@ -1108,6 +1503,26 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_DATETIME_DATE + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date") + return true; +#endif + +#ifdef USE_DATETIME_TIME + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "time") + return true; +#endif + +#ifdef USE_DATETIME_DATETIME + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime") + return true; +#endif + +#ifdef USE_TEXT + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text") + return true; +#endif + #ifdef USE_SELECT if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select") return true; @@ -1123,6 +1538,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_VALVE + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "valve") + return true; +#endif + #ifdef USE_ALARM_CONTROL_PANEL if (request->method() == HTTP_GET && match.domain == "alarm_control_panel") return true; @@ -1150,6 +1570,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS + if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { + this->handle_pna_cors_request(request); + return; + } +#endif + UrlMatch match = match_url(request->url().c_str()); #ifdef USE_SENSOR if (match.domain == "sensor") { @@ -1214,6 +1641,34 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_DATETIME_DATE + if (match.domain == "date") { + this->handle_date_request(request, match); + return; + } +#endif + +#ifdef USE_DATETIME_TIME + if (match.domain == "time") { + this->handle_time_request(request, match); + return; + } +#endif + +#ifdef USE_DATETIME_DATETIME + if (match.domain == "datetime") { + this->handle_datetime_request(request, match); + return; + } +#endif + +#ifdef USE_TEXT + if (match.domain == "text") { + this->handle_text_request(request, match); + return; + } +#endif + #ifdef USE_SELECT if (match.domain == "select") { this->handle_select_request(request, match); @@ -1236,6 +1691,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_VALVE + if (match.domain == "valve") { + this->handle_valve_request(request, match); + return; + } +#endif + #ifdef USE_ALARM_CONTROL_PANEL if (match.domain == "alarm_control_panel") { this->handle_alarm_control_panel_request(request, match); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 788e30ccf214..dda14a7e05a0 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -8,12 +8,12 @@ #include #ifdef USE_ESP32 -#include #include #include +#include #endif -#if USE_WEBSERVER_VERSION == 2 +#if USE_WEBSERVER_VERSION >= 2 extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM; extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE; #endif @@ -130,6 +130,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif +#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS + // Handle Private Network Access CORS OPTIONS request + void handle_pna_cors_request(AsyncWebServerRequest *request); +#endif + #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'. @@ -216,6 +221,42 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif +#ifdef USE_DATETIME_DATE + void on_date_update(datetime::DateEntity *obj) override; + /// Handle a date request under '/date/'. + void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// 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 + void on_time_update(datetime::TimeEntity *obj) override; + /// Handle a time request under '/time/'. + void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// 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 + void on_datetime_update(datetime::DateTimeEntity *obj) override; + /// Handle a datetime request under '/datetime/'. + void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// 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 + void on_text_update(text::Text *obj, const std::string &state) override; + /// Handle a text input request under '/text/'. + void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// 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 void on_select_update(select::Select *obj, const std::string &state, size_t index) override; /// Handle a select request under '/select/'. @@ -244,6 +285,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif +#ifdef USE_VALVE + void on_valve_update(valve::Valve *obj) override; + + /// Handle a valve request under '/valve//'. + void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the valve state as a JSON string. + std::string valve_json(valve::Valve *obj, JsonDetail start_config); +#endif + #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; @@ -255,6 +306,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); #endif +#ifdef USE_EVENT + void on_event(event::Event *obj, const std::string &event_type) override; + + /// 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 + /// Override the web handler's canHandle method. bool canHandle(AsyncWebServerRequest *request) override; /// Override the web handler's handleRequest method. diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 54bb577daf0f..1970b5a0c566 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -37,8 +37,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library( - "ESPAsyncWebServer-esphome", - None, - "https://github.com/libretiny-eu/ESPAsyncWebServer", - ) + cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.0") diff --git a/esphome/components/web_server_idf/utils.cpp b/esphome/components/web_server_idf/utils.cpp new file mode 100644 index 000000000000..6299937ce12e --- /dev/null +++ b/esphome/components/web_server_idf/utils.cpp @@ -0,0 +1,93 @@ +#ifdef USE_ESP_IDF +#include +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "http_parser.h" + +#include "utils.h" + +namespace esphome { +namespace web_server_idf { + +static const char *const TAG = "web_server_idf_utils"; + +void url_decode(char *str) { + char *ptr = str, buf; + for (; *str; str++, ptr++) { + if (*str == '%') { + str++; + if (parse_hex(str, 2, reinterpret_cast(&buf), 1) == 2) { + *ptr = buf; + str++; + } else { + str--; + *ptr = *str; + } + } else if (*str == '+') { + *ptr = ' '; + } else { + *ptr = *str; + } + } + *ptr = *str; +} + +bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); } + +optional request_get_header(httpd_req_t *req, const char *name) { + size_t len = httpd_req_get_hdr_value_len(req, name); + if (len == 0) { + return {}; + } + + std::string str; + str.resize(len); + + auto res = httpd_req_get_hdr_value_str(req, name, &str[0], len + 1); + if (res != ESP_OK) { + return {}; + } + + return {str}; +} + +optional request_get_url_query(httpd_req_t *req) { + auto len = httpd_req_get_url_query_len(req); + if (len == 0) { + return {}; + } + + std::string str; + str.resize(len); + + auto res = httpd_req_get_url_query_str(req, &str[0], len + 1); + if (res != ESP_OK) { + ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res)); + return {}; + } + + return {str}; +} + +optional query_key_value(const std::string &query_url, const std::string &key) { + if (query_url.empty()) { + return {}; + } + + auto val = std::unique_ptr(new char[query_url.size()]); + if (!val) { + ESP_LOGE(TAG, "Not enough memory to the query key value"); + return {}; + } + + if (httpd_query_key_value(query_url.c_str(), key.c_str(), val.get(), query_url.size()) != ESP_OK) { + return {}; + } + + url_decode(val.get()); + return {val.get()}; +} + +} // namespace web_server_idf +} // namespace esphome +#endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/utils.h b/esphome/components/web_server_idf/utils.h new file mode 100644 index 000000000000..9ed17c1d505c --- /dev/null +++ b/esphome/components/web_server_idf/utils.h @@ -0,0 +1,17 @@ +#pragma once +#ifdef USE_ESP_IDF + +#include +#include "esphome/core/helpers.h" + +namespace esphome { +namespace web_server_idf { + +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); +optional query_key_value(const std::string &query_url, const std::string &key); + +} // namespace web_server_idf +} // namespace esphome +#endif // USE_ESP_IDF diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 444e6824605f..cf187cd647ea 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -7,6 +7,7 @@ #include "esp_tls_crypto.h" +#include "utils.h" #include "web_server_idf.h" namespace esphome { @@ -47,27 +48,77 @@ void AsyncWebServer::begin() { const httpd_uri_t handler_post = { .uri = "", .method = HTTP_POST, - .handler = AsyncWebServer::request_handler, + .handler = AsyncWebServer::request_post_handler, .user_ctx = this, }; httpd_register_uri_handler(this->server_, &handler_post); + + const httpd_uri_t handler_options = { + .uri = "", + .method = HTTP_OPTIONS, + .handler = AsyncWebServer::request_handler, + .user_ctx = this, + }; + httpd_register_uri_handler(this->server_, &handler_options); + } +} + +esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) { + ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri); + auto content_type = request_get_header(r, "Content-Type"); + if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") { + ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request"); + // fallback to get handler to support backward compatibility + return AsyncWebServer::request_handler(r); + } + + if (!request_has_header(r, "Content-Length")) { + ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri); + httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr); + return ESP_OK; + } + + if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) { + ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len); + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; } + + std::string post_query; + if (r->content_len > 0) { + post_query.resize(r->content_len); + const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1); + if (ret <= 0) { // 0 return value indicates connection closed + if (ret == HTTPD_SOCK_ERR_TIMEOUT) { + httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr); + return ESP_ERR_TIMEOUT; + } + httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr); + return ESP_FAIL; + } + } + + AsyncWebServerRequest req(r, std::move(post_query)); + return static_cast(r->user_ctx)->request_handler_(&req); } esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) { - ESP_LOGV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri); + ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri); AsyncWebServerRequest req(r); - auto *server = static_cast(r->user_ctx); - for (auto *handler : server->handlers_) { - if (handler->canHandle(&req)) { + return static_cast(r->user_ctx)->request_handler_(&req); +} + +esp_err_t AsyncWebServer::request_handler_(AsyncWebServerRequest *request) const { + for (auto *handler : this->handlers_) { + if (handler->canHandle(request)) { // At now process only basic requests. // OTA requires multipart request support and handleUpload for it - handler->handleRequest(&req); + handler->handleRequest(request); return ESP_OK; } } - if (server->on_not_found_) { - server->on_not_found_(&req); + if (this->on_not_found_) { + this->on_not_found_(request); return ESP_OK; } return ESP_ERR_NOT_FOUND; @@ -80,20 +131,10 @@ AsyncWebServerRequest::~AsyncWebServerRequest() { } } +bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); } + optional AsyncWebServerRequest::get_header(const char *name) const { - size_t buf_len = httpd_req_get_hdr_value_len(*this, name); - if (buf_len == 0) { - return {}; - } - auto buf = std::unique_ptr(new char[++buf_len]); - if (!buf) { - ESP_LOGE(TAG, "No enough memory for get header %s", name); - return {}; - } - if (httpd_req_get_hdr_value_str(*this, name, buf.get(), buf_len) != ESP_OK) { - return {}; - } - return {buf.get()}; + return request_get_header(*this, name); } std::string AsyncWebServerRequest::url() const { @@ -183,74 +224,25 @@ void AsyncWebServerRequest::requestAuthentication(const char *realm) const { httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr); } -static std::string url_decode(const std::string &in) { - std::string out; - out.reserve(in.size()); - for (std::size_t i = 0; i < in.size(); ++i) { - if (in[i] == '%') { - ++i; - if (i + 1 < in.size()) { - auto c = parse_hex(&in[i], 2); - if (c.has_value()) { - out += static_cast(*c); - ++i; - } else { - out += '%'; - out += in[i++]; - out += in[i]; - } - } else { - out += '%'; - out += in[i]; - } - } else if (in[i] == '+') { - out += ' '; - } else { - out += in[i]; - } - } - return out; -} - AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) { auto find = this->params_.find(name); if (find != this->params_.end()) { return find->second; } - auto query_len = httpd_req_get_url_query_len(this->req_); - if (query_len == 0) { - return nullptr; - } - - auto query_str = std::unique_ptr(new char[++query_len]); - if (!query_str) { - ESP_LOGE(TAG, "No enough memory for get query param"); - return nullptr; - } - - auto res = httpd_req_get_url_query_str(*this, query_str.get(), query_len); - if (res != ESP_OK) { - ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res)); - return nullptr; - } - - auto query_val = std::unique_ptr(new char[query_len]); - if (!query_val) { - ESP_LOGE(TAG, "No enough memory for get query param value"); - return nullptr; + optional val = query_key_value(this->post_query_, name); + if (!val.has_value()) { + auto url_query = request_get_url_query(*this); + if (url_query.has_value()) { + val = query_key_value(url_query.value(), name); + } } - res = httpd_query_key_value(query_str.get(), name.c_str(), query_val.get(), query_len); - if (res != ESP_OK) { - this->params_.insert({name, nullptr}); - return nullptr; + AsyncWebParameter *param = nullptr; + if (val.has_value()) { + param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory) } - query_str.release(); - auto decoded = url_decode(query_val.get()); - query_val.release(); - auto *param = new AsyncWebParameter(decoded); // NOLINT(cppcoreguidelines-owning-memory) - this->params_.insert(std::make_pair(name, param)); + this->params_.insert({name, param}); return param; } @@ -261,14 +253,15 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) { void AsyncResponseStream::print(float value) { this->print(to_string(value)); } void AsyncResponseStream::printf(const char *fmt, ...) { - std::string str; va_list args; va_start(args, fmt); - size_t length = vsnprintf(nullptr, 0, fmt, args); + const int length = vsnprintf(nullptr, 0, fmt, args); va_end(args); + std::string str; str.resize(length); + va_start(args, fmt); vsnprintf(&str[0], length + 1, fmt, args); va_end(args); @@ -305,6 +298,10 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); httpd_resp_set_hdr(req, "Connection", "keep-alive"); + for (const auto &pair : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str()); + } + httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN); req->sess_ctx = this; diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index f3cecca16fcf..2ead5e3f0372 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -3,11 +3,11 @@ #include -#include #include -#include #include #include +#include +#include namespace esphome { namespace web_server_idf { @@ -90,11 +90,10 @@ class AsyncWebServerResponseProgmem : public AsyncWebServerResponse { protected: const uint8_t *data_; - const size_t size_; + size_t size_; }; class AsyncWebServerRequest { - // FIXME friend class AsyncWebServerResponse; friend class AsyncWebServer; public: @@ -117,7 +116,7 @@ class AsyncWebServerRequest { // NOLINTNEXTLINE(readability-identifier-naming) AsyncWebServerResponse *beginResponse(int code, const char *content_type) { auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory) - this->init_response_(res, 200, content_type); + this->init_response_(res, code, content_type); return res; } // NOLINTNEXTLINE(readability-identifier-naming) @@ -157,12 +156,16 @@ class AsyncWebServerRequest { operator httpd_req_t *() const { return this->req_; } optional get_header(const char *name) const; + // NOLINTNEXTLINE(readability-identifier-naming) + bool hasHeader(const char *name) const; protected: httpd_req_t *req_; AsyncWebServerResponse *rsp_{}; std::map params_; + std::string post_query_; AsyncWebServerRequest(httpd_req_t *req) : req_(req) {} + AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {} void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type); }; @@ -189,6 +192,8 @@ class AsyncWebServer { uint16_t port_{}; httpd_handle_t server_{}; 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; std::vector handlers_; std::function on_not_found_{}; }; @@ -246,6 +251,8 @@ class AsyncEventSource : public AsyncWebHandler { void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); + size_t count() const { return this->sessions_.size(); } + protected: std::string url_; std::set sessions_; @@ -254,6 +261,7 @@ class AsyncEventSource : public AsyncWebHandler { class DefaultHeaders { friend class AsyncWebServerRequest; + friend class AsyncEventSourceResponse; public: // NOLINTNEXTLINE(readability-identifier-naming) diff --git a/esphome/components/weikai/__init__.py b/esphome/components/weikai/__init__.py new file mode 100644 index 000000000000..4248c48e3507 --- /dev/null +++ b/esphome/components/weikai/__init__.py @@ -0,0 +1,108 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import ( + CONF_BAUD_RATE, + CONF_CHANNEL, + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, +) + +CODEOWNERS = ["@DrCoolZic"] +AUTO_LOAD = ["uart"] + +MULTI_CONF = True +CONF_STOP_BITS = "stop_bits" +CONF_PARITY = "parity" +CONF_CRYSTAL = "crystal" +CONF_UART = "uart" +CONF_TEST_MODE = "test_mode" + +weikai_ns = cg.esphome_ns.namespace("weikai") +WeikaiComponent = weikai_ns.class_("WeikaiComponent", cg.Component) +WeikaiChannel = weikai_ns.class_("WeikaiChannel", uart.UARTComponent) + + +def check_channel_max(value, max): + channel_uniq = [] + channel_dup = [] + for x in value[CONF_UART]: + if x[CONF_CHANNEL] > max - 1: + raise cv.Invalid(f"Invalid channel number: {x[CONF_CHANNEL]}") + if x[CONF_CHANNEL] not in channel_uniq: + channel_uniq.append(x[CONF_CHANNEL]) + else: + channel_dup.append(x[CONF_CHANNEL]) + if len(channel_dup) > 0: + raise cv.Invalid(f"Duplicate channel list: {channel_dup}") + return value + + +def check_channel_max_4(value): + return check_channel_max(value, 4) + + +def check_channel_max_2(value): + return check_channel_max(value, 2) + + +WKBASE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(WeikaiComponent), + cv.Optional(CONF_CRYSTAL, default=14745600): cv.int_, + cv.Optional(CONF_TEST_MODE, default=0): cv.int_, + cv.Required(CONF_UART): cv.ensure_list( + { + cv.Required(CONF_ID): cv.declare_id(WeikaiChannel), + cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=3), + cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), + cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), + cv.Optional(CONF_PARITY, default="NONE"): cv.enum( + uart.UART_PARITY_OPTIONS, upper=True + ), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def register_weikai(var, config): + """Register an weikai device with the given config.""" + cg.add(var.set_crystal(config[CONF_CRYSTAL])) + cg.add(var.set_test_mode(config[CONF_TEST_MODE])) + await cg.register_component(var, config) + for uart_elem in config[CONF_UART]: + chan = cg.new_Pvariable(uart_elem[CONF_ID]) + cg.add(chan.set_channel_name(str(uart_elem[CONF_ID]))) + cg.add(chan.set_parent(var)) + cg.add(chan.set_channel(uart_elem[CONF_CHANNEL])) + cg.add(chan.set_baud_rate(uart_elem[CONF_BAUD_RATE])) + cg.add(chan.set_stop_bits(uart_elem[CONF_STOP_BITS])) + cg.add(chan.set_parity(uart_elem[CONF_PARITY])) + + +def validate_pin_mode(value): + """Checks input/output mode inconsistency""" + if not (value[CONF_MODE][CONF_INPUT] or value[CONF_MODE][CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_MODE][CONF_INPUT] and value[CONF_MODE][CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + return value + + +WEIKAI_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp new file mode 100644 index 000000000000..a04bc0a574de --- /dev/null +++ b/esphome/components/weikai/weikai.cpp @@ -0,0 +1,615 @@ +/// @file weikai.cpp +/// @brief WeiKai component family - classes implementation +/// @date Last Modified: 2024/04/06 15:13:11 +/// @details The classes declared in this file can be used by the Weikai family + +#include "weikai.h" + +namespace esphome { +namespace weikai { + +/*! @mainpage Weikai source code documentation + This documentation provides information about the implementation of the family of WeiKai Components in ESPHome. + Here is the class diagram related to Weikai family of components: + @image html weikai_class.png + + @section WKRingBuffer_ The WKRingBuffer template class +The WKRingBuffer template class has it names implies implement a simple ring buffer helper class. This straightforward +container implements FIFO functionality, enabling bytes to be pushed into one side and popped from the other in the +order of entry. Implementation is classic and therefore not described in any details. + + @section WeikaiRegister_ The WeikaiRegister class + The WeikaiRegister helper class creates objects that act as proxies to the device registers. + @details This is an abstract virtual class (interface) that provides all the necessary access to registers while hiding + the actual implementation. The access to the registers can be made through an I²C bus in for example for wk2168_i2c + component or through a SPI bus for example in the case of the wk2168_spi component. Derived classes will actually + performs the specific bus operations. + + @section WeikaiRegisterI2C_ WeikaiRegisterI2C + The weikai_i2c::WeikaiRegisterI2C class implements the virtual methods of the WeikaiRegister class for an I2C bus. + + @section WeikaiRegisterSPI_ WeikaiRegisterSPI + The weikai_spi::WeikaiRegisterSPI class implements the virtual methods of the WeikaiRegister class for an SPI bus. + + @section WeikaiComponent_ The WeikaiComponent class +The WeikaiComponent class stores the information global to a WeiKai family component and provides methods to set/access +this information. It also serves as a container for WeikaiChannel instances. This is done by maintaining an array of +references these WeikaiChannel instances. This class derives from the esphome::Component classes. This class override +esphome::Component::loop() method to facilitate the seamless transfer of accumulated bytes from the receive +FIFO into the ring buffer. This process ensures quick access to the stored bytes, enhancing the overall efficiency of +the component. + + @section WeikaiComponentI2C_ WeikaiComponentI2C + The weikai_i2c::WeikaiComponentI2C class implements the virtual methods of the WeikaiComponent class for an I2C bus. + + @section WeikaiComponentSPI_ WeikaiComponentSPI + The weikai_spi::WeikaiComponentSPI class implements the virtual methods of the WeikaiComponent class for an SPI bus. + + @section WeikaiGPIOPin_ WeikaiGPIOPin class + The WeikaiGPIOPin class is an helper class to expose the GPIO pins of WK family components as if they were internal + GPIO pins. It also provides the setup() and dump_summary() methods. + + @section WeikaiChannel_ The WeikaiChannel class + The WeikaiChannel class is used to implement all the virtual methods of the ESPHome uart::UARTComponent class. An + individual instance of this class is created for each UART channel. It has a link back to the WeikaiComponent object it + belongs to. This class derives from the uart::UARTComponent class. It collaborates through an aggregation with + WeikaiComponent. This implies that WeikaiComponent acts as a container, housing several WeikaiChannel instances. + Furthermore, the WeikaiChannel class derives from the ESPHome uart::UARTComponent class, it also has an association + relationship with the WKRingBuffer and WeikaiRegister helper classes. Consequently, when a WeikaiChannel instance is + destroyed, the associated WKRingBuffer instance is also destroyed. + +*/ + +static const char *const TAG = "weikai"; + +/// @brief convert an int to binary representation as C++ std::string +/// @param val integer to convert +/// @return a std::string +inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); } +/// Convert std::string to C string +#define I2S2CS(val) (i2s(val).c_str()) + +/// @brief measure the time elapsed between two calls +/// @param last_time time of the previous call +/// @return the elapsed time in milliseconds +uint32_t elapsed_ms(uint32_t &last_time) { + uint32_t e = millis() - last_time; + last_time = millis(); + return e; +}; + +/// @brief Converts the parity enum value to a C string +/// @param parity enum +/// @return the string +const char *p2s(uart::UARTParityOptions parity) { + using namespace uart; + switch (parity) { + case UART_CONFIG_PARITY_NONE: + return "NONE"; + case UART_CONFIG_PARITY_EVEN: + return "EVEN"; + case UART_CONFIG_PARITY_ODD: + return "ODD"; + default: + return "UNKNOWN"; + } +} + +/// @brief Display a buffer in hexadecimal format (32 hex values / line) for debug +void print_buffer(const uint8_t *data, size_t length) { + char hex_buffer[100]; + hex_buffer[(3 * 32) + 1] = 0; + for (size_t i = 0; i < length; i++) { + snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]); + if (i % 32 == 31) { + ESP_LOGVV(TAG, " %s", hex_buffer); + } + } + if (length % 32) { + // null terminate if incomplete line + hex_buffer[3 * (length % 32) + 2] = 0; + ESP_LOGVV(TAG, " %s", hex_buffer); + } +} + +static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER", + "SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"}; +static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL", + "TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"}; + +// method to print a register value as text: used in the log messages ... +const char *reg_to_str(int reg, bool page1) { + if (reg == WKREG_GPDAT) { + return "GPDAT"; + } else if (reg == WKREG_GPDIR) { + return "GPDIR"; + } else { + return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F]; + } +} + +enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiRegister methods +/////////////////////////////////////////////////////////////////////////////// +WeikaiRegister &WeikaiRegister::operator=(uint8_t value) { + write_reg(value); + return *this; +} + +WeikaiRegister &WeikaiRegister::operator&=(uint8_t value) { + value &= read_reg(); + write_reg(value); + return *this; +} + +WeikaiRegister &WeikaiRegister::operator|=(uint8_t value) { + value |= read_reg(); + write_reg(value); + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiComponent methods +/////////////////////////////////////////////////////////////////////////////// +void WeikaiComponent::loop() { + if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP) + return; + + // If there are some bytes in the receive FIFO we transfers them to the ring buffers + size_t transferred = 0; + for (auto *child : this->children_) { + // we look if some characters has been received in the fifo + transferred += child->xfer_fifo_to_buffer_(); + } + if (transferred > 0) { + ESP_LOGV(TAG, "we transferred %d bytes from fifo to buffer...", transferred); + } + +#ifdef TEST_COMPONENT + static uint32_t loop_time = 0; + static uint32_t loop_count = 0; + uint32_t time = 0; + + if (test_mode_ == 1) { // test component in loopback + ESP_LOGI(TAG, "Component loop %" PRIu32 " for %s : %" PRIu32 " ms since last call ...", loop_count++, + this->get_name(), millis() - loop_time); + loop_time = millis(); + char message[64]; + elapsed_ms(time); // set time to now + for (int i = 0; i < this->children_.size(); i++) { + if (i != ((loop_count - 1) % this->children_.size())) // we do only one per loop + continue; + snprintf(message, sizeof(message), "%s:%s", this->get_name(), children_[i]->get_channel_name()); + children_[i]->uart_send_test_(message); + uint32_t const start_time = millis(); + while (children_[i]->tx_fifo_is_not_empty_()) { // wait until buffer empty + if (millis() - start_time > 1500) { + ESP_LOGE(TAG, "timeout while flushing - %d bytes left in buffer...", children_[i]->tx_in_fifo_()); + break; + } + yield(); // reschedule our thread to avoid blocking + } + bool status = children_[i]->uart_receive_test_(message); + ESP_LOGI(TAG, "Test %s => send/received %u bytes %s - execution time %" PRIu32 " ms...", message, + RING_BUFFER_SIZE, status ? "correctly" : "with error", elapsed_ms(time)); + } + } + + if (this->test_mode_ == 2) { // test component in echo mode + for (auto *child : this->children_) { + uint8_t data = 0; + if (child->available()) { + child->read_byte(&data); + ESP_LOGI(TAG, "echo mode: read -> send %02X", data); + child->write_byte(data); + } + } + } + if (test_mode_ == 3) { + test_gpio_input_(); + } + + if (test_mode_ == 4) { + test_gpio_output_(); + } +#endif +} + +#if defined(TEST_COMPONENT) +void WeikaiComponent::test_gpio_input_() { + static bool init_input{false}; + static uint8_t state{0}; + uint8_t value; + if (!init_input) { + init_input = true; + // set all pins in input mode + this->reg(WKREG_GPDIR, 0) = 0x00; + ESP_LOGI(TAG, "initializing all pins to input mode"); + state = this->reg(WKREG_GPDAT, 0); + ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, I2S2CS(state)); + } + value = this->reg(WKREG_GPDAT, 0); + if (value != state) { + ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, I2S2CS(value)); + state = value; + } +} + +void WeikaiComponent::test_gpio_output_() { + static bool init_output{false}; + static uint8_t state{0}; + if (!init_output) { + init_output = true; + // set all pins in output mode + this->reg(WKREG_GPDIR, 0) = 0xFF; + ESP_LOGI(TAG, "initializing all pins to output mode"); + this->reg(WKREG_GPDAT, 0) = state; + ESP_LOGI(TAG, "setting all outputs to 0"); + } + state = ~state; + this->reg(WKREG_GPDAT, 0) = state; + ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, I2S2CS(state)); + delay(100); // NOLINT +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiGPIOPin methods +/////////////////////////////////////////////////////////////////////////////// +bool WeikaiComponent::read_pin_val_(uint8_t pin) { + this->input_state_ = this->reg(WKREG_GPDAT, 0); + ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin), I2S2CS(input_state_)); + return this->input_state_ & (1 << pin); +} + +void WeikaiComponent::write_pin_val_(uint8_t pin, bool value) { + if (value) { + this->output_state_ |= (1 << pin); + } else { + this->output_state_ &= ~(1 << pin); + } + ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value), I2S2CS(this->output_state_)); + this->reg(WKREG_GPDAT, 0) = this->output_state_; +} + +void WeikaiComponent::set_pin_direction_(uint8_t pin, gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + this->pin_config_ &= ~(1 << pin); // clear bit (input mode) + } else { + if (flags == gpio::FLAG_OUTPUT) { + this->pin_config_ |= 1 << pin; // set bit (output mode) + } else { + ESP_LOGE(TAG, "pin %d direction invalid", pin); + } + } + ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, I2S2CS(this->pin_config_)); + this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~ +} + +void WeikaiGPIOPin::setup() { + ESP_LOGCONFIG(TAG, "Setting GPIO pin %d mode to %s", this->pin_, + flags_ == gpio::FLAG_INPUT ? "Input" + : this->flags_ == gpio::FLAG_OUTPUT ? "Output" + : "NOT SPECIFIED"); + // ESP_LOGCONFIG(TAG, "Setting GPIO pins mode to '%s' %02X", I2S2CS(this->flags_), this->flags_); + 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; +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiChannel methods +/////////////////////////////////////////////////////////////////////////////// +void WeikaiChannel::setup_channel() { + ESP_LOGCONFIG(TAG, " Setting up UART %s:%s ...", this->parent_->get_name(), this->get_channel_name()); + // we enable transmit and receive on this channel + if (this->check_channel_down()) { + ESP_LOGCONFIG(TAG, " Error channel %s not working...", this->get_channel_name()); + } + this->reset_fifo_(); + this->receive_buffer_.clear(); + this->set_line_param_(); + this->set_baudrate_(); +} + +void WeikaiChannel::dump_channel() { + ESP_LOGCONFIG(TAG, " UART %s ...", this->get_channel_name()); + ESP_LOGCONFIG(TAG, " Baud rate: %" PRIu32 " Bd", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", p2s(this->parity_)); +} + +void WeikaiChannel::reset_fifo_() { + // enable transmission and reception + this->reg(WKREG_SCR) = SCR_RXEN | SCR_TXEN; + // we reset and enable transmit and receive FIFO + this->reg(WKREG_FCR) = FCR_TFEN | FCR_RFEN | FCR_TFRST | FCR_RFRST; +} + +void WeikaiChannel::set_line_param_() { + this->data_bits_ = 8; // always equal to 8 for WeiKai (cant be changed) + uint8_t lcr = 0; + if (this->stop_bits_ == 2) + lcr |= LCR_STPL; + switch (this->parity_) { // parity selection settings + case uart::UART_CONFIG_PARITY_ODD: + lcr |= (LCR_PAEN | LCR_PAR_ODD); + break; + case uart::UART_CONFIG_PARITY_EVEN: + lcr |= (LCR_PAEN | LCR_PAR_EVEN); + break; + default: + break; // no parity 000x + } + this->reg(WKREG_LCR) = lcr; // write LCR + ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_, + this->stop_bits_, p2s(this->parity_), I2S2CS(lcr)); +} + +void WeikaiChannel::set_baudrate_() { + if (this->baud_rate_ > this->parent_->crystal_ / 16) { + baud_rate_ = this->parent_->crystal_ / 16; + ESP_LOGE(TAG, " Requested baudrate too high for crystal=%" PRIu32 " Hz. Has been reduced to %" PRIu32 " Bd", + this->parent_->crystal_, this->baud_rate_); + }; + uint16_t const val_int = this->parent_->crystal_ / (this->baud_rate_ * 16) - 1; + uint16_t val_dec = (this->parent_->crystal_ % (this->baud_rate_ * 16)) / (this->baud_rate_ * 16); + uint8_t const baud_high = (uint8_t) (val_int >> 8); + uint8_t const baud_low = (uint8_t) (val_int & 0xFF); + while (val_dec > 0x0A) + val_dec /= 0x0A; + uint8_t const baud_dec = (uint8_t) (val_dec); + + this->parent_->page1_ = true; // switch to page 1 + this->reg(WKREG_SPAGE) = 1; + this->reg(WKREG_BRH) = baud_high; + this->reg(WKREG_BRL) = baud_low; + this->reg(WKREG_BRD) = baud_dec; + this->parent_->page1_ = false; // switch back to page 0 + this->reg(WKREG_SPAGE) = 0; + + ESP_LOGV(TAG, " Crystal=%d baudrate=%d => registers [%d %d %d]", this->parent_->crystal_, this->baud_rate_, + baud_high, baud_low, baud_dec); +} + +inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; } + +size_t WeikaiChannel::tx_in_fifo_() { + size_t tfcnt = this->reg(WKREG_TFCNT); + if (tfcnt == 0) { + uint8_t const fsr = this->reg(WKREG_FSR); + if (fsr & FSR_TFFULL) { + ESP_LOGVV(TAG, "tx FIFO full FSR=%s", I2S2CS(fsr)); + tfcnt = FIFO_SIZE; + } + } + ESP_LOGVV(TAG, "tx FIFO contains %d bytes", tfcnt); + return tfcnt; +} + +size_t WeikaiChannel::rx_in_fifo_() { + size_t available = this->reg(WKREG_RFCNT); + uint8_t const fsr = this->reg(WKREG_FSR); + if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) { + if (fsr & FSR_RFOE) + ESP_LOGE(TAG, "Receive data overflow FSR=%s", I2S2CS(fsr)); + if (fsr & FSR_RFLB) + ESP_LOGE(TAG, "Receive line break FSR=%s", I2S2CS(fsr)); + if (fsr & FSR_RFFE) + ESP_LOGE(TAG, "Receive frame error FSR=%s", I2S2CS(fsr)); + if (fsr & FSR_RFPE) + ESP_LOGE(TAG, "Receive parity error FSR=%s", I2S2CS(fsr)); + } + if ((available == 0) && (fsr & FSR_RFDAT)) { + // here we should be very careful because we can have something like this: + // - at time t0 we read RFCNT=0 because nothing yet received + // - at time t0+delta we might read FIFO not empty because one byte has just been received + // - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full + available = this->reg(WKREG_RFCNT); + if (available == 0) { // still zero ? + ESP_LOGV(TAG, "rx FIFO is full FSR=%s", I2S2CS(fsr)); + available = FIFO_SIZE; + } + } + ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, I2S2CS(fsr)); + return available; +} + +bool WeikaiChannel::check_channel_down() { + // to check if we channel is up we write to the LCR W/R register + // note that this will put a break on the tx line for few ms + WeikaiRegister &lcr = this->reg(WKREG_LCR); + lcr = 0x3F; + uint8_t val = lcr; + if (val != 0x3F) { + ESP_LOGE(TAG, "R/W of register failed expected 0x3F received 0x%02X", val); + return true; + } + lcr = 0; + val = lcr; + if (val != 0x00) { + ESP_LOGE(TAG, "R/W of register failed expected 0x00 received 0x%02X", val); + return true; + } + return false; +} + +bool WeikaiChannel::peek_byte(uint8_t *buffer) { + auto available = this->receive_buffer_.count(); + if (!available) + xfer_fifo_to_buffer_(); + return this->receive_buffer_.peek(*buffer); +} + +int WeikaiChannel::available() { + size_t available = this->receive_buffer_.count(); + if (!available) + available = xfer_fifo_to_buffer_(); + return available; +} + +bool WeikaiChannel::read_array(uint8_t *buffer, size_t length) { + bool status = true; + auto available = this->receive_buffer_.count(); + if (length > available) { + ESP_LOGW(TAG, "read_array: buffer underflow requested %d bytes only %d bytes available...", length, available); + length = available; + status = false; + } + // retrieve the bytes from ring buffer + for (size_t i = 0; i < length; i++) { + this->receive_buffer_.pop(buffer[i]); + } + ESP_LOGVV(TAG, "read_array(ch=%d buffer[0]=%02X, length=%d): status %s", this->channel_, *buffer, length, + status ? "OK" : "ERROR"); + return status; +} + +void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) { + if (length > XFER_MAX_SIZE) { + ESP_LOGE(TAG, "Write_array: invalid call - requested %d bytes but max size %d ...", length, XFER_MAX_SIZE); + length = XFER_MAX_SIZE; + } + this->reg(0).write_fifo(const_cast(buffer), length); +} + +void WeikaiChannel::flush() { + uint32_t const start_time = millis(); + while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty + if (millis() - start_time > 200) { + ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms...", this->tx_in_fifo_()); + return; + } + yield(); // reschedule our thread to avoid blocking + } +} + +size_t WeikaiChannel::xfer_fifo_to_buffer_() { + size_t to_transfer; + size_t free; + while ((to_transfer = this->rx_in_fifo_()) && (free = this->receive_buffer_.free())) { + // while bytes in fifo and some room in the buffer we transfer + if (to_transfer > XFER_MAX_SIZE) + to_transfer = XFER_MAX_SIZE; // we can only do so much + if (to_transfer > free) + to_transfer = free; // we'll do the rest next time + if (to_transfer) { + uint8_t data[to_transfer]; + this->reg(0).read_fifo(data, to_transfer); + for (size_t i = 0; i < to_transfer; i++) + this->receive_buffer_.push(data[i]); + } + } // while work to do + return to_transfer; +} + +/// +// TEST COMPONENT +// +#ifdef TEST_COMPONENT +/// @addtogroup test_ Test component information +/// @{ + +/// @brief An increment "Functor" (i.e. a class object that acts like a method with state!) +/// +/// Functors are objects that can be treated as though they are a function or function pointer. +class Increment { + public: + /// @brief constructor: initialize current value to 0 + Increment() : i_(0) {} + /// @brief overload of the parenthesis operator. + /// Returns the current value and auto increment it + /// @return the current value. + uint8_t operator()() { return i_++; } + + private: + uint8_t i_; +}; + +/// @brief Hex converter to print/display a buffer in hexadecimal format (32 hex values / line). +/// @param buffer contains the values to display +void print_buffer(std::vector buffer) { + char hex_buffer[100]; + hex_buffer[(3 * 32) + 1] = 0; + for (size_t i = 0; i < buffer.size(); i++) { + snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", buffer[i]); + if (i % 32 == 31) + ESP_LOGI(TAG, " %s", hex_buffer); + } + if (buffer.size() % 32) { + // null terminate if incomplete line + hex_buffer[3 * (buffer.size() % 32) + 1] = 0; + ESP_LOGI(TAG, " %s", hex_buffer); + } +} + +/// @brief test the write_array method +void WeikaiChannel::uart_send_test_(char *message) { + auto start_exec = micros(); + std::vector output_buffer(XFER_MAX_SIZE); + generate(output_buffer.begin(), output_buffer.end(), Increment()); // fill with incrementing number + size_t to_send = RING_BUFFER_SIZE; + while (to_send) { + this->write_array(&output_buffer[0], XFER_MAX_SIZE); // we send the buffer + this->flush(); + to_send -= XFER_MAX_SIZE; + } + ESP_LOGV(TAG, "%s => sent %d bytes - exec time %d µs ...", message, RING_BUFFER_SIZE, micros() - start_exec); +} + +/// @brief test read_array method +bool WeikaiChannel::uart_receive_test_(char *message) { + auto start_exec = micros(); + bool status = true; + size_t received = 0; + std::vector buffer(RING_BUFFER_SIZE); + + // we wait until we have received all the bytes + uint32_t const start_time = millis(); + status = true; + while (received < RING_BUFFER_SIZE) { + while (XFER_MAX_SIZE > this->available()) { + this->xfer_fifo_to_buffer_(); + if (millis() - start_time > 1500) { + ESP_LOGE(TAG, "uart_receive_test_() timeout: only %d bytes received...", this->available()); + break; + } + yield(); // reschedule our thread to avoid blocking + } + status = this->read_array(&buffer[received], XFER_MAX_SIZE) && status; + received += XFER_MAX_SIZE; + } + + uint8_t peek_value = 0; + this->peek_byte(&peek_value); + if (peek_value != 0) { + ESP_LOGE(TAG, "Peek first byte value error..."); + status = false; + } + + for (size_t i = 0; i < RING_BUFFER_SIZE; i++) { + if (buffer[i] != i % XFER_MAX_SIZE) { + ESP_LOGE(TAG, "Read buffer contains error...b=%x i=%x", buffer[i], i % XFER_MAX_SIZE); + print_buffer(buffer); + status = false; + break; + } + } + + ESP_LOGV(TAG, "%s => received %d bytes status %s - exec time %d µs ...", message, received, status ? "OK" : "ERROR", + micros() - start_exec); + return status; +} + +/// @} +#endif + +} // namespace weikai +} // namespace esphome diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h new file mode 100644 index 000000000000..042c7291625b --- /dev/null +++ b/esphome/components/weikai/weikai.h @@ -0,0 +1,443 @@ +/// @file weikai.h +/// @author DrCoolZic +/// @brief WeiKai component family - classes declaration +/// @date Last Modified: 2024/04/06 14:44:17 +/// @details The classes declared in this file can be used by the Weikai family +/// of UART and GPIO expander components. As of today it provides support for +/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi, +/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c + +#pragma once +#include +#include +#include +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "wk_reg_def.h" + +/// When the TEST_COMPONENT flag is defined we include some auto-test methods. Used to test the software during +/// development but can also be used in situ to test if the component is working correctly. For release we do +/// not set it by default but you can set it by using the following lines in you configuration file: +/// @code +/// esphome: +/// platformio_options: +/// build_flags: +/// - -DTEST_COMPONENT +/// @endcode +// #define TEST_COMPONENT + +namespace esphome { +namespace weikai { + +/// @brief XFER_MAX_SIZE defines the maximum number of bytes allowed during one transfer. +#if defined(I2C_BUFFER_LENGTH) +constexpr size_t XFER_MAX_SIZE = I2C_BUFFER_LENGTH; +#else +constexpr size_t XFER_MAX_SIZE = 128; +#endif + +/// @brief size of the internal WeiKai FIFO +constexpr size_t FIFO_SIZE = 256; + +/// @brief size of the ring buffer set to size of the FIFO +constexpr size_t RING_BUFFER_SIZE = FIFO_SIZE; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief This is an helper class that provides a simple ring buffers that works as a FIFO +/// @details This ring buffer is used to buffer the bytes received in the FIFO of the Weika device. The best way to read +/// characters from the device FIFO, is to first check how many bytes were received and then read them all at once. +/// Unfortunately in all the code I have reviewed the characters are read one by one in a while loop by checking if +/// bytes are available then reading the byte until no more byte available. This is pretty inefficient for two reasons: +/// - Fist you need to perform a test for each byte to read +/// - and second you call the read byte method for each character. +/// . +/// Assuming you need to read 100 bytes that results into 200 calls. This is to compare to 2 calls (one to find the +/// number of bytes available plus one to read all the bytes) in the best case! If the registers you read are located on +/// the micro-controller this is acceptable because the registers can be accessed fast. But when the registers are +/// located on a remote device accessing them requires several cycles on a slow bus. As it it not possible to fix this +/// problem by asking users to rewrite their code, I have implemented this ring buffer that store the bytes received +/// locally. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template class WKRingBuffer { + public: + /// @brief pushes an item at the tail of the fifo + /// @param item item to push + /// @return true if item has been pushed, false il item could not pushed (buffer full) + bool push(const T item) { + if (is_full()) + return false; + this->rb_[this->head_] = item; + this->head_ = (this->head_ + 1) % SIZE; + this->count_++; + return true; + } + + /// @brief return and remove the item at head of the fifo + /// @param item item read + /// @return true if an item has been retrieved, false il no item available (buffer empty) + bool pop(T &item) { + if (is_empty()) + return false; + item = this->rb_[this->tail_]; + this->tail_ = (this->tail_ + 1) % SIZE; + this->count_--; + return true; + } + + /// @brief return the value of the item at fifo's head without removing it + /// @param item pointer to item to return + /// @return true if item has been retrieved, false il no item available (buffer empty) + bool peek(T &item) { + if (is_empty()) + return false; + item = this->rb_[this->tail_]; + return true; + } + + /// @brief test is the Ring Buffer is empty ? + /// @return true if empty + inline bool is_empty() { return (this->count_ == 0); } + + /// @brief test is the ring buffer is full ? + /// @return true if full + inline bool is_full() { return (this->count_ == SIZE); } + + /// @brief return the number of item in the ring buffer + /// @return the number of items + inline size_t count() { return this->count_; } + + /// @brief returns the number of free positions in the buffer + /// @return how many items can be added + inline size_t free() { return SIZE - this->count_; } + + /// @brief clear the buffer content + inline void clear() { this->head_ = this->tail_ = this->count_ = 0; } + + protected: + std::array rb_{0}; ///< the ring buffer + int tail_{0}; ///< position of the next element to read + int head_{0}; ///< position of the next element to write + size_t count_{0}; ///< count number of element in the buffer +}; + +class WeikaiComponent; +// class WeikaiComponentSPI; +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief WeikaiRegister objects acts as proxies to access remote register independently of the bus type. +/// @details This is an abstract interface class that provides many operations to access to registers while hiding +/// the actual implementation. This allow to accesses the registers in the Weikai component abstract class independently +/// of the actual bus (I2C, SPI). The derived classes will actually implements the specific bus operations dependant of +/// the bus used. +/// @n typical usage of WeikaiRegister: +/// @code +/// WeikaiRegister reg_X {&WeikaiComponent, ADDR_REGISTER_X, CHANNEL_NUM} // declaration +/// reg_X |= 0x01; // set bit 0 of the weikai register +/// reg_X &= ~0x01; // reset bit 0 of the weikai register +/// reg_X = 10; // Set the value of weikai register +/// uint val = reg_X; // get the value of weikai register +/// @endcode +class WeikaiRegister { + public: + /// @brief WeikaiRegister constructor. + /// @param comp our parent WeikaiComponent + /// @param reg address of the register + /// @param channel the channel of this register + WeikaiRegister(WeikaiComponent *const comp, uint8_t reg, uint8_t channel) + : comp_(comp), register_(reg), channel_(channel) {} + virtual ~WeikaiRegister() {} + + /// @brief overloads the = operator. This is used to set a value into the weikai register + /// @param value to be set + /// @return this object + WeikaiRegister &operator=(uint8_t value); + + /// @brief overloads the compound &= operator. This is often used to reset bits in the weikai register + /// @param value performs an & operation with value and store the result + /// @return this object + WeikaiRegister &operator&=(uint8_t value); + + /// @brief overloads the compound |= operator. This is often used to set bits in the weikai register + /// @param value performs an | operation with value and store the result + /// @return this object + WeikaiRegister &operator|=(uint8_t value); + + /// @brief cast operator that returns the content of the weikai register + operator uint8_t() const { return read_reg(); } + + /// @brief reads the register + /// @return the value read from the register + virtual uint8_t read_reg() const = 0; + + /// @brief writes the register + /// @param value to write in the register + virtual void write_reg(uint8_t value) = 0; + + /// @brief read an array of bytes from the receiver fifo + /// @param data pointer to data buffer + /// @param length number of bytes to read + virtual void read_fifo(uint8_t *data, size_t length) const = 0; + + /// @brief write an array of bytes to the transmitter fifo + /// @param data pointer to data buffer + /// @param length number of bytes to write + virtual void write_fifo(uint8_t *data, size_t length) = 0; + + WeikaiComponent *const comp_; ///< pointer to our parent (aggregation) + uint8_t register_; ///< address of the register + uint8_t channel_; ///< channel for this register +}; + +class WeikaiChannel; // forward declaration +//////////////////////////////////////////////////////////////////////////////////// +/// @brief The WeikaiComponent class stores the information global to the WeiKai component +/// and provides methods to set/access this information. It is also the container of +/// the WeikaiChannel children objects. This class is derived from esphome::Component +/// class. +//////////////////////////////////////////////////////////////////////////////////// +class WeikaiComponent : public Component { + public: + /// @brief virtual destructor + virtual ~WeikaiComponent() {} + + /// @brief store crystal frequency + /// @param crystal frequency + void set_crystal(uint32_t crystal) { this->crystal_ = crystal; } + + /// @brief store if the component is in test mode + /// @param test_mode 0=normal mode any other values mean component in test mode + void set_test_mode(int test_mode) { this->test_mode_ = test_mode; } + + /// @brief store the name for the component + /// @param name the name as defined by the python code generator + void set_name(std::string name) { this->name_ = std::move(name); } + + /// @brief Get the name of the component + /// @return the name + const char *get_name() { return this->name_.c_str(); } + + /// @brief override the Component loop() + void loop() override; + + bool page1() { return page1_; } + + /// @brief Factory method to create a Register object + /// @param reg address of the register + /// @param channel channel associated with this register + /// @return a reference to WeikaiRegister + virtual WeikaiRegister ®(uint8_t reg, uint8_t channel) = 0; + + protected: + friend class WeikaiChannel; + + /// @brief Get the priority of the component + /// @return the priority + /// @details The priority is set below setup_priority::BUS because we use + /// the spi/i2c busses (which has a priority of BUS) to communicate and the WeiKai + /// therefore it is seen by our client almost as if it was a bus. + float get_setup_priority() const override { return setup_priority::BUS - 0.1F; } + + friend class WeikaiGPIOPin; + /// Helper method to read the value of a pin. + bool read_pin_val_(uint8_t pin); + + /// Helper method to write the value of a pin. + void write_pin_val_(uint8_t pin, bool value); + + /// Helper method to set the pin mode of a pin. + void set_pin_direction_(uint8_t pin, gpio::Flags flags); + +#ifdef TEST_COMPONENT + /// @defgroup test_ Test component information + /// @brief Contains information about the auto-tests of the component + /// @{ + void test_gpio_input_(); + void test_gpio_output_(); + /// @} +#endif + + uint8_t pin_config_{0x00}; ///< pin config mask: 1 means OUTPUT, 0 means INPUT + uint8_t output_state_{0x00}; ///< output state: 1 means HIGH, 0 means LOW + uint8_t input_state_{0x00}; ///< input pin states: 1 means HIGH, 0 means LOW + uint32_t crystal_; ///< crystal value; + int test_mode_; ///< test mode value (0 -> no tests) + bool page1_{false}; ///< set to true when in "page1 mode" + std::vector children_{}; ///< the list of WeikaiChannel UART children + std::string name_; ///< name of entity +}; + +/////////////////////////////////////////////////////////////////////////////// +/// @brief Helper class to expose a WeiKai family IO pin as an internal GPIO pin. +/////////////////////////////////////////////////////////////////////////////// +class WeikaiGPIOPin : public GPIOPin { + public: + void set_parent(WeikaiComponent *parent) { this->parent_ = parent; } + 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; } + + void setup() override; + std::string dump_summary() 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_); } + + protected: + WeikaiComponent *parent_{nullptr}; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief The WeikaiChannel class is used to implement all the virtual methods of the ESPHome +/// uart::UARTComponent virtual class. This class is common to the different members of the Weikai +/// components family and therefore avoid code duplication. +/////////////////////////////////////////////////////////////////////////////////////////////////// +class WeikaiChannel : public uart::UARTComponent { + public: + /// @brief We belongs to this WeikaiComponent + /// @param parent pointer to the component we belongs to + void set_parent(WeikaiComponent *parent) { + this->parent_ = parent; + this->parent_->children_.push_back(this); // add ourself to the list (vector) + } + + /// @brief Sets the channel number + /// @param channel number + void set_channel(uint8_t channel) { this->channel_ = channel; } + + /// @brief The name as generated by the Python code generator + /// @param name of the channel + void set_channel_name(std::string name) { this->name_ = std::move(name); } + + /// @brief Get the channel name + /// @return the name + const char *get_channel_name() { return this->name_.c_str(); } + + /// @brief Setup the channel + void virtual setup_channel(); + + /// @brief dump channel information + void virtual dump_channel(); + + /// @brief Factory method to create a WeikaiRegister proxy object + /// @param reg address of the register + /// @return a reference to WeikaiRegister + WeikaiRegister ®(uint8_t reg) { return this->parent_->reg(reg, channel_); } + + // + // we implements/overrides the virtual class from UARTComponent + // + + /// @brief Writes a specified number of bytes to a serial port + /// @param buffer pointer to the buffer + /// @param length number of bytes to write + /// @details This method sends 'length' characters from the buffer to the serial line. Unfortunately (unlike the + /// Arduino equivalent) this method does not return any flag and therefore it is not possible to know if any/all bytes + /// have been transmitted correctly. Another problem is that it is not possible to know ahead of time how many bytes + /// we can safely send as there is no tx_available() method provided! To avoid overrun when using the write method you + /// can use the flush() method to wait until the transmit fifo is empty. + /// @n Typical usage could be: + /// @code + /// // ... + /// uint8_t buffer[128]; + /// // ... + /// write_array(&buffer, length); + /// flush(); + /// // ... + /// @endcode + void write_array(const uint8_t *buffer, size_t length) override; + + /// @brief Reads a specified number of bytes from a serial port + /// @param buffer buffer to store the bytes + /// @param length number of bytes to read + /// @return true if succeed, false otherwise + /// @details Typical usage: + /// @code + /// // ... + /// auto length = available(); + /// uint8_t buffer[128]; + /// if (length > 0) { + /// auto status = read_array(&buffer, length) + /// // test status ... + /// } + /// @endcode + bool read_array(uint8_t *buffer, size_t length) override; + + /// @brief Reads the first byte in FIFO without removing it + /// @param buffer pointer to the byte + /// @return true if succeed reading one byte, false if no character available + /// @details This method returns the next byte from receiving buffer without removing it from the internal fifo. It + /// returns true if a character is available and has been read, false otherwise.\n + bool peek_byte(uint8_t *buffer) override; + + /// @brief Returns the number of bytes in the receive buffer + /// @return the number of bytes available in the receiver fifo + int available() override; + + /// @brief Flush the output fifo. + /// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data + /// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore + /// we wait until all bytes are gone with a timeout of 100 ms + void flush() override; + + protected: + friend class WeikaiComponent; + + /// @brief this cannot happen with external uart therefore we do nothing + void check_logger_conflict() override {} + + /// @brief reset the weikai internal FIFO + void reset_fifo_(); + + /// @brief set the line parameters + void set_line_param_(); + + /// @brief set the baud rate + void set_baudrate_(); + + /// @brief Returns the number of bytes in the receive fifo + /// @return the number of bytes in the fifo + size_t rx_in_fifo_(); + + /// @brief Returns the number of bytes in the transmit fifo + /// @return the number of bytes in the fifo + size_t tx_in_fifo_(); + + /// @brief test if transmit buffer is not empty in the status register (optimization) + /// @return true if not emptygroup test_ + bool tx_fifo_is_not_empty_(); + + /// @brief transfer bytes from the weikai internal FIFO to the buffer (if any) + /// @return number of bytes transferred + size_t xfer_fifo_to_buffer_(); + + /// @brief check if channel is alive + /// @return true if OK + bool virtual check_channel_down(); + +#ifdef TEST_COMPONENT + /// @ingroup test_ + /// @{ + + /// @brief Test the write_array() method + /// @param message to display + void uart_send_test_(char *message); + + /// @brief Test the read_array() method + /// @param message to display + /// @return true if success + bool uart_receive_test_(char *message); + /// @} +#endif + + /// @brief the buffer where we store temporarily the bytes received + WKRingBuffer receive_buffer_; + WeikaiComponent *parent_; ///< our WK2168component parent + uint8_t channel_; ///< our Channel number + uint8_t data_; ///< a one byte buffer for register read storage + std::string name_; ///< name of the entity +}; + +} // namespace weikai +} // namespace esphome diff --git a/esphome/components/weikai/wk_reg_def.h b/esphome/components/weikai/wk_reg_def.h new file mode 100644 index 000000000000..f3c90b196a65 --- /dev/null +++ b/esphome/components/weikai/wk_reg_def.h @@ -0,0 +1,304 @@ +/// @file wk_reg_def.h +/// @author DrCoolZic +/// @brief WeiKai component family - registers' definition +/// @date Last Modified: 2024/02/18 15:49:18 +#pragma once + +namespace esphome { +namespace weikai { + +//////////////////////////////////////////////////////////////////////////////////////// +/// Definition of the WeiKai registers +//////////////////////////////////////////////////////////////////////////////////////// + +/// @defgroup wk2168_gr_ WeiKai Global Registers +/// This section groups all **Global Registers**: these registers are global to the +/// the WeiKai chip (i.e. independent of the UART channel used) +/// @note only registers and parameters used have been fully documented +/// @{ + +/// @brief Global Control Register - 00 0000 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | M0 | M1 | RSV | C4EN | C3EN | C2EN | C1EN | name +/// ------------------------------------------------------------------------- +/// | R | R | R | R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_GENA = 0x00; +/// @brief Channel 4 enable clock (0: disable, 1: enable) +constexpr uint8_t GENA_C4EN = 1 << 3; +/// @brief Channel 3 enable clock (0: disable, 1: enable) +constexpr uint8_t GENA_C3EN = 1 << 2; +/// @brief Channel 2 enable clock (0: disable, 1: enable) +constexpr uint8_t GENA_C2EN = 1 << 1; +/// @brief Channel 1 enable clock (0: disable, 1: enable) +constexpr uint8_t GENA_C1EN = 1 << 0; + +/// @brief Global Reset Register - 00 0001 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | C4SLEEP| C3SLEEP| C2SLEEP| C1SLEEP| C4RST | C3RST | C2RST | C1RST | name +/// ------------------------------------------------------------------------- +/// | R | R | R | R | W1/R0 | W1/R0 | W1/R0 | W1/R0 | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_GRST = 0x01; +/// @brief Channel 4 soft reset (0: not reset, 1: reset) +constexpr uint8_t GRST_C4RST = 1 << 3; +/// @brief Channel 3 soft reset (0: not reset, 1: reset) +constexpr uint8_t GRST_C3RST = 1 << 2; +/// @brief Channel 2 soft reset (0: not reset, 1: reset) +constexpr uint8_t GRST_C2RST = 1 << 1; +/// @brief Channel 1 soft reset (0: not reset, 1: reset) +constexpr uint8_t GRST_C1RST = 1 << 0; + +/// @brief Global Master channel control register (not used) - 000010 +constexpr uint8_t WKREG_GMUT = 0x02; + +/// Global interrupt register (not used) - 01 0000 +constexpr uint8_t WKREG_GIER = 0x10; + +/// Global interrupt flag register (not used) 01 0001 +constexpr uint8_t WKREG_GIFR = 0x11; + +/// @brief Global GPIO direction register - 10 0001 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | PD7 | PD6 | PD5 | PD4 | PD3 | PD2 | PD1 | PD0 | name +/// ------------------------------------------------------------------------- +/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_GPDIR = 0x21; + +/// @brief Global GPIO data register - 11 0001 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | PV7 | PV6 | PV5 | PV4 | PV3 | PV2 | PV1 | PV0 | name +/// ------------------------------------------------------------------------- +/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_GPDAT = 0x31; + +/// @} +/// @defgroup WeiKai_cr_ WeiKai Channel Registers +/// @brief Definition of the register linked to a particular channel +/// @details This topic groups all the **Channel Registers**: these registers are specific +/// to the a specific channel i.e. each channel has its own set of registers +/// @note only registers and parameters used have been documented +/// @{ + +/// @defgroup cr_p0 Channel registers when SPAGE=0 +/// @brief Definition of the register linked to a particular channel when SPAGE=0 +/// @details The channel registers are further splitted into two groups. +/// This first group is defined when the Global register WKREG_SPAGE is 0 +/// @{ + +/// @brief Global Page register c0/c1 0011 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | RSV | PAGE | name +/// ------------------------------------------------------------------------- +/// | R | R | R | R | R | R | R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_SPAGE = 0x03; + +/// @brief Serial Control Register - c0/c1 0100 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | RSV | SLPEN | TXEN | RXEN | name +/// ------------------------------------------------------------------------- +/// | R | R | R | R | R | R/W | R/W | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_SCR = 0x04; +/// @brief transmission control (0: enable, 1: disable) +constexpr uint8_t SCR_TXEN = 1 << 1; +/// @brief receiving control (0: enable, 1: disable) +constexpr uint8_t SCR_RXEN = 1 << 0; + +/// @brief Line Configuration Register - c0/c1 0101 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | RSV | BREAK | IREN | PAEN | PARITY | STPL | name +/// ------------------------------------------------------------------------- +/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_LCR = 0x05; +/// @brief Parity enable (0: no check, 1: check) +constexpr uint8_t LCR_PAEN = 1 << 3; +/// @brief Parity force 0 +constexpr uint8_t LCR_PAR_F0 = 0 << 1; +/// @brief Parity odd +constexpr uint8_t LCR_PAR_ODD = 1 << 1; +/// @brief Parity even +constexpr uint8_t LCR_PAR_EVEN = 2 << 1; +/// @brief Parity force 1 +constexpr uint8_t LCR_PAR_F1 = 3 << 1; +/// @brief Stop length (0: 1 bit, 1: 2 bits) +constexpr uint8_t LCR_STPL = 1 << 0; + +/// @brief FIFO Control Register - c0/c1 0110 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | TFTRIG | RFTRIG | TFEN | RFEN | TFRST | RFRST | name +/// ------------------------------------------------------------------------- +/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_FCR = 0x06; +/// @brief Transmitter FIFO enable +constexpr uint8_t FCR_TFEN = 1 << 3; +/// @brief Receiver FIFO enable +constexpr uint8_t FCR_RFEN = 1 << 2; +/// @brief Transmitter FIFO reset +constexpr uint8_t FCR_TFRST = 1 << 1; +/// @brief Receiver FIFO reset +constexpr uint8_t FCR_RFRST = 1 << 0; + +/// @brief Serial Interrupt Enable Register (not used) - c0/c1 0111 +constexpr uint8_t WKREG_SIER = 0x07; + +/// @brief Serial Interrupt Flag Register (not used) - c0/c1 1000 +constexpr uint8_t WKREG_SIFR = 0x08; + +/// @brief Transmitter FIFO Count - c0/c1 1001 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | +/// ------------------------------------------------------------------------- +/// | NUMBER OF DATA IN TRANSMITTER FIFO | +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_TFCNT = 0x09; + +/// @brief Receiver FIFO count - c0/c1 1010 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | +/// ------------------------------------------------------------------------- +/// | NUMBER OF DATA IN RECEIVER FIFO | +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_RFCNT = 0x0A; + +/// @brief FIFO Status Register - c0/c1 1011 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit +/// ------------------------------------------------------------------------- +/// | RFOE | RFLB | RFFE | RFPE | RFDAT | TFDAT | TFFULL | TBUSY | name +/// ------------------------------------------------------------------------- +/// | R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type +/// ------------------------------------------------------------------------- +/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset +/// ------------------------------------------------------------------------- +/// @endcode +/// @warning The received buffer can hold 256 bytes. However, as the RFCNT reg +/// is 8 bits, if we have 256 byte in the register this is reported as 0 ! Therefore +/// RFCNT=0 can indicate that there are 0 **or** 256 bytes in the buffer. If we +/// have RFDAT = 1 and RFCNT = 0 it should be interpreted as 256 bytes in the FIFO. +/// @note Note that in case of overflow the RFOE goes to one **but** as soon as you read +/// the FSR this bit is cleared. Therefore Overflow can be read only once. +/// @n The same problem applies to the transmit buffer but here we have to check the +/// TFFULL flag. So if TFFULL is set and TFCNT is 0 this should be interpreted as 256 +constexpr uint8_t WKREG_FSR = 0x0B; +/// @brief Receiver FIFO Overflow Error (0: no OE, 1: OE) +constexpr uint8_t FSR_RFOE = 1 << 7; +/// @brief Receiver FIFO Line Break (0: no LB, 1: LB) +constexpr uint8_t FSR_RFLB = 1 << 6; +/// @brief Receiver FIFO Frame Error (0: no FE, 1: FE) +constexpr uint8_t FSR_RFFE = 1 << 5; +/// @brief Receiver Parity Error (0: no PE, 1: PE) +constexpr uint8_t FSR_RFPE = 1 << 4; +/// @brief Receiver FIFO count (0: empty, 1: not empty) +constexpr uint8_t FSR_RFDAT = 1 << 3; +/// @brief Transmitter FIFO count (0: empty, 1: not empty) +constexpr uint8_t FSR_TFDAT = 1 << 2; +/// @brief Transmitter FIFO full (0: not full, 1: full) +constexpr uint8_t FSR_TFFULL = 1 << 1; +/// @brief Transmitter busy (0 nothing to transmit, 1: transmitter busy sending) +constexpr uint8_t FSR_TBUSY = 1 << 0; + +/// @brief Line Status Register (not used - using FIFO) +constexpr uint8_t WKREG_LSR = 0x0C; + +/// @brief FIFO Data Register (not used - does not seems to work) +constexpr uint8_t WKREG_FDAT = 0x0D; + +/// @} +/// @defgroup cr_p1 Channel registers for SPAGE=1 +/// @brief Definition of the register linked to a particular channel when SPAGE=1 +/// @details The channel registers are further splitted into two groups. +/// This second group is defined when the Global register WKREG_SPAGE is 1 +/// @{ + +/// @brief Baud rate configuration register: high byte - c0/c1 0100 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | +/// ------------------------------------------------------------------------- +/// | High byte of the baud rate | +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_BRH = 0x04; + +/// @brief Baud rate configuration register: low byte - c0/c1 0101 +/// @details @code +/// ------------------------------------------------------------------------- +/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | +/// ------------------------------------------------------------------------- +/// | Low byte of the baud rate | +/// ------------------------------------------------------------------------- +/// @endcode +constexpr uint8_t WKREG_BRL = 0x05; + +/// @brief Baud rate configuration register decimal part - c0/c1 0110 +constexpr uint8_t WKREG_BRD = 0x06; + +/// @brief Receive FIFO Interrupt trigger configuration (not used) - c0/c1 0111 +constexpr uint8_t WKREG_RFI = 0x07; + +/// @brief Transmit FIFO interrupt trigger configuration (not used) - c0/c1 1000 +constexpr uint8_t WKREG_TFI = 0x08; + +/// @} +/// @} +} // namespace weikai +} // namespace esphome diff --git a/esphome/components/weikai_i2c/__init__.py b/esphome/components/weikai_i2c/__init__.py new file mode 100644 index 000000000000..2c6a421a0a37 --- /dev/null +++ b/esphome/components/weikai_i2c/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@DrCoolZic"] diff --git a/esphome/components/weikai_i2c/weikai_i2c.cpp b/esphome/components/weikai_i2c/weikai_i2c.cpp new file mode 100644 index 000000000000..9d0c9446ecb0 --- /dev/null +++ b/esphome/components/weikai_i2c/weikai_i2c.cpp @@ -0,0 +1,177 @@ +/// @file weikai_i2c.cpp +/// @brief WeiKai component family - classes implementation +/// @date Last Modified: 2024/04/06 14:43:31 +/// @details The classes declared in this file can be used by the Weikai family + +#include "weikai_i2c.h" + +namespace esphome { +namespace weikai_i2c { +static const char *const TAG = "weikai_i2c"; + +/// @brief Display a buffer in hexadecimal format (32 hex values / line). +void print_buffer(const uint8_t *data, size_t length) { + char hex_buffer[100]; + hex_buffer[(3 * 32) + 1] = 0; + for (size_t i = 0; i < length; i++) { + snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]); + if (i % 32 == 31) { + ESP_LOGVV(TAG, " %s", hex_buffer); + } + } + if (length % 32) { + // null terminate if incomplete line + hex_buffer[3 * (length % 32) + 2] = 0; + ESP_LOGVV(TAG, " %s", hex_buffer); + } +} + +static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER", + "SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"}; +static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL", + "TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"}; +using namespace weikai; +// method to print a register value as text: used in the log messages ... +const char *reg_to_str(int reg, bool page1) { + if (reg == WKREG_GPDAT) { + return "GPDAT"; + } else if (reg == WKREG_GPDIR) { + return "GPDIR"; + } else { + return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F]; + } +} +enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO + +/// @brief Computes the I²C bus's address used to access the component +/// @param base_address the base address of the component - set by the A1 A0 pins +/// @param channel (0-3) the UART channel +/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo +/// @return the i2c address to use +inline uint8_t i2c_address(uint8_t base_address, uint8_t channel, RegType fifo) { + // the address of the device is: + // +----+----+----+----+----+----+----+----+ + // | 0 | A1 | A0 | 1 | 0 | C1 | C0 | F | + // +----+----+----+----+----+----+----+----+ + // where: + // - A1,A0 is the address read from A1,A0 switch + // - C1,C0 is the channel number (in practice only 00 or 01) + // - F is: 0 when accessing register, one when accessing FIFO + uint8_t const addr = base_address | channel << 1 | fifo << 0; + return addr; +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiRegisterI2C methods +/////////////////////////////////////////////////////////////////////////////// +uint8_t WeikaiRegisterI2C::read_reg() const { + uint8_t value = 0x00; + WeikaiComponentI2C *comp_i2c = static_cast(this->comp_); + uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG); + comp_i2c->set_i2c_address(address); + auto error = comp_i2c->read_register(this->register_, &value, 1); + if (error == i2c::NO_ERROR) { + this->comp_->status_clear_warning(); + ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address, + reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value); + } else { // error + this->comp_->status_set_warning(); + ESP_LOGE(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address, + reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value); + } + return value; +} + +void WeikaiRegisterI2C::read_fifo(uint8_t *data, size_t length) const { + WeikaiComponentI2C *comp_i2c = static_cast(this->comp_); + uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO); + comp_i2c->set_i2c_address(address); + auto error = comp_i2c->read(data, length); + if (error == i2c::NO_ERROR) { + this->comp_->status_clear_warning(); +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_, + (int) error, length); + print_buffer(data, length); +#endif + } else { // error + this->comp_->status_set_warning(); + ESP_LOGE(TAG, "WeikaiRegisterI2C::read_fifo() @%02X reg=N/A ch=%d I2C_code:%d len=%d buf=%02X...", address, + this->channel_, (int) error, length, data[0]); + } +} + +void WeikaiRegisterI2C::write_reg(uint8_t value) { + WeikaiComponentI2C *comp_i2c = static_cast(this->comp_); + uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG); // update the i2c bus + comp_i2c->set_i2c_address(address); + auto error = comp_i2c->write_register(this->register_, &value, 1); + if (error == i2c::NO_ERROR) { + this->comp_->status_clear_warning(); + ESP_LOGVV(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%02X", address, + reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value); + } else { // error + this->comp_->status_set_warning(); + ESP_LOGE(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%d", address, + reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value); + } +} + +void WeikaiRegisterI2C::write_fifo(uint8_t *data, size_t length) { + WeikaiComponentI2C *comp_i2c = static_cast(this->comp_); + uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO); // set fifo flag + comp_i2c->set_i2c_address(address); + auto error = comp_i2c->write(data, length); + if (error == i2c::NO_ERROR) { + this->comp_->status_clear_warning(); +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + ESP_LOGVV(TAG, "WK2168Reg::write_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_, + (int) error, length); + print_buffer(data, length); +#endif + } else { // error + this->comp_->status_set_warning(); + ESP_LOGE(TAG, "WK2168Reg::write_fifo() @%02X reg=N/A, ch=%d I2C_code:%d len=%d, buf=%02X...", address, + this->channel_, (int) error, length, data[0]); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiComponentI2C methods +/////////////////////////////////////////////////////////////////////////////// +void WeikaiComponentI2C::setup() { + // before any manipulation we store the address to base_address_ for future use + this->base_address_ = this->address_; + ESP_LOGCONFIG(TAG, "Setting up wk2168_i2c: %s with %d UARTs at @%02X ...", this->get_name(), this->children_.size(), + this->base_address_); + + // enable all channels + this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN; + // reset all channels + this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST; + // initialize the spage register to page 0 + this->reg(WKREG_SPAGE, 0) = 0; + this->page1_ = false; + + // we setup our children channels + for (auto *child : this->children_) { + child->setup_channel(); + } +} + +void WeikaiComponentI2C::dump_config() { + ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size()); + ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32, this->crystal_); + if (test_mode_) + ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_); + ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE); + this->address_ = this->base_address_; // we restore the base_address before display (less confusing) + LOG_I2C_DEVICE(this); + + for (auto *child : this->children_) { + child->dump_channel(); + } +} + +} // namespace weikai_i2c +} // namespace esphome diff --git a/esphome/components/weikai_i2c/weikai_i2c.h b/esphome/components/weikai_i2c/weikai_i2c.h new file mode 100644 index 000000000000..0da9ed9cdec5 --- /dev/null +++ b/esphome/components/weikai_i2c/weikai_i2c.h @@ -0,0 +1,61 @@ +/// @file weikai_i2c.h +/// @author DrCoolZic +/// @brief WeiKai component family - classes declaration +/// @date Last Modified: 2024/03/01 13:31:57 +/// @details The classes declared in this file can be used by the Weikai family +/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c + +#pragma once +#include +#include +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/weikai/weikai.h" + +namespace esphome { +namespace weikai_i2c { + +class WeikaiComponentI2C; + +// using namespace weikai; +//////////////////////////////////////////////////////////////////////////////////// +/// @brief WeikaiRegisterI2C objects acts as proxies to access remote register through an I2C Bus +//////////////////////////////////////////////////////////////////////////////////// +class WeikaiRegisterI2C : public weikai::WeikaiRegister { + public: + uint8_t read_reg() const override; + void write_reg(uint8_t value) override; + void read_fifo(uint8_t *data, size_t length) const override; + void write_fifo(uint8_t *data, size_t length) override; + + protected: + friend WeikaiComponentI2C; + WeikaiRegisterI2C(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel) + : weikai::WeikaiRegister(comp, reg, channel) {} +}; + +//////////////////////////////////////////////////////////////////////////////////// +/// @brief The WeikaiComponentI2C class stores the information to the WeiKai component +/// connected through an I2C bus. +//////////////////////////////////////////////////////////////////////////////////// +class WeikaiComponentI2C : public weikai::WeikaiComponent, public i2c::I2CDevice { + public: + weikai::WeikaiRegister ®(uint8_t reg, uint8_t channel) override { + reg_i2c_.register_ = reg; + reg_i2c_.channel_ = channel; + return reg_i2c_; + } + + // + // override Component methods + // + void setup() override; + void dump_config() override; + + uint8_t base_address_; ///< base address of I2C device + WeikaiRegisterI2C reg_i2c_{this, 0, 0}; ///< init to this component +}; + +} // namespace weikai_i2c +} // namespace esphome diff --git a/esphome/components/weikai_spi/__init__.py b/esphome/components/weikai_spi/__init__.py new file mode 100644 index 000000000000..2c6a421a0a37 --- /dev/null +++ b/esphome/components/weikai_spi/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@DrCoolZic"] diff --git a/esphome/components/weikai_spi/weikai_spi.cpp b/esphome/components/weikai_spi/weikai_spi.cpp new file mode 100644 index 000000000000..22c63bbd2d6b --- /dev/null +++ b/esphome/components/weikai_spi/weikai_spi.cpp @@ -0,0 +1,189 @@ +/// @file weikai_spi.cpp +/// @brief WeiKai component family - classes implementation +/// @date Last Modified: 2024/04/06 14:46:09 +/// @details The classes declared in this file can be used by the Weikai family + +#include "weikai_spi.h" + +namespace esphome { +namespace weikai_spi { +using namespace weikai; +static const char *const TAG = "weikai_spi"; + +/// @brief convert an int to binary representation as C++ std::string +/// @param val integer to convert +/// @return a std::string +inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); } +/// Convert std::string to C string +#define I2S2CS(val) (i2s(val).c_str()) + +/// @brief measure the time elapsed between two calls +/// @param last_time time of the previous call +/// @return the elapsed time in microseconds +uint32_t elapsed_ms(uint32_t &last_time) { + uint32_t e = millis() - last_time; + last_time = millis(); + return e; +}; + +/// @brief Converts the parity enum value to a C string +/// @param parity enum +/// @return the string +const char *p2s(uart::UARTParityOptions parity) { + using namespace uart; + switch (parity) { + case UART_CONFIG_PARITY_NONE: + return "NONE"; + case UART_CONFIG_PARITY_EVEN: + return "EVEN"; + case UART_CONFIG_PARITY_ODD: + return "ODD"; + default: + return "UNKNOWN"; + } +} + +/// @brief Display a buffer in hexadecimal format (32 hex values / line). +void print_buffer(const uint8_t *data, size_t length) { + char hex_buffer[100]; + hex_buffer[(3 * 32) + 1] = 0; + for (size_t i = 0; i < length; i++) { + snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]); + if (i % 32 == 31) { + ESP_LOGVV(TAG, " %s", hex_buffer); + } + } + if (length % 32) { + // null terminate if incomplete line + hex_buffer[3 * (length % 32) + 2] = 0; + ESP_LOGVV(TAG, " %s", hex_buffer); + } +} + +static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER", + "SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"}; +static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL", + "TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"}; + +// method to print a register value as text: used in the log messages ... +const char *reg_to_str(int reg, bool page1) { + if (reg == WKREG_GPDAT) { + return "GPDAT"; + } else if (reg == WKREG_GPDIR) { + return "GPDIR"; + } else { + return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F]; + } +} + +enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO +enum CmdType { WRITE_CMD = 0, READ_CMD = 1 }; ///< Read or Write transfer + +/// @brief Computes the SPI command byte +/// @param transfer_type read or write command +/// @param reg (0-15) the address of the register +/// @param channel (0-3) the UART channel +/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo +/// @return the spi command byte +/// @details +/// +------+------+------+------+------+------+------+------+ +/// | FIFO | R/W | C1-C0 | A3-A0 | +/// +------+------+-------------+---------------------------+ +/// FIFO: 0 = register, 1 = FIFO +/// R/W: 0 = write, 1 = read +/// C1-C0: Channel (0-1) +/// A3-A0: Address (0-F) +inline static uint8_t cmd_byte(RegType fifo, CmdType transfer_type, uint8_t channel, uint8_t reg) { + return (fifo << 7 | transfer_type << 6 | channel << 4 | reg << 0); +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiRegisterSPI methods +/////////////////////////////////////////////////////////////////////////////// +uint8_t WeikaiRegisterSPI::read_reg() const { + auto *spi_comp = static_cast(this->comp_); + uint8_t cmd = cmd_byte(REG, READ_CMD, this->channel_, this->register_); + spi_comp->enable(); + spi_comp->write_byte(cmd); + uint8_t val = spi_comp->read_byte(); + spi_comp->disable(); + ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(cmd), cmd, + reg_to_str(this->register_, this->comp_->page1()), this->channel_, val); + return val; +} + +void WeikaiRegisterSPI::read_fifo(uint8_t *data, size_t length) const { + auto *spi_comp = static_cast(this->comp_); + uint8_t cmd = cmd_byte(FIFO, READ_CMD, this->channel_, this->register_); + spi_comp->enable(); + spi_comp->write_byte(cmd); + spi_comp->read_array(data, length); + spi_comp->disable(); +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_, + length); + print_buffer(data, length); +#endif +} + +void WeikaiRegisterSPI::write_reg(uint8_t value) { + auto *spi_comp = static_cast(this->comp_); + uint8_t buf[2]{cmd_byte(REG, WRITE_CMD, this->channel_, this->register_), value}; + spi_comp->enable(); + spi_comp->write_array(buf, 2); + spi_comp->disable(); + ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(buf[0]), buf[0], + reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]); +} + +void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) { + auto *spi_comp = static_cast(this->comp_); + uint8_t cmd = cmd_byte(FIFO, WRITE_CMD, this->channel_, this->register_); + spi_comp->enable(); + spi_comp->write_byte(cmd); + spi_comp->write_array(data, length); + spi_comp->disable(); + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_, + length); + print_buffer(data, length); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +// The WeikaiComponentSPI methods +/////////////////////////////////////////////////////////////////////////////// +void WeikaiComponentSPI::setup() { + using namespace weikai; + ESP_LOGCONFIG(TAG, "Setting up wk2168_spi: %s with %d UARTs...", this->get_name(), this->children_.size()); + this->spi_setup(); + // enable all channels + this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN; + // reset all channels + this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST; + // initialize the spage register to page 0 + this->reg(WKREG_SPAGE, 0) = 0; + this->page1_ = false; + + // we setup our children channels + for (auto *child : this->children_) { + child->setup_channel(); + } +} + +void WeikaiComponentSPI::dump_config() { + ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size()); + ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32 "", this->crystal_); + if (test_mode_) + ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_); + ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE); + LOG_PIN(" CS Pin: ", this->cs_); + + for (auto *child : this->children_) { + child->dump_channel(); + } +} + +} // namespace weikai_spi +} // namespace esphome diff --git a/esphome/components/weikai_spi/weikai_spi.h b/esphome/components/weikai_spi/weikai_spi.h new file mode 100644 index 000000000000..dd0dc8d49566 --- /dev/null +++ b/esphome/components/weikai_spi/weikai_spi.h @@ -0,0 +1,54 @@ +/// @file weikai.h +/// @author DrCoolZic +/// @brief WeiKai component family - classes declaration +/// @date Last Modified: 2024/02/29 17:20:32 +/// @details The classes declared in this file can be used by the Weikai family +/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi, + +#pragma once +#include +#include +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/weikai/weikai.h" + +namespace esphome { +namespace weikai_spi { +//////////////////////////////////////////////////////////////////////////////////// +/// @brief WeikaiRegisterSPI objects acts as proxies to access remote register through an SPI Bus +//////////////////////////////////////////////////////////////////////////////////// +class WeikaiRegisterSPI : public weikai::WeikaiRegister { + public: + WeikaiRegisterSPI(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel) + : weikai::WeikaiRegister(comp, reg, channel) {} + + uint8_t read_reg() const override; + void write_reg(uint8_t value) override; + void read_fifo(uint8_t *data, size_t length) const override; + void write_fifo(uint8_t *data, size_t length) override; +}; + +//////////////////////////////////////////////////////////////////////////////////// +/// @brief The WeikaiComponentSPI class stores the information to the WeiKai component +/// connected through an SPI bus. +//////////////////////////////////////////////////////////////////////////////////// +class WeikaiComponentSPI : public weikai::WeikaiComponent, + public spi::SPIDevice { + public: + weikai::WeikaiRegister ®(uint8_t reg, uint8_t channel) override { + reg_spi_.register_ = reg; + reg_spi_.channel_ = channel; + return reg_spi_; + } + + void setup() override; + void dump_config() override; + + protected: + WeikaiRegisterSPI reg_spi_{this, 0, 0}; ///< init to this component +}; + +} // namespace weikai_spi +} // namespace esphome diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index 225423b4db70..1ac32f30daf3 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -33,6 +33,7 @@ const uint8_t WHIRLPOOL_SWING_MASK = 128; const uint8_t WHIRLPOOL_POWER = 0x04; void WhirlpoolClimate::transmit_state() { + this->last_transmit_time_ = millis(); // setting the time of the last transmission. uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0}; remote_state[0] = 0x83; remote_state[1] = 0x06; @@ -149,6 +150,12 @@ void WhirlpoolClimate::transmit_state() { } bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { + // Check if the esp isn't currently transmitting. + if (millis() - this->last_transmit_time_ < 500) { + ESP_LOGV(TAG, "Blocked receive because of current trasmittion"); + return false; + } + // Validate header if (!data.expect_item(WHIRLPOOL_HEADER_MARK, WHIRLPOOL_HEADER_SPACE)) { ESP_LOGV(TAG, "Header fail"); diff --git a/esphome/components/whirlpool/whirlpool.h b/esphome/components/whirlpool/whirlpool.h index 7f31894df9b5..907a21225ced 100644 --- a/esphome/components/whirlpool/whirlpool.h +++ b/esphome/components/whirlpool/whirlpool.h @@ -47,6 +47,8 @@ class WhirlpoolClimate : public climate_ir::ClimateIR { void transmit_state() override; /// Handle received IR Buffer bool on_receive(remote_base::RemoteReceiveData data) override; + /// Set the time of the last transmission. + int32_t last_transmit_time_{}; bool send_swing_cmd_{false}; Model model_; diff --git a/esphome/components/whynter/climate.py b/esphome/components/whynter/climate.py index b9dc5868bc4a..1d576344e622 100644 --- a/esphome/components/whynter/climate.py +++ b/esphome/components/whynter/climate.py @@ -1,14 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate_ir -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT AUTO_LOAD = ["climate_ir"] whynter_ns = cg.esphome_ns.namespace("whynter") Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR) -CONF_USE_FAHRENHEIT = "use_fahrenheit" CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { diff --git a/esphome/components/whynter/whynter.cpp b/esphome/components/whynter/whynter.cpp index 190bf70acc5c..9f57fdb84302 100644 --- a/esphome/components/whynter/whynter.cpp +++ b/esphome/components/whynter/whynter.cpp @@ -118,7 +118,7 @@ bool Whynter::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Decoded 0x%02X", remote_state); + ESP_LOGD(TAG, "Decoded 0x%02" PRIX32, remote_state); if ((remote_state & COMMAND_MASK) != COMMAND_CODE) return false; if ((remote_state & POWER_MASK) != POWER_MASK) { @@ -156,7 +156,7 @@ bool Whynter::on_receive(remote_base::RemoteReceiveData data) { } void Whynter::transmit_(uint32_t value) { - ESP_LOGD(TAG, "Sending whynter code: 0x%02X", value); + ESP_LOGD(TAG, "Sending Whynter code: 0x%02" PRIX32, value); auto transmit = this->transmitter_->transmit(); auto *data = transmit.get_data(); diff --git a/esphome/components/whynter/whynter.h b/esphome/components/whynter/whynter.h index 939583ebfb29..8273c21e4b83 100644 --- a/esphome/components/whynter/whynter.h +++ b/esphome/components/whynter/whynter.h @@ -2,6 +2,8 @@ #include "esphome/components/climate_ir/climate_ir.h" +#include + namespace esphome { namespace whynter { diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 1baffcbfcc1a..e0a17e9a2ac1 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -11,6 +11,7 @@ CONF_DNS2, CONF_DOMAIN, CONF_ENABLE_BTM, + CONF_ENABLE_ON_BOOT, CONF_ENABLE_RRM, CONF_FAST_CONNECT, CONF_GATEWAY, @@ -32,14 +33,18 @@ CONF_KEY, CONF_USERNAME, CONF_EAP, + CONF_ON_CONNECT, + CONF_ON_DISCONNECT, ) from esphome.core import CORE, HexInt, coroutine_with_priority -from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const from esphome.components.network import IPAddress from . import wpa2_eap AUTO_LOAD = ["network"] +NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2] + wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") ManualIP = wifi_ns.struct("ManualIP") @@ -148,6 +153,13 @@ def wifi_network_ap(value): ) +def validate_variant(_): + if CORE.is_esp32: + variant = get_esp32_variant() + if variant in NO_WIFI_VARIANTS: + raise cv.Invalid(f"{variant} does not support WiFi") + + def final_validate(config): has_sta = bool(config.get(CONF_NETWORKS, True)) has_ap = CONF_AP in config @@ -199,6 +211,7 @@ def final_validate_power_esp32_ble(value): extra=cv.ALLOW_EXTRA, ), final_validate, + validate_variant, ) @@ -256,7 +269,6 @@ def _validate(config): CONF_OUTPUT_POWER = "output_power" CONF_PASSIVE_SCAN = "passive_scan" -CONF_ENABLE_ON_BOOT = "enable_on_boot" CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -296,6 +308,10 @@ def _validate(config): "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( + single=True + ), } ), _validate, @@ -387,6 +403,10 @@ def add_sta(ap, network): lambda ap: cg.add(var.set_ap(wifi_network(conf, ap, ip_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: + add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False) + add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) @@ -415,9 +435,21 @@ def add_sta(ap, network): cg.add_define("USE_WIFI") - # Register at end for OTA safe mode + # must register before OTA safe mode check await cg.register_component(var, config) + await cg.past_safe_mode() + + if on_connect_config := config.get(CONF_ON_CONNECT): + await automation.build_automation( + var.get_connect_trigger(), [], on_connect_config + ) + + if on_disconnect_config := config.get(CONF_ON_DISCONNECT): + await automation.build_automation( + var.get_disconnect_trigger(), [], on_disconnect_config + ) + @automation.register_condition("wifi.connected", WiFiConnectedCondition, cv.Schema({})) async def wifi_connected_to_code(config, condition_id, template_arg, args): diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ff621291f067..7e245d3e8653 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -8,16 +8,16 @@ #include #endif -#include #include -#include "lwip/err.h" +#include #include "lwip/dns.h" +#include "lwip/err.h" +#include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/hal.h" #include "esphome/core/util.h" -#include "esphome/core/application.h" #ifdef USE_CAPTIVE_PORTAL #include "esphome/components/captive_portal/captive_portal.h" @@ -40,6 +40,9 @@ void WiFiComponent::setup() { if (this->enable_on_boot_) { this->start(); } else { +#ifdef USE_ESP32 + esp_netif_init(); +#endif this->state_ = WIFI_COMPONENT_STATE_DISABLED; } } @@ -52,6 +55,9 @@ void WiFiComponent::start() { uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); + if (this->fast_connect_) { + this->fast_connect_pref_ = global_preferences->make_preference(hash, false); + } SavedWifiSettings save{}; if (this->pref_.load(&save)) { @@ -75,10 +81,12 @@ void WiFiComponent::start() { if (this->fast_connect_) { this->selected_ap_ = this->sta_[0]; + this->load_fast_connect_settings_(); this->start_connecting(this->selected_ap_, false); } else { this->start_scanning(); } +#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_)) { @@ -91,9 +99,10 @@ void WiFiComponent::start() { captive_portal::global_captive_portal->start(); } #endif +#endif // USE_WIFI_AP } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) { + if (!this->has_sta() && esp32_improv::global_improv_component != nullptr) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); } @@ -106,11 +115,20 @@ void WiFiComponent::loop() { const uint32_t now = millis(); if (this->has_sta()) { + if (this->is_connected() != this->handled_connected_state_) { + if (this->handled_connected_state_) { + this->disconnect_trigger_->trigger(); + } else { + this->connect_trigger_->trigger(); + } + this->handled_connected_state_ = this->is_connected(); + } + switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(); if (millis() - this->action_started_ > 5000) { - if (this->fast_connect_) { + if (this->fast_connect_ || this->retry_hidden_) { this->start_connecting(this->sta_[0], false); } else { this->start_scanning(); @@ -148,8 +166,9 @@ void WiFiComponent::loop() { return; } +#ifdef USE_WIFI_AP if (this->has_ap() && !this->ap_setup_) { - if (now - this->last_connected_ > this->ap_timeout_) { + if (this->ap_timeout_ != 0 && (now - this->last_connected_ > this->ap_timeout_)) { ESP_LOGI(TAG, "Starting fallback AP!"); this->setup_ap_config_(); #ifdef USE_CAPTIVE_PORTAL @@ -158,10 +177,11 @@ void WiFiComponent::loop() { #endif } } +#endif // USE_WIFI_AP #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) { - if (!this->is_connected()) { + if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active()) { + if (now - this->last_connected_ > esp32_improv::global_improv_component->get_wifi_timeout()) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); } @@ -187,11 +207,15 @@ void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; } void WiFiComponent::set_rrm(bool rrm) { this->rrm_ = rrm; } #endif -network::IPAddress WiFiComponent::get_ip_address() { +network::IPAddresses WiFiComponent::get_ip_addresses() { if (this->has_sta()) - return this->wifi_sta_ip(); + return this->wifi_sta_ip_addresses(); + +#ifdef USE_WIFI_AP if (this->has_ap()) - return this->wifi_soft_ap_ip(); + return {this->wifi_soft_ap_ip()}; +#endif // USE_WIFI_AP + return {}; } network::IPAddress WiFiComponent::get_dns_address(int num) { @@ -206,6 +230,8 @@ 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; } + +#ifdef USE_WIFI_AP void WiFiComponent::setup_ap_config_() { this->wifi_mode_({}, true); @@ -243,13 +269,16 @@ void WiFiComponent::setup_ap_config_() { } } -float WiFiComponent::get_loop_priority() const { - return 10.0f; // before other loop components -} void WiFiComponent::set_ap(const WiFiAP &ap) { this->ap_ = ap; this->has_ap_ = true; } +#endif // USE_WIFI_AP + +float WiFiComponent::get_loop_priority() const { + return 10.0f; // before other loop components +} + void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); } void WiFiComponent::set_sta(const WiFiAP &ap) { this->clear_sta(); @@ -377,8 +406,16 @@ void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); + if (this->is_disabled()) { + ESP_LOGCONFIG(TAG, " WiFi is disabled!"); + return; + } ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); - ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); + for (auto &ip : wifi_sta_ip_addresses()) { + if (ip.is_set()) { + ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str()); + } + } ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); @@ -554,6 +591,9 @@ void WiFiComponent::check_connecting_finished() { return; } + // We won't retry hidden networks unless a reconnect fails more than three times again + this->retry_hidden_ = false; + ESP_LOGI(TAG, "WiFi Connected!"); this->print_connect_params_(); @@ -574,6 +614,11 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + + if (this->fast_connect_) { + this->save_fast_connect_settings_(); + } + return; } @@ -619,12 +664,20 @@ void WiFiComponent::retry_connect() { delay(10); if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_() && - (this->num_retried_ > 5 || this->error_from_callback_)) { - // If retry failed for more than 5 times, let's restart STA - ESP_LOGW(TAG, "Restarting WiFi adapter..."); - this->wifi_mode_(false, {}); - delay(100); // NOLINT - this->num_retried_ = 0; + (this->num_retried_ > 3 || this->error_from_callback_)) { + if (this->num_retried_ > 5) { + // If retry failed for more than 5 times, let's restart STA + ESP_LOGW(TAG, "Restarting WiFi adapter..."); + this->wifi_mode_(false, {}); + delay(100); // NOLINT + this->num_retried_ = 0; + this->retry_hidden_ = false; + } else { + // Try hidden networks after 3 failed retries + ESP_LOGD(TAG, "Retrying with hidden networks..."); + this->retry_hidden_ = true; + this->num_retried_++; + } } else { this->num_retried_++; } @@ -641,7 +694,7 @@ void WiFiComponent::retry_connect() { } bool WiFiComponent::can_proceed() { - if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED) { + if (!this->has_sta() || this->state_ == WIFI_COMPONENT_STATE_DISABLED || this->ap_setup_) { return true; } return this->is_connected(); @@ -675,6 +728,35 @@ bool WiFiComponent::is_esp32_improv_active_() { #endif } +void WiFiComponent::load_fast_connect_settings_() { + SavedWifiFastConnectSettings fast_connect_save{}; + + if (this->fast_connect_pref_.load(&fast_connect_save)) { + bssid_t bssid{}; + std::copy(fast_connect_save.bssid, fast_connect_save.bssid + 6, bssid.begin()); + this->selected_ap_.set_bssid(bssid); + this->selected_ap_.set_channel(fast_connect_save.channel); + + ESP_LOGD(TAG, "Loaded saved fast_connect wifi settings"); + } +} + +void WiFiComponent::save_fast_connect_settings_() { + bssid_t bssid = wifi_bssid(); + uint8_t channel = wifi_channel_(); + + 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; + + this->fast_connect_pref_.save(&fast_connect_save); + + ESP_LOGD(TAG, "Saved fast_connect wifi settings"); + } +} + 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; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index b418a5b353e4..133fa2970c62 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -1,18 +1,18 @@ #pragma once +#include "esphome/components/network/ip_address.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/automation.h" #include "esphome/core/helpers.h" -#include "esphome/components/network/ip_address.h" #include #include #ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#include #include +#include +#include #endif #ifdef USE_LIBRETINY @@ -48,6 +48,11 @@ struct SavedWifiSettings { char password[65]; } PACKED; // NOLINT +struct SavedWifiFastConnectSettings { + uint8_t bssid[6]; + uint8_t channel; +} PACKED; // NOLINT + enum WiFiComponentState { /** Nothing has been initialized yet. Internal AP, if configured, is disabled at this point. */ WIFI_COMPONENT_STATE_OFF = 0, @@ -194,6 +199,7 @@ class WiFiComponent : public Component { void add_sta(const WiFiAP &ap); void clear_sta(); +#ifdef USE_WIFI_AP /** Setup an Access Point that should be created if no connection to a station can be made. * * This can also be used without set_sta(). Then the AP will always be active. @@ -203,6 +209,7 @@ class WiFiComponent : public Component { */ void set_ap(const WiFiAP &ap); WiFiAP get_ap() { return this->ap_; } +#endif // USE_WIFI_AP void enable(); void disable(); @@ -251,7 +258,7 @@ class WiFiComponent : public Component { #endif network::IPAddress get_dns_address(int num); - network::IPAddress get_ip_address(); + network::IPAddresses get_ip_addresses(); std::string get_use_address() const; void set_use_address(const std::string &use_address); @@ -286,7 +293,7 @@ class WiFiComponent : public Component { }); } - network::IPAddress wifi_sta_ip(); + network::IPAddresses wifi_sta_ip_addresses(); std::string wifi_ssid(); bssid_t wifi_bssid(); @@ -294,9 +301,16 @@ class WiFiComponent : public Component { void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } + Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; + Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; + protected: static std::string format_mac_addr(const uint8_t mac[6]); + +#ifdef USE_WIFI_AP void setup_ap_config_(); +#endif // USE_WIFI_AP + void print_connect_params_(); void wifi_loop_(); @@ -310,8 +324,12 @@ class WiFiComponent : public Component { void wifi_pre_setup_(); WiFiSTAConnectStatus wifi_sta_connect_status_(); bool wifi_scan_start_(bool passive); + +#ifdef USE_WIFI_AP bool wifi_ap_ip_config_(optional manual_ip); bool wifi_start_ap_(const WiFiAP &ap); +#endif // USE_WIFI_AP + bool wifi_disconnect_(); int32_t wifi_channel_(); network::IPAddress wifi_subnet_mask_(); @@ -321,6 +339,9 @@ class WiFiComponent : public Component { bool is_captive_portal_active_(); bool is_esp32_improv_active_(); + void load_fast_connect_settings_(); + void save_fast_connect_settings_(); + #ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); @@ -350,10 +371,12 @@ class WiFiComponent : public Component { std::vector sta_priorities_; WiFiAP selected_ap_; bool fast_connect_{false}; + bool retry_hidden_{false}; bool has_ap_{false}; WiFiAP ap_; WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; + bool handled_connected_state_{false}; uint32_t action_started_; uint8_t num_retried_{0}; uint32_t last_connected_{0}; @@ -367,12 +390,20 @@ class WiFiComponent : public Component { optional output_power_; bool passive_scan_{false}; ESPPreferenceObject pref_; + ESPPreferenceObject fast_connect_pref_; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT bool btm_{false}; bool rrm_{false}; #endif bool enable_on_boot_; + bool got_ipv4_address_{false}; +#if USE_NETWORK_IPV6 + uint8_t num_ipv6_addresses_{0}; +#endif /* USE_NETWORK_IPV6 */ + + Trigger<> *connect_trigger_{new Trigger<>()}; + Trigger<> *disconnect_trigger_{new Trigger<>()}; }; extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 95f4e2ce9220..44d77b4eedb8 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -113,9 +113,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { tcpip_adapter_ip_info_t info; memset(&info, 0, sizeof(info)); - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; esp_err_t dhcp_stop_ret = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); if (dhcp_stop_ret != ESP_OK && dhcp_stop_ret != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { @@ -128,35 +128,52 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } ip_addr_t dns; +// TODO: is this needed? #if LWIP_IPV6 dns.type = IPADDR_TYPE_V4; -#endif - if (uint32_t(manual_ip->dns1) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns1); -#else - dns.addr = static_cast(manual_ip->dns1); -#endif +#endif /* LWIP_IPV6 */ + if (manual_ip->dns1.is_set()) { + dns = manual_ip->dns1; dns_setserver(0, &dns); } - if (uint32_t(manual_ip->dns2) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns2); -#else - dns.addr = static_cast(manual_ip->dns2); -#endif + if (manual_ip->dns2.is_set()) { + dns = manual_ip->dns2; dns_setserver(1, &dns); } return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; + network::IPAddresses addresses; tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); - return {ip.ip.addr}; + esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + ip6_addr_t ipv6; + err = tcpip_adapter_get_ip6_global(TCPIP_ADAPTER_IF_STA, &ipv6); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip6_gobal failed: %s", esp_err_to_name(err)); + } else { + addresses[1] = network::IPAddress(&ipv6); + } + err = tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_STA, &ipv6); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip6_linklocal failed: %s", esp_err_to_name(err)); + } else { + addresses[2] = network::IPAddress(&ipv6); + } +#endif /* USE_NETWORK_IPV6 */ + + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { @@ -447,9 +464,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 this->set_timeout(100, [] { WiFi.enableIpV6(); }); -#endif /* LWIP_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ break; } @@ -501,18 +518,26 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str()); + this->got_ipv4_address_ = true; +#if USE_NETWORK_IPV6 + s_sta_connecting = this->num_ipv6_addresses_ < USE_NETWORK_MIN_IPV6_ADDR_COUNT; +#else s_sta_connecting = false; +#endif /* USE_NETWORK_IPV6 */ break; } -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { auto it = info.got_ip6.ip6_info; ESP_LOGV(TAG, "Got IPv6 address=" IPV6STR, IPV62STR(it.ip)); + this->num_ipv6_addresses_++; + s_sta_connecting = !(this->got_ipv4_address_ & (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT)); break; } -#endif /* LWIP_IPV6 */ +#endif /* USE_NETWORK_IPV6 */ case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); + this->got_ipv4_address_ = false; break; } case ESPHOME_EVENT_ID_WIFI_AP_START: { @@ -557,14 +582,14 @@ void WiFiComponent::wifi_pre_setup_() { } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { auto status = WiFiClass::status(); - if (status == WL_CONNECTED) { - return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { + 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; + } else if (status == WL_CONNECTED) { + return WiFiSTAConnectStatus::CONNECTED; } return WiFiSTAConnectStatus::IDLE; } @@ -604,6 +629,8 @@ void WiFiComponent::wifi_scan_done_callback_() { WiFi.scanDelete(); this->scan_done_ = true; } + +#ifdef USE_WIFI_AP bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { esp_err_t err; @@ -614,13 +641,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { tcpip_adapter_ip_info_t info; memset(&info, 0, sizeof(info)); if (manual_ip.has_value()) { - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; } else { - info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); + info.ip = network::IPAddress(192, 168, 4, 1); + info.gw = network::IPAddress(192, 168, 4, 1); + info.netmask = network::IPAddress(255, 255, 255, 0); } tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); @@ -638,12 +665,12 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { dhcps_lease_t lease; lease.enable = true; - network::IPAddress start_address = info.ip.addr; - start_address[3] += 99; - lease.start_ip.addr = static_cast(start_address); + 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()); - start_address[3] += 100; - lease.end_ip.addr = static_cast(start_address); + start_address += 100; + lease.end_ip = start_address; ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); @@ -661,6 +688,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return true; } + bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { // enable AP if (!this->wifi_mode_({}, true)) @@ -679,7 +707,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); } conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; @@ -699,11 +727,14 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } + network::IPAddress WiFiComponent::wifi_soft_ap_ip() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); - return {ip.ip.addr}; + return network::IPAddress(&ip.ip); } +#endif // USE_WIFI_AP + bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } bssid_t WiFiComponent::wifi_bssid() { @@ -718,9 +749,9 @@ bssid_t WiFiComponent::wifi_bssid() { std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int32_t WiFiComponent::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)}; } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return network::IPAddress(WiFi.subnetMask()); } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return network::IPAddress(WiFi.gatewayIP()); } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(WiFi.dnsIP(num)); } void WiFiComponent::wifi_loop_() {} } // namespace wifi diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index a28aa8b85872..838250972bdd 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -17,15 +17,18 @@ extern "C" { #include "lwip/dhcp.h" #include "lwip/init.h" // LWIP_VERSION_ #include "lwip/apps/sntp.h" -#if LWIP_IPV6 #include "lwip/netif.h" // struct netif -#endif +#include #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" +#if USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0) +#include +#include "ESP8266WiFiAP.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) #define wifi_softap_set_dhcps_offer_option(offer, mode) dhcpSoftAP.set_dhcps_offer_option(offer, mode) #endif +#endif } #include "esphome/core/helpers.h" @@ -97,6 +100,7 @@ bool WiFiComponent::wifi_apply_power_save_() { power_save = NONE_SLEEP_T; break; } + wifi_fpm_auto_sleep_set_in_null_mode(1); return wifi_set_sleep_type(power_save); } @@ -145,9 +149,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { #endif struct ip_info info {}; - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; if (dhcp_status == DHCP_STARTED) { bool dhcp_stop_ret = wifi_station_dhcpc_stop(); @@ -163,12 +167,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } ip_addr_t dns; - if (uint32_t(manual_ip->dns1) != 0) { - dns.addr = static_cast(manual_ip->dns1); + if (manual_ip->dns1.is_set()) { + dns = manual_ip->dns1; dns_setserver(0, &dns); } - if (uint32_t(manual_ip->dns2) != 0) { - dns.addr = static_cast(manual_ip->dns2); + if (manual_ip->dns2.is_set()) { + dns = manual_ip->dns2; dns_setserver(1, &dns); } @@ -183,12 +187,15 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return ret; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; - struct ip_info ip {}; - wifi_get_ip_info(STATION_IF, &ip); - return {ip.ip.addr}; + network::IPAddresses addresses; + uint8_t index = 0; + for (auto &addr : addrList) { + addresses[index++] = addr.ipFromNetifNum(); + } + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { const std::string &hostname = App.get_name(); @@ -325,6 +332,21 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } +#if USE_NETWORK_IPV6 + bool connected = false; + while (!connected) { + uint8_t ipv6_addr_count = 0; + for (auto addr : addrList) { + ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); + if (addr.isV6()) { + ipv6_addr_count++; + } + } + delay(500); // NOLINT + connected = (ipv6_addr_count >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); + } +#endif /* USE_NETWORK_IPV6 */ + if (ap.get_channel().has_value()) { ret = wifi_set_channel(*ap.get_channel()); if (!ret) { @@ -674,6 +696,8 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { } this->scan_done_ = true; } + +#ifdef USE_WIFI_AP bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { // enable AP if (!this->wifi_mode_({}, true)) @@ -681,13 +705,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { struct ip_info info {}; if (manual_ip.has_value()) { - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; } else { - info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); + info.ip = network::IPAddress(192, 168, 4, 1); + info.gw = network::IPAddress(192, 168, 4, 1); + info.netmask = network::IPAddress(255, 255, 255, 0); } if (wifi_softap_dhcps_status() == DHCP_STARTED) { @@ -701,18 +725,18 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(3, 1, 0) dhcpSoftAP.begin(&info); #endif struct dhcps_lease lease {}; lease.enable = true; - network::IPAddress start_address = info.ip.addr; - start_address[3] += 99; - lease.start_ip.addr = static_cast(start_address); + 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()); - start_address[3] += 100; - lease.end_ip.addr = static_cast(start_address); + start_address += 100; + lease.end_ip = start_address; ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); if (!wifi_softap_set_dhcps_lease(&lease)) { ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!"); @@ -725,12 +749,16 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) + ESP8266WiFiClass::softAPDhcpServer().setRouter(true); // send ROUTER option with netif's gateway IP +#else uint8_t mode = 1; // bit0, 1 enables router information from ESP8266 SoftAP DHCP server. if (!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) { ESP_LOGV(TAG, "wifi_softap_set_dhcps_offer_option failed!"); return false; } +#endif if (!wifi_softap_dhcps_start()) { ESP_LOGV(TAG, "Starting SoftAP DHCPS failed!"); @@ -739,6 +767,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return true; } + bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { // enable AP if (!this->wifi_mode_({}, true)) @@ -776,11 +805,14 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } + network::IPAddress WiFiComponent::wifi_soft_ap_ip() { struct ip_info ip {}; wifi_get_ip_info(SOFTAP_IF, &ip); - return {ip.ip.addr}; + return network::IPAddress(&ip.ip); } +#endif // USE_WIFI_AP + bssid_t WiFiComponent::wifi_bssid() { bssid_t bssid{}; uint8_t *raw_bssid = WiFi.BSSID(); @@ -793,9 +825,9 @@ bssid_t WiFiComponent::wifi_bssid() { std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int32_t WiFiComponent::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)}; } +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()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; } void WiFiComponent::wifi_loop_() {} } // namespace wifi diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 9041679ccf86..ebb2fb92eab4 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -17,7 +17,11 @@ #ifdef USE_WIFI_WPA2_EAP #include #endif + +#ifdef USE_WIFI_AP #include "dhcpserver/dhcpserver.h" +#endif // USE_WIFI_AP + #include "lwip/err.h" #include "lwip/dns.h" @@ -35,15 +39,16 @@ static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -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_sta_got_ip = 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) -static bool s_wifi_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#ifdef USE_WIFI_AP +static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#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) +static bool s_wifi_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) struct IDFWiFiEvent { esp_event_base_t event_base; @@ -58,9 +63,9 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 ip_event_got_ip6_t ip_got_ip6; -#endif +#endif /* USE_NETWORK_IPV6 */ ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -84,7 +89,7 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); -#if LWIP_IPV6 +#if USE_NETWORK_IPV6 } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); #endif @@ -159,7 +164,11 @@ void WiFiComponent::wifi_pre_setup_() { } s_sta_netif = esp_netif_create_default_wifi_sta(); + +#ifdef USE_WIFI_AP s_ap_netif = esp_netif_create_default_wifi_ap(); +#endif // USE_WIFI_AP + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // cfg.nvs_enable = false; err = esp_wifi_init(&cfg); @@ -399,7 +408,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // may be called from wifi_station_connect s_sta_connecting = true; s_sta_connected = false; - s_sta_got_ip = false; s_sta_connect_error = false; s_sta_connect_not_found = false; @@ -437,9 +445,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; err = esp_netif_dhcpc_stop(s_sta_netif); if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err)); @@ -452,28 +460,41 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } esp_netif_dns_info_t dns; - if (uint32_t(manual_ip->dns1) != 0) { - dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns1); + if (manual_ip->dns1.is_set()) { + dns.ip = manual_ip->dns1; esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns); } - if (uint32_t(manual_ip->dns2) != 0) { - dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns2); + if (manual_ip->dns2.is_set()) { + dns.ip = manual_ip->dns2; esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns); } return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; + network::IPAddresses addresses; esp_netif_ip_info_t ip; esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); if (err != ESP_OK) { ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); - return false; - } - return {ip.ip.addr}; + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(s_sta_netif, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + addresses[i + 1] = network::IPAddress(&if_ip6s[i]); + } +#endif /* USE_NETWORK_IPV6 */ + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { @@ -508,7 +529,7 @@ 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 +#endif /* LWIP_IPV6 */ const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -645,22 +666,23 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; -#if LWIP_IPV6_AUTOCONFIG +#if USE_NETWORK_IPV6 esp_netif_create_ip6_linklocal(s_sta_netif); -#endif +#endif /* USE_NETWORK_IPV6 */ ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); - s_sta_got_ip = true; + this->got_ipv4_address_ = true; -#if LWIP_IPV6 +#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, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); -#endif + this->num_ipv6_addresses_++; +#endif /* USE_NETWORK_IPV6 */ } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); - s_sta_got_ip = false; + this->got_ipv4_address_ = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_SCAN_DONE) { const auto &it = data->data.sta_scan_done; @@ -673,6 +695,11 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { return; } + if (it.number == 0) { + // no results + return; + } + uint16_t number = it.number; std::vector records(number); err = esp_wifi_scan_get_ap_records(&number, records.data()); @@ -719,8 +746,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - if (s_sta_connected && s_sta_got_ip) { + if (s_sta_connected && this->got_ipv4_address_) { +#if USE_NETWORK_IPV6 + if (this->num_ipv6_addresses_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT) { + return WiFiSTAConnectStatus::CONNECTED; + } +#else return WiFiSTAConnectStatus::CONNECTED; +#endif /* USE_NETWORK_IPV6 */ } if (s_sta_connect_error) { return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; @@ -760,6 +793,8 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { scan_done_ = false; return true; } + +#ifdef USE_WIFI_AP bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { esp_err_t err; @@ -769,13 +804,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { esp_netif_ip_info_t info; if (manual_ip.has_value()) { - info.ip.addr = static_cast(manual_ip->static_ip); - info.gw.addr = static_cast(manual_ip->gateway); - info.netmask.addr = static_cast(manual_ip->subnet); + info.ip = manual_ip->static_ip; + info.gw = manual_ip->gateway; + info.netmask = manual_ip->subnet; } else { - info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); + info.ip = network::IPAddress(192, 168, 4, 1); + info.gw = network::IPAddress(192, 168, 4, 1); + info.netmask = network::IPAddress(255, 255, 255, 0); } err = esp_netif_dhcpc_stop(s_sta_netif); @@ -792,12 +827,12 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { dhcps_lease_t lease; lease.enable = true; - network::IPAddress start_address = info.ip.addr; - start_address[3] += 99; - lease.start_ip.addr = static_cast(start_address); + 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()); - start_address[3] += 100; - lease.end_ip.addr = static_cast(start_address); + start_address += 100; + lease.end_ip = start_address; ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); err = esp_netif_dhcps_option(s_sta_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); @@ -815,6 +850,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return true; } + bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { // enable AP if (!this->wifi_mode_({}, true)) @@ -852,10 +888,12 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } +#endif // USE_WIFI_AP + network::IPAddress WiFiComponent::wifi_soft_ap_ip() { esp_netif_ip_info_t ip; esp_netif_get_ip_info(s_sta_netif, &ip); - return {ip.ip.addr}; + return network::IPAddress(&ip.ip); } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } @@ -907,7 +945,7 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() { ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); return {}; } - return {ip.netmask.addr}; + return network::IPAddress(&ip.netmask); } network::IPAddress WiFiComponent::wifi_gateway_ip_() { esp_netif_ip_info_t ip; @@ -916,15 +954,11 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); return {}; } - return {ip.gw.addr}; + return network::IPAddress(&ip.gw); } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { const ip_addr_t *dns_ip = dns_getserver(num); -#if LWIP_IPV6 - return {dns_ip->u_addr.ip4.addr}; -#else - return {dns_ip->addr}; -#endif + return network::IPAddress(dns_ip); } } // namespace wifi diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index abad5aca9c05..f6b0fb2699e3 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -76,14 +76,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } - WiFi.config(static_cast(manual_ip->static_ip), static_cast(manual_ip->gateway), - static_cast(manual_ip->subnet), static_cast(manual_ip->dns1), - static_cast(manual_ip->dns2)); + WiFi.config(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet, manual_ip->dns1, manual_ip->dns2); return true; } -network::IPAddress WiFiComponent::wifi_sta_ip() { +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; return {WiFi.localIP()}; @@ -414,18 +412,20 @@ void WiFiComponent::wifi_scan_done_callback_() { WiFi.scanDelete(); this->scan_done_ = true; } + +#ifdef USE_WIFI_AP bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { // enable AP if (!this->wifi_mode_({}, true)) return false; if (manual_ip.has_value()) { - return WiFi.softAPConfig(static_cast(manual_ip->static_ip), static_cast(manual_ip->gateway), - static_cast(manual_ip->subnet)); + return WiFi.softAPConfig(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet); } else { return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); } } + bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { // enable AP if (!this->wifi_mode_({}, true)) @@ -441,7 +441,10 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { 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()); } + network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } +#endif // USE_WIFI_AP + bool WiFiComponent::wifi_disconnect_() { return WiFi.disconnect(); } bssid_t WiFiComponent::wifi_bssid() { diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 489ebc36993f..2bb1af5489ef 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -6,6 +6,7 @@ #include "lwip/dns.h" #include "lwip/err.h" #include "lwip/netif.h" +#include #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -70,11 +71,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } - IPAddress ip_address = IPAddress(manual_ip->static_ip); - IPAddress gateway = IPAddress(manual_ip->gateway); - IPAddress subnet = IPAddress(manual_ip->subnet); + IPAddress ip_address = manual_ip->static_ip; + IPAddress gateway = manual_ip->gateway; + IPAddress subnet = manual_ip->subnet; - IPAddress dns = IPAddress(manual_ip->dns1); + IPAddress dns = manual_ip->dns1; WiFi.config(ip_address, dns, gateway, subnet); return true; @@ -138,6 +139,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { return true; } +#ifdef USE_WIFI_AP bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { // TODO: return false; @@ -151,7 +153,9 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } -network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.localIP()}; } + +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {(const ip_addr_t *) WiFi.localIP()}; } +#endif // USE_WIFI_AP bool WiFiComponent::wifi_disconnect_() { int err = cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA); @@ -170,12 +174,19 @@ std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } -network::IPAddress WiFiComponent::wifi_sta_ip() { return {WiFi.localIP()}; } -network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } -network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { + network::IPAddresses addresses; + uint8_t index = 0; + for (auto addr : addrList) { + addresses[index++] = addr.ipFromNetifNum(); + } + return addresses; +} +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()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { const ip_addr_t *dns_ip = dns_getserver(num); - return {dns_ip->addr}; + return network::IPAddress(dns_ip); } void WiFiComponent::wifi_loop_() { diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 3cb60e617518..3985dfef18bb 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -3,6 +3,7 @@ The cryptography package is loaded lazily in the functions so that it doesn't crash if it's not installed. """ + import logging from pathlib import Path diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 659fd08db1c4..75513712dd08 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -37,7 +37,16 @@ { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ) + .extend(cv.polling_component_schema("1s")) + .extend( + { + cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + for x in range(5) + } + ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("60s")), @@ -65,9 +74,15 @@ async def setup_conf(config, key): async def to_code(config): - await setup_conf(config, CONF_IP_ADDRESS) await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) await setup_conf(config, CONF_SCAN_RESULTS) await setup_conf(config, CONF_DNS_ADDRESS) + 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]) + for x in range(5): + if sensor_conf := conf.get(f"address_{x}"): + sens = await text_sensor.new_text_sensor(sensor_conf) + cg.add(wifi_info.add_ip_sensors(x, sens)) diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 35ce108c86c7..0f31a57cc5db 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/wifi/wifi_component.h" +#include namespace esphome { namespace wifi_info { @@ -10,32 +11,38 @@ namespace wifi_info { class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - auto ip = wifi::global_wifi_component->wifi_sta_ip(); - if (ip != this->last_ip_) { - this->last_ip_ = ip; - this->publish_state(ip.str()); + 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; } std::string unique_id() override { return get_mac_address() + "-wifiinfo-ip"; } void dump_config() override; + void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } protected: - network::IPAddress last_ip_; + network::IPAddresses last_ips_; + std::array ip_sensors_; }; class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSensor { public: void update() override { - std::string dns_results; - auto dns_one = wifi::global_wifi_component->get_dns_address(0); auto dns_two = wifi::global_wifi_component->get_dns_address(1); - dns_results += "DNS1: "; - dns_results += dns_one.str(); - dns_results += " DNS2: "; - dns_results += dns_two.str(); + std::string dns_results = dns_one.str() + " " + dns_two.str(); if (dns_results != this->last_results_) { this->last_results_ = dns_results; diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py new file mode 100644 index 000000000000..7612c7d96466 --- /dev/null +++ b/esphome/components/wireguard/__init__.py @@ -0,0 +1,171 @@ +import re +import ipaddress +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + CONF_ADDRESS, + CONF_REBOOT_TIMEOUT, +) +from esphome.components import time +from esphome.core import TimePeriod +from esphome import automation + +CONF_NETMASK = "netmask" +CONF_PRIVATE_KEY = "private_key" +CONF_PEER_ENDPOINT = "peer_endpoint" +CONF_PEER_PUBLIC_KEY = "peer_public_key" +CONF_PEER_PORT = "peer_port" +CONF_PEER_PRESHARED_KEY = "peer_preshared_key" +CONF_PEER_ALLOWED_IPS = "peer_allowed_ips" +CONF_PEER_PERSISTENT_KEEPALIVE = "peer_persistent_keepalive" +CONF_REQUIRE_CONNECTION_TO_PROCEED = "require_connection_to_proceed" + +CONF_WIREGUARD_ID = "wireguard_id" + +DEPENDENCIES = ["time"] +CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"] + +# The key validation regex has been described by Jason Donenfeld himself +# url: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html +_WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$") + +wireguard_ns = cg.esphome_ns.namespace("wireguard") +Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent) +WireguardPeerOnlineCondition = wireguard_ns.class_( + "WireguardPeerOnlineCondition", automation.Condition +) +WireguardEnabledCondition = wireguard_ns.class_( + "WireguardEnabledCondition", automation.Condition +) +WireguardEnableAction = wireguard_ns.class_("WireguardEnableAction", automation.Action) +WireguardDisableAction = wireguard_ns.class_( + "WireguardDisableAction", automation.Action +) + + +def _wireguard_key(value): + if _WG_KEY_REGEX.match(cv.string(value)) is not None: + return value + raise cv.Invalid(f"Invalid WireGuard key: {value}") + + +def _cidr_network(value): + try: + ipaddress.ip_network(value, strict=False) + except ValueError as err: + raise cv.Invalid(f"Invalid network in CIDR notation: {err}") + return value + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(Wireguard), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_ADDRESS): cv.ipv4, + cv.Optional(CONF_NETMASK, default="255.255.255.255"): cv.ipv4, + cv.Required(CONF_PRIVATE_KEY): _wireguard_key, + cv.Required(CONF_PEER_ENDPOINT): cv.string, + cv.Required(CONF_PEER_PUBLIC_KEY): _wireguard_key, + cv.Optional(CONF_PEER_PORT, default=51820): cv.port, + cv.Optional(CONF_PEER_PRESHARED_KEY): _wireguard_key, + cv.Optional(CONF_PEER_ALLOWED_IPS, default=["0.0.0.0/0"]): cv.ensure_list( + _cidr_network + ), + cv.Optional(CONF_PEER_PERSISTENT_KEEPALIVE, default="0s"): cv.All( + cv.positive_time_period_seconds, + cv.Range(max=TimePeriod(seconds=65535)), + ), + cv.Optional( + CONF_REBOOT_TIMEOUT, default="15min" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_REQUIRE_CONNECTION_TO_PROCEED, default=False): cv.boolean, + } +).extend(cv.polling_component_schema("10s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + cg.add(var.set_address(str(config[CONF_ADDRESS]))) + cg.add(var.set_netmask(str(config[CONF_NETMASK]))) + cg.add(var.set_private_key(config[CONF_PRIVATE_KEY])) + cg.add(var.set_peer_endpoint(config[CONF_PEER_ENDPOINT])) + cg.add(var.set_peer_public_key(config[CONF_PEER_PUBLIC_KEY])) + cg.add(var.set_peer_port(config[CONF_PEER_PORT])) + cg.add(var.set_keepalive(config[CONF_PEER_PERSISTENT_KEEPALIVE])) + cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) + + if CONF_PEER_PRESHARED_KEY in config: + cg.add(var.set_preshared_key(config[CONF_PEER_PRESHARED_KEY])) + + allowed_ips = list( + ipaddress.collapse_addresses( + [ + ipaddress.ip_network(ip, strict=False) + for ip in config[CONF_PEER_ALLOWED_IPS] + ] + ) + ) + + for ip in allowed_ips: + cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask))) + + cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID]))) + + if config[CONF_REQUIRE_CONNECTION_TO_PROCEED]: + cg.add(var.disable_auto_proceed()) + + # This flag is added here because the esp_wireguard library statically + # set the size of its allowed_ips list at compile time using this value; + # the '+1' modifier is relative to the device's own address that will + # be automatically added to the provided list. + cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}") + cg.add_library("droscy/esp_wireguard", "0.4.0") + + await cg.register_component(var, config) + + +@automation.register_condition( + "wireguard.peer_online", + WireguardPeerOnlineCondition, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_peer_up_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_condition( + "wireguard.enabled", + WireguardEnabledCondition, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_enabled_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "wireguard.enable", + WireguardEnableAction, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_enable_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( + "wireguard.disable", + WireguardDisableAction, + cv.Schema({cv.GenerateID(): cv.use_id(Wireguard)}), +) +async def wireguard_disable_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/wireguard/binary_sensor.py b/esphome/components/wireguard/binary_sensor.py new file mode 100644 index 000000000000..734455865903 --- /dev/null +++ b/esphome/components/wireguard/binary_sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_STATUS, + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +from . import CONF_WIREGUARD_ID, Wireguard + +CONF_ENABLED = "enabled" + +DEPENDENCIES = ["wireguard"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard), + cv.Optional(CONF_STATUS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + ), + cv.Optional(CONF_ENABLED): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_WIREGUARD_ID]) + + if status_config := config.get(CONF_STATUS): + sens = await binary_sensor.new_binary_sensor(status_config) + cg.add(parent.set_status_sensor(sens)) + + if enabled_config := config.get(CONF_ENABLED): + sens = await binary_sensor.new_binary_sensor(enabled_config) + cg.add(parent.set_enabled_sensor(sens)) diff --git a/esphome/components/wireguard/sensor.py b/esphome/components/wireguard/sensor.py new file mode 100644 index 000000000000..85703d24b36f --- /dev/null +++ b/esphome/components/wireguard/sensor.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_TIMESTAMP, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +from . import CONF_WIREGUARD_ID, Wireguard + +CONF_LATEST_HANDSHAKE = "latest_handshake" + +DEPENDENCIES = ["wireguard"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard), + cv.Optional(CONF_LATEST_HANDSHAKE): sensor.sensor_schema( + device_class=DEVICE_CLASS_TIMESTAMP, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_WIREGUARD_ID]) + + if latest_handshake_config := config.get(CONF_LATEST_HANDSHAKE): + sens = await sensor.new_sensor(latest_handshake_config) + cg.add(parent.set_handshake_sensor(sens)) diff --git a/esphome/components/wireguard/text_sensor.py b/esphome/components/wireguard/text_sensor.py new file mode 100644 index 000000000000..51614a1a2886 --- /dev/null +++ b/esphome/components/wireguard/text_sensor.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ADDRESS, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +from . import CONF_WIREGUARD_ID, Wireguard + +DEPENDENCIES = ["wireguard"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard), + cv.Optional(CONF_ADDRESS): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_WIREGUARD_ID]) + + if address_config := config.get(CONF_ADDRESS): + sens = await text_sensor.new_text_sensor(address_config) + cg.add(parent.set_address_sensor(sens)) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp new file mode 100644 index 000000000000..17ebc701e383 --- /dev/null +++ b/esphome/components/wireguard/wireguard.cpp @@ -0,0 +1,291 @@ +#include "wireguard.h" + +#include +#include +#include + +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/time.h" +#include "esphome/components/network/util.h" + +#include +#include + +namespace esphome { +namespace wireguard { + +static const char *const TAG = "wireguard"; + +/* + * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform + * because log messages in `Wireguard::update()` method fail. + */ +#define LOGMSG_PEER_STATUS "WireGuard remote peer is %s (latest handshake %s)" + +static const char *const LOGMSG_ONLINE = "online"; +static const char *const LOGMSG_OFFLINE = "offline"; + +void Wireguard::setup() { + ESP_LOGD(TAG, "initializing WireGuard..."); + + this->wg_config_.address = this->address_.c_str(); + this->wg_config_.private_key = this->private_key_.c_str(); + this->wg_config_.endpoint = this->peer_endpoint_.c_str(); + this->wg_config_.public_key = this->peer_public_key_.c_str(); + this->wg_config_.port = this->peer_port_; + this->wg_config_.netmask = this->netmask_.c_str(); + this->wg_config_.persistent_keepalive = this->keepalive_; + + if (this->preshared_key_.length() > 0) + this->wg_config_.preshared_key = this->preshared_key_.c_str(); + + this->publish_enabled_state(); + + this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_)); + + if (this->wg_initialized_ == ESP_OK) { + ESP_LOGI(TAG, "WireGuard initialized"); + this->wg_peer_offline_time_ = millis(); + this->srctime_->add_on_time_sync_callback(std::bind(&Wireguard::start_connection_, this)); + this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup + +#ifdef USE_TEXT_SENSOR + if (this->address_sensor_ != nullptr) { + this->address_sensor_->publish_state(this->address_); + } +#endif + } else { + ESP_LOGE(TAG, "cannot initialize WireGuard, error code %d", this->wg_initialized_); + this->mark_failed(); + } +} + +void Wireguard::loop() { + if (!this->enabled_) { + return; + } + + if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) { + ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard..."); + this->stop_connection_(); + } +} + +void Wireguard::update() { + bool peer_up = this->is_peer_up(); + time_t lhs = this->get_latest_handshake(); + bool lhs_updated = (lhs > this->latest_saved_handshake_); + + ESP_LOGV(TAG, "enabled=%d, connected=%d, peer_up=%d, handshake: current=%.0f latest=%.0f updated=%d", + (int) this->enabled_, (int) (this->wg_connected_ == ESP_OK), (int) peer_up, (double) lhs, + (double) this->latest_saved_handshake_, (int) lhs_updated); + + if (lhs_updated) { + this->latest_saved_handshake_ = lhs; + } + + std::string latest_handshake = + (this->latest_saved_handshake_ > 0) + ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z") + : "timestamp not available"; + + if (peer_up) { + if (this->wg_peer_offline_time_ != 0) { + ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str()); + this->wg_peer_offline_time_ = 0; + } else { + ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str()); + } + } else { + if (this->wg_peer_offline_time_ == 0) { + ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); + this->wg_peer_offline_time_ = millis(); + } else if (this->enabled_) { + ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); + this->start_connection_(); + } + + // check reboot timeout every time the peer is down + if (this->enabled_ && this->reboot_timeout_ > 0) { + if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) { + ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting..."); + App.reboot(); + } + } + } + +#ifdef USE_BINARY_SENSOR + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(peer_up); + } +#endif + +#ifdef USE_SENSOR + if (this->handshake_sensor_ != nullptr && lhs_updated) { + this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_); + } +#endif +} + +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_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + 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()); + } + ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_, + (this->keepalive_ > 0 ? "s" : " (DISABLED)")); + ESP_LOGCONFIG(TAG, " Reboot Timeout: %" PRIu32 "%s", (this->reboot_timeout_ / 1000), + (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)")); + // be careful: if proceed_allowed_ is true, require connection is false + ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES")); + LOG_UPDATE_INTERVAL(this); +} + +void Wireguard::on_shutdown() { this->stop_connection_(); } + +bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up() || !this->enabled_); } + +bool Wireguard::is_peer_up() const { + return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && + (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK); +} + +time_t Wireguard::get_latest_handshake() const { + time_t result; + if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) { + result = 0; + } + return result; +} + +void Wireguard::set_address(const std::string &address) { this->address_ = address; } +void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; } +void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; } +void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; } +void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; } +void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; } +void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; } + +void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) { + this->allowed_ips_.emplace_back(ip, netmask); +} + +void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; } +void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; } +void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; } + +#ifdef USE_BINARY_SENSOR +void Wireguard::set_status_sensor(binary_sensor::BinarySensor *sensor) { this->status_sensor_ = sensor; } +void Wireguard::set_enabled_sensor(binary_sensor::BinarySensor *sensor) { this->enabled_sensor_ = sensor; } +#endif + +#ifdef USE_SENSOR +void Wireguard::set_handshake_sensor(sensor::Sensor *sensor) { this->handshake_sensor_ = sensor; } +#endif + +#ifdef USE_TEXT_SENSOR +void Wireguard::set_address_sensor(text_sensor::TextSensor *sensor) { this->address_sensor_ = sensor; } +#endif + +void Wireguard::disable_auto_proceed() { this->proceed_allowed_ = false; } + +void Wireguard::enable() { + this->enabled_ = true; + ESP_LOGI(TAG, "WireGuard enabled"); + this->publish_enabled_state(); +} + +void Wireguard::disable() { + this->enabled_ = false; + this->defer(std::bind(&Wireguard::stop_connection_, this)); // defer to avoid blocking running loop + ESP_LOGI(TAG, "WireGuard disabled"); + this->publish_enabled_state(); +} + +void Wireguard::publish_enabled_state() { +#ifdef USE_BINARY_SENSOR + if (this->enabled_sensor_ != nullptr) { + this->enabled_sensor_->publish_state(this->enabled_); + } +#endif +} + +bool Wireguard::is_enabled() { return this->enabled_; } + +void Wireguard::start_connection_() { + if (!this->enabled_) { + ESP_LOGV(TAG, "WireGuard is disabled, cannot start connection"); + return; + } + + if (this->wg_initialized_ != ESP_OK) { + ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_); + return; + } + + if (!network::is_connected()) { + ESP_LOGD(TAG, "WireGuard is waiting for local network connection to be available"); + return; + } + + if (!this->srctime_->now().is_valid()) { + ESP_LOGD(TAG, "WireGuard is waiting for system time to be synchronized"); + return; + } + + if (this->wg_connected_ == ESP_OK) { + ESP_LOGV(TAG, "WireGuard connection already started"); + return; + } + + ESP_LOGD(TAG, "starting WireGuard connection..."); + this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_)); + + if (this->wg_connected_ == ESP_OK) { + ESP_LOGI(TAG, "WireGuard connection started"); + } else if (this->wg_connected_ == ESP_ERR_RETRY) { + ESP_LOGD(TAG, "WireGuard is waiting for endpoint IP address to be available"); + return; + } else { + ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_); + return; + } + + ESP_LOGD(TAG, "configuring WireGuard allowed IPs list..."); + bool allowed_ips_ok = true; + for (std::tuple ip : this->allowed_ips_) { + allowed_ips_ok &= + (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK); + } + + if (allowed_ips_ok) { + ESP_LOGD(TAG, "allowed IPs list configured correctly"); + } else { + ESP_LOGE(TAG, "cannot configure WireGuard allowed IPs list, aborting..."); + this->stop_connection_(); + this->mark_failed(); + } +} + +void Wireguard::stop_connection_() { + if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) { + ESP_LOGD(TAG, "stopping WireGuard connection..."); + esp_wireguard_disconnect(&(this->wg_ctx_)); + this->wg_connected_ = ESP_FAIL; + } +} + +std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); } + +} // namespace wireguard +} // namespace esphome diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h new file mode 100644 index 000000000000..a0e9e27a1b6c --- /dev/null +++ b/esphome/components/wireguard/wireguard.h @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include + +#include "esphome/core/component.h" +#include "esphome/components/time/real_time_clock.h" + +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif + +#include + +namespace esphome { +namespace wireguard { + +/// Main Wireguard component class. +class Wireguard : public PollingComponent { + public: + void setup() override; + void loop() override; + void update() override; + void dump_config() override; + void on_shutdown() override; + bool can_proceed() override; + + float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; } + + void set_address(const std::string &address); + void set_netmask(const std::string &netmask); + void set_private_key(const std::string &key); + void set_peer_endpoint(const std::string &endpoint); + void set_peer_public_key(const std::string &key); + void set_peer_port(uint16_t port); + void set_preshared_key(const std::string &key); + + void add_allowed_ip(const std::string &ip, const std::string &netmask); + + void set_keepalive(uint16_t seconds); + void set_reboot_timeout(uint32_t seconds); + void set_srctime(time::RealTimeClock *srctime); + +#ifdef USE_BINARY_SENSOR + void set_status_sensor(binary_sensor::BinarySensor *sensor); + void set_enabled_sensor(binary_sensor::BinarySensor *sensor); +#endif + +#ifdef USE_SENSOR + void set_handshake_sensor(sensor::Sensor *sensor); +#endif + +#ifdef USE_TEXT_SENSOR + void set_address_sensor(text_sensor::TextSensor *sensor); +#endif + + /// Block the setup step until peer is connected. + void disable_auto_proceed(); + + /// Enable the WireGuard component. + void enable(); + + /// Stop any running connection and disable the WireGuard component. + void disable(); + + /// Publish the enabled state if the enabled binary sensor is configured. + void publish_enabled_state(); + + /// Return if the WireGuard component is or is not enabled. + bool is_enabled(); + + bool is_peer_up() const; + time_t get_latest_handshake() const; + + protected: + std::string address_; + std::string netmask_; + std::string private_key_; + std::string peer_endpoint_; + std::string peer_public_key_; + std::string preshared_key_; + + std::vector> allowed_ips_; + + uint16_t peer_port_; + uint16_t keepalive_; + uint32_t reboot_timeout_; + + time::RealTimeClock *srctime_; + +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *status_sensor_ = nullptr; + binary_sensor::BinarySensor *enabled_sensor_ = nullptr; +#endif + +#ifdef USE_SENSOR + sensor::Sensor *handshake_sensor_ = nullptr; +#endif + +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *address_sensor_ = nullptr; +#endif + + /// Set to false to block the setup step until peer is connected. + bool proceed_allowed_ = true; + + /// When false the wireguard link will not be established + bool enabled_ = true; + + wireguard_config_t wg_config_ = ESP_WIREGUARD_CONFIG_DEFAULT(); + wireguard_ctx_t wg_ctx_ = ESP_WIREGUARD_CONTEXT_DEFAULT(); + + esp_err_t wg_initialized_ = ESP_FAIL; + esp_err_t wg_connected_ = ESP_FAIL; + + /// The last time the remote peer become offline. + uint32_t wg_peer_offline_time_ = 0; + + /** \brief The latest saved handshake. + * + * This is used to save (and log) the latest completed handshake even + * after a full refresh of the wireguard keys (for example after a + * stop/start connection cycle). + */ + time_t latest_saved_handshake_ = 0; + + void start_connection_(); + void stop_connection_(); +}; + +// These are used for possibly long DNS resolution to temporarily suspend the watchdog +void suspend_wdt(); +void resume_wdt(); + +/// Strip most part of the key only for secure printing +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(); } +}; + +/// 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(); } +}; + +/// Action to enable Wireguard component. +template class WireguardEnableAction : public Action, public Parented { + public: + void play(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(); } +}; + +} // namespace wireguard +} // namespace esphome diff --git a/esphome/components/wk2132_i2c/__init__.py b/esphome/components/wk2132_i2c/__init__.py new file mode 100644 index 000000000000..912ab04236f6 --- /dev/null +++ b/esphome/components/wk2132_i2c/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, weikai +from esphome.const import CONF_ID + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["weikai", "weikai_i2c"] +MULTI_CONF = True + +weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") +WeikaiComponentI2C = weikai_i2c_ns.class_( + "WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentI2C), + } + ).extend(i2c.i2c_device_schema(0x2C)), + weikai.check_channel_max_2, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/wk2132_i2c/wk2132_i2c.cpp b/esphome/components/wk2132_i2c/wk2132_i2c.cpp new file mode 100644 index 000000000000..aaefae6f9706 --- /dev/null +++ b/esphome/components/wk2132_i2c/wk2132_i2c.cpp @@ -0,0 +1,4 @@ +/* compiling with esp-idf framework requires a .cpp file for some reason ? */ +namespace esphome { +namespace wk2132_i2c {} +} // namespace esphome diff --git a/esphome/components/wk2132_spi/__init__.py b/esphome/components/wk2132_spi/__init__.py new file mode 100644 index 000000000000..02c5fd960484 --- /dev/null +++ b/esphome/components/wk2132_spi/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi, weikai + +from esphome.const import CONF_ID + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["weikai", "weikai_spi"] +MULTI_CONF = True + +weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") +WeikaiComponentSPI = weikai_spi_ns.class_( + "WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentSPI), + } + ).extend(spi.spi_device_schema()), + weikai.check_channel_max_2, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/wk2168_i2c/__init__.py b/esphome/components/wk2168_i2c/__init__.py new file mode 100644 index 000000000000..93a8161e8e6d --- /dev/null +++ b/esphome/components/wk2168_i2c/__init__.py @@ -0,0 +1,64 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import i2c, weikai +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, +) + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["weikai", "weikai_i2c"] +MULTI_CONF = True +CONF_WK2168_I2C = "wk2168_i2c" + +weikai_ns = cg.esphome_ns.namespace("weikai") +weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") +WeikaiComponentI2C = weikai_i2c_ns.class_( + "WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice +) +WeikaiGPIOPin = weikai_ns.class_( + "WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C) +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentI2C), + } + ).extend(i2c.i2c_device_schema(0x2C)), + weikai.check_channel_max_4, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await i2c.register_i2c_device(var, config) + + +WK2168_PIN_SCHEMA = cv.All( + weikai.WEIKAI_PIN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiGPIOPin), + cv.Required(CONF_WK2168_I2C): cv.use_id(WeikaiComponentI2C), + } + ), + weikai.validate_pin_mode, +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_I2C, WK2168_PIN_SCHEMA) +async def sc16is75x_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_WK2168_I2C]) + cg.add(var.set_parent(parent)) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/wk2168_spi/__init__.py b/esphome/components/wk2168_spi/__init__.py new file mode 100644 index 000000000000..8861a6738c9f --- /dev/null +++ b/esphome/components/wk2168_spi/__init__.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi, weikai +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, +) + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["weikai", "weikai_spi"] +MULTI_CONF = True +CONF_WK2168_SPI = "wk2168_spi" + +weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") +weikai_ns = cg.esphome_ns.namespace("weikai") +WeikaiComponentSPI = weikai_spi_ns.class_( + "WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice +) +WeikaiGPIOPin = weikai_ns.class_( + "WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI) +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)} + ).extend(spi.spi_device_schema()), + weikai.check_channel_max_4, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await spi.register_spi_device(var, config) + + +WK2168_PIN_SCHEMA = cv.All( + weikai.WEIKAI_PIN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiGPIOPin), + cv.Required(CONF_WK2168_SPI): cv.use_id(WeikaiComponentSPI), + }, + ), + weikai.validate_pin_mode, +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_SPI, WK2168_PIN_SCHEMA) +async def sc16is75x_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_WK2168_SPI]) + cg.add(var.set_parent(parent)) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/wk2204_i2c/__init__.py b/esphome/components/wk2204_i2c/__init__.py new file mode 100644 index 000000000000..98eca56c4d1a --- /dev/null +++ b/esphome/components/wk2204_i2c/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, weikai +from esphome.const import CONF_ID + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["weikai", "weikai_i2c"] +MULTI_CONF = True + +weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") +WeikaiComponentI2C = weikai_i2c_ns.class_( + "WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentI2C), + } + ).extend(i2c.i2c_device_schema(0x2C)), + weikai.check_channel_max_4, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/wk2204_spi/__init__.py b/esphome/components/wk2204_spi/__init__.py new file mode 100644 index 000000000000..447805375dc3 --- /dev/null +++ b/esphome/components/wk2204_spi/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi, weikai +from esphome.const import CONF_ID + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["weikai", "weikai_spi"] +MULTI_CONF = True + +weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") +WeikaiComponentSPI = weikai_spi_ns.class_( + "WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentSPI), + } + ).extend(spi.spi_device_schema()), + weikai.check_channel_max_4, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/wk2212_i2c/__init__.py b/esphome/components/wk2212_i2c/__init__.py new file mode 100644 index 000000000000..fd4d717b31c2 --- /dev/null +++ b/esphome/components/wk2212_i2c/__init__.py @@ -0,0 +1,64 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import i2c, weikai +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, +) + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["weikai", "weikai_i2c"] +MULTI_CONF = True +CONF_WK2212_I2C = "wk2212_i2c" + +weikai_ns = cg.esphome_ns.namespace("weikai") +weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") +WeikaiComponentI2C = weikai_i2c_ns.class_( + "WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice +) +WeikaiGPIOPin = weikai_ns.class_( + "WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C) +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiComponentI2C), + } + ).extend(i2c.i2c_device_schema(0x2C)), + weikai.check_channel_max_2, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await i2c.register_i2c_device(var, config) + + +WK2212_PIN_SCHEMA = cv.All( + weikai.WEIKAI_PIN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiGPIOPin), + cv.Required(CONF_WK2212_I2C): cv.use_id(WeikaiComponentI2C), + } + ), + weikai.validate_pin_mode, +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_I2C, WK2212_PIN_SCHEMA) +async def sc16is75x_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_WK2212_I2C]) + cg.add(var.set_parent(parent)) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/wk2212_spi/__init__.py b/esphome/components/wk2212_spi/__init__.py new file mode 100644 index 000000000000..bfeca87c222d --- /dev/null +++ b/esphome/components/wk2212_spi/__init__.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi, weikai +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, +) + +CODEOWNERS = ["@DrCoolZic"] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["weikai", "weikai_spi"] +MULTI_CONF = True +CONF_WK2212_SPI = "wk2212_spi" + +weikai_ns = cg.esphome_ns.namespace("weikai") +weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") +WeikaiComponentSPI = weikai_spi_ns.class_( + "WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice +) +WeikaiGPIOPin = weikai_ns.class_( + "WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI) +) + +CONFIG_SCHEMA = cv.All( + weikai.WKBASE_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)} + ).extend(spi.spi_device_schema()), + weikai.check_channel_max_2, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_name(str(config[CONF_ID]))) + await weikai.register_weikai(var, config) + await spi.register_spi_device(var, config) + + +WK2212_PIN_SCHEMA = cv.All( + weikai.WEIKAI_PIN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WeikaiGPIOPin), + cv.Required(CONF_WK2212_SPI): cv.use_id(WeikaiComponentSPI), + }, + ), + weikai.validate_pin_mode, +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_SPI, WK2212_PIN_SCHEMA) +async def sc16is75x_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_WK2212_SPI]) + cg.add(var.set_parent(parent)) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index 279552920365..396d5891d82e 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -8,6 +8,8 @@ WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino) +CONF_SYNC_GROUP_MASK = "sync_group_mask" +CONF_BLANK_ON_START = "blank_on_start" @register_addressable_effect( @@ -16,10 +18,13 @@ "WLED", { cv.Optional(CONF_PORT, default=21324): cv.port, + cv.Optional(CONF_SYNC_GROUP_MASK, default=0): cv.int_range(min=0, max=255), + cv.Optional(CONF_BLANK_ON_START, default=True): cv.boolean, }, ) async def wled_light_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(effect.set_port(config[CONF_PORT])) - + cg.add(effect.set_sync_group_mask(config[CONF_SYNC_GROUP_MASK])) + cg.add(effect.set_blank_on_start(config[CONF_BLANK_ON_START])) return effect diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 8c68bca6e30b..84842dff393e 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -13,6 +13,10 @@ #include #endif +#ifdef USE_BK72XX +#include +#endif + namespace esphome { namespace wled { @@ -29,7 +33,11 @@ WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffe void WLEDLightEffect::start() { AddressableLightEffect::start(); - blank_at_ = 0; + if (this->blank_on_start_) { + this->blank_at_ = 0; + } else { + this->blank_at_ = UINT32_MAX; + } } void WLEDLightEffect::stop() { @@ -101,8 +109,11 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p if (!parse_drgb_frame_(it, payload, size)) return false; } else { - if (!parse_notifier_frame_(it, payload, size)) + if (!parse_notifier_frame_(it, payload, size)) { return false; + } else { + timeout = UINT8_MAX; + } } break; @@ -143,8 +154,32 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p } bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { - // Packet needs to be empty - return size == 0; + // Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification + // https://kno.wled.ge/interfaces/udp-notifier/ + // https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp + + if (size < 34) { + return false; + } + + uint8_t payload_sync_group_mask = payload[34]; + + if (this->sync_group_mask_ && !(payload_sync_group_mask & this->sync_group_mask_)) { + ESP_LOGD(TAG, "sync group mask does not match"); + return false; + } + + uint8_t bri = payload[0]; + uint8_t r = esp_scale8(payload[1], bri); + uint8_t g = esp_scale8(payload[2], bri); + uint8_t b = esp_scale8(payload[3], bri); + uint8_t w = esp_scale8(payload[8], bri); + + for (auto &&led : it) { + led.set(Color(r, g, b, w)); + } + + return true; } bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index 8f239276d736..a591e1fd1a0c 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -21,6 +21,8 @@ class WLEDLightEffect : public light::AddressableLightEffect { void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; void set_port(uint16_t port) { this->port_ = port; } + void set_sync_group_mask(uint8_t mask) { this->sync_group_mask_ = mask; } + void set_blank_on_start(bool blank) { this->blank_on_start_ = blank; } protected: void blank_all_leds_(light::AddressableLight &it); @@ -35,6 +37,8 @@ class WLEDLightEffect : public light::AddressableLightEffect { std::unique_ptr udp_; uint32_t blank_at_{0}; uint32_t dropped_{0}; + uint8_t sync_group_mask_{0}; + bool blank_on_start_{true}; }; } // namespace wled diff --git a/esphome/components/x9c/x9c.cpp b/esphome/components/x9c/x9c.cpp index ff7777e71f4f..32a1375f0252 100644 --- a/esphome/components/x9c/x9c.cpp +++ b/esphome/components/x9c/x9c.cpp @@ -7,6 +7,10 @@ namespace x9c { static const char *const TAG = "x9c.output"; void X9cOutput::trim_value(int change_amount) { + if (change_amount == 0) { + return; + } + if (change_amount > 0) { // Set change direction this->ud_pin_->digital_write(true); } else { @@ -45,17 +49,17 @@ void X9cOutput::setup() { if (this->initial_value_ <= 0.50) { this->trim_value(-101); // Set min value (beyond 0) - this->trim_value((int) (this->initial_value_ * 100)); + this->trim_value(static_cast(roundf(this->initial_value_ * 100))); } else { this->trim_value(101); // Set max value (beyond 100) - this->trim_value((int) (this->initial_value_ * 100) - 100); + this->trim_value(static_cast(roundf(this->initial_value_ * 100) - 100)); } this->pot_value_ = this->initial_value_; this->write_state(this->initial_value_); } void X9cOutput::write_state(float state) { - this->trim_value((int) ((state - this->pot_value_) * 100)); + this->trim_value(static_cast(roundf((state - this->pot_value_) * 100))); this->pot_value_ = state; } diff --git a/esphome/components/xgzp68xx/__init__.py b/esphome/components/xgzp68xx/__init__.py new file mode 100644 index 000000000000..122ffaf6edf8 --- /dev/null +++ b/esphome/components/xgzp68xx/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@gcormier"] diff --git a/esphome/components/xgzp68xx/sensor.py b/esphome/components/xgzp68xx/sensor.py new file mode 100644 index 000000000000..3e381aaa61a5 --- /dev/null +++ b/esphome/components/xgzp68xx/sensor.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_PRESSURE, + CONF_ID, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_PASCAL, + UNIT_CELSIUS, + CONF_TEMPERATURE, + CONF_PRESSURE, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@gcormier"] + +CONF_K_VALUE = "k_value" + +xgzp68xx_ns = cg.esphome_ns.namespace("xgzp68xx") +XGZP68XXComponent = xgzp68xx_ns.class_( + "XGZP68XXComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XGZP68XXComponent), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_PASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + 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_K_VALUE, default=4096): cv.uint16_t, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x6D)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + + cg.add(var.set_k_value(config[CONF_K_VALUE])) diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp new file mode 100644 index 000000000000..ea3583c3c537 --- /dev/null +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -0,0 +1,91 @@ +#include "xgzp68xx.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace xgzp68xx { + +static const char *const TAG = "xgzp68xx.sensor"; + +static const uint8_t CMD_ADDRESS = 0x30; +static const uint8_t SYSCONFIG_ADDRESS = 0xA5; +static const uint8_t PCONFIG_ADDRESS = 0xA6; +static const uint8_t READ_COMMAND = 0x0A; + +void XGZP68XXComponent::update() { + // Request temp + pressure acquisition + this->write_register(0x30, &READ_COMMAND, 1); + + // Wait 20mS per datasheet + this->set_timeout("measurement", 20, [this]() { + uint8_t data[5]; + uint32_t pressure_raw; + uint16_t temperature_raw; + float pressure_in_pa, temperature; + int success; + + // Read the sensor data + success = this->read_register(0x06, data, 5); + if (success != 0) { + ESP_LOGE(TAG, "Failed to read sensor data! Error code: %i", success); + return; + } + + pressure_raw = encode_uint24(data[0], data[1], data[2]); + temperature_raw = encode_uint16(data[3], data[4]); + + // Convert the pressure data to hPa + ESP_LOGV(TAG, "Got raw pressure=%d, raw temperature=%d ", pressure_raw, temperature_raw); + ESP_LOGV(TAG, "K value is %d ", this->k_value_); + + // The most significant bit of both pressure and temperature will be 1 to indicate a negative value. + // This is directly from the datasheet, and the calculations below will handle this. + if (pressure_raw > pow(2, 23)) { + // Negative pressure + pressure_in_pa = (pressure_raw - pow(2, 24)) / (float) (this->k_value_); + } else { + // Positive pressure + pressure_in_pa = pressure_raw / (float) (this->k_value_); + } + + if (temperature_raw > pow(2, 15)) { + // Negative temperature + temperature = (float) (temperature_raw - pow(2, 16)) / 256.0f; + } else { + // Positive temperature + temperature = (float) temperature_raw / 256.0f; + } + + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressure_in_pa); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + }); // end of set_timeout +} + +void XGZP68XXComponent::setup() { + ESP_LOGD(TAG, "Setting up XGZP68xx..."); + uint8_t config; + + // Display some sample bits to confirm we are talking to the sensor + this->read_register(SYSCONFIG_ADDRESS, &config, 1); + ESP_LOGCONFIG(TAG, "Gain value is %d", (config >> 3) & 0b111); + ESP_LOGCONFIG(TAG, "XGZP68xx started!"); +} + +void XGZP68XXComponent::dump_config() { + ESP_LOGCONFIG(TAG, "XGZP68xx"); + LOG_SENSOR(" ", "Temperature: ", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure: ", this->pressure_sensor_); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Connection with XGZP68xx failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +} // namespace xgzp68xx +} // namespace esphome diff --git a/esphome/components/xgzp68xx/xgzp68xx.h b/esphome/components/xgzp68xx/xgzp68xx.h new file mode 100644 index 000000000000..1bb7304b159b --- /dev/null +++ b/esphome/components/xgzp68xx/xgzp68xx.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace xgzp68xx { + +class XGZP68XXComponent : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { + public: + SUB_SENSOR(temperature) + SUB_SENSOR(pressure) + void set_k_value(uint16_t k_value) { this->k_value_ = k_value; } + + void update() override; + void setup() override; + void dump_config() override; + + protected: + /// Internal method to read the pressure from the component after it has been scheduled. + void read_pressure_(); + uint16_t k_value_; +}; + +} // namespace xgzp68xx +} // namespace esphome diff --git a/esphome/components/xiaomi_hhccjcy10/__init__.py b/esphome/components/xiaomi_hhccjcy10/__init__.py new file mode 100644 index 000000000000..d47cef13c669 --- /dev/null +++ b/esphome/components/xiaomi_hhccjcy10/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@fariouche"] diff --git a/esphome/components/xiaomi_hhccjcy10/sensor.py b/esphome/components/xiaomi_hhccjcy10/sensor.py new file mode 100644 index 000000000000..4f77fa8103ca --- /dev/null +++ b/esphome/components/xiaomi_hhccjcy10/sensor.py @@ -0,0 +1,96 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_WATER_PERCENT, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_ID, + CONF_MOISTURE, + CONF_ILLUMINANCE, + UNIT_LUX, + CONF_CONDUCTIVITY, + UNIT_MICROSIEMENS_PER_CENTIMETER, + ICON_FLOWER, + DEVICE_CLASS_BATTERY, + CONF_BATTERY_LEVEL, +) + +DEPENDENCIES = ["esp32_ble_tracker"] + +xiaomi_hhccjcy10_ns = cg.esphome_ns.namespace("xiaomi_hhccjcy10") +XiaomiHHCCJCY10 = xiaomi_hhccjcy10_ns.class_( + "XiaomiHHCCJCY10", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiHHCCJCY10), + 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_MOISTURE): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROSIEMENS_PER_CENTIMETER, + icon=ICON_FLOWER, + accuracy_decimals=0, + 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, + ), + } + ) + .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 moisture_config := config.get(CONF_MOISTURE): + sens = await sensor.new_sensor(moisture_config) + cg.add(var.set_moisture(sens)) + if illuminance_config := config.get(CONF_ILLUMINANCE): + sens = await sensor.new_sensor(illuminance_config) + cg.add(var.set_illuminance(sens)) + if conductivity_config := config.get(CONF_CONDUCTIVITY): + sens = await sensor.new_sensor(conductivity_config) + cg.add(var.set_conductivity(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)) diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp new file mode 100644 index 000000000000..45d4cceb5249 --- /dev/null +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp @@ -0,0 +1,68 @@ +#include "xiaomi_hhccjcy10.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_hhccjcy10 { + +static const char *const TAG = "xiaomi_hhccjcy10"; + +void XiaomiHHCCJCY10::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi HHCCJCY10"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Moisture", this->moisture_); + LOG_SENSOR(" ", "Conductivity", this->conductivity_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool XiaomiHHCCJCY10::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + if (!service_data.uuid.contains(0x50, 0xFD)) { + ESP_LOGVV(TAG, "no tuya service data UUID."); + continue; + } + if (service_data.data.size() != 9) { // tuya alternate between two service data + continue; + } + const uint8_t *data = service_data.data.data(); + + if (this->temperature_ != nullptr) { + const int16_t temperature = encode_uint16(data[1], data[2]); + this->temperature_->publish_state((float) temperature / 10.0f); + } + + if (this->moisture_ != nullptr) + this->moisture_->publish_state(data[0]); + + if (this->conductivity_ != nullptr) { + const uint16_t conductivity = encode_uint16(data[7], data[8]); + this->conductivity_->publish_state((float) conductivity); + } + + if (this->illuminance_ != nullptr) { + const uint32_t illuminance = encode_uint24(data[3], data[4], data[5]); + this->illuminance_->publish_state((float) illuminance); + } + + if (this->battery_level_ != nullptr) + this->battery_level_->publish_state(data[6]); + success = true; + } + + return success; +} + +} // namespace xiaomi_hhccjcy10 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h new file mode 100644 index 000000000000..bc1e580ce410 --- /dev/null +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.h @@ -0,0 +1,38 @@ +#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 { +namespace xiaomi_hhccjcy10 { + +class XiaomiHHCCJCY10 : 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; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_moisture(sensor::Sensor *moisture) { this->moisture_ = moisture; } + void set_conductivity(sensor::Sensor *conductivity) { this->conductivity_ = conductivity; } + void set_illuminance(sensor::Sensor *illuminance) { this->illuminance_ = illuminance; } + void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *moisture_{nullptr}; + sensor::Sensor *conductivity_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace xiaomi_hhccjcy10 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py b/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py index 8eee10685e4e..ef8a472d663e 100644 --- a/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py +++ b/esphome/components/xiaomi_rtcgq02lm/binary_sensor.py @@ -8,6 +8,7 @@ DEVICE_CLASS_LIGHT, DEVICE_CLASS_MOTION, CONF_ID, + CONF_BUTTON, ) from esphome.core import TimePeriod @@ -15,8 +16,6 @@ DEPENDENCIES = ["xiaomi_rtcgq02lm"] -CONF_BUTTON = "button" - CONFIG_SCHEMA = cv.Schema( { diff --git a/esphome/components/xl9535/__init__.py b/esphome/components/xl9535/__init__.py index 7fcac50ba775..e6f8b28b46f9 100644 --- a/esphome/components/xl9535/__init__.py +++ b/esphome/components/xl9535/__init__.py @@ -43,11 +43,17 @@ def validate_mode(mode): return mode +def validate_pin(pin): + if pin in (8, 9): + raise cv.Invalid(f"pin {pin} doesn't exist") + return pin + + XL9535_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(XL9535GPIOPin), cv.Required(CONF_XL9535): cv.use_id(XL9535Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Required(CONF_NUMBER): cv.All(cv.int_range(min=0, max=17), validate_pin), cv.Optional(CONF_MODE, default={}): cv.All( { cv.Optional(CONF_INPUT, default=False): cv.boolean, diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index 8f4f556b4ac2..93c65a4cb747 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -36,14 +36,14 @@ bool XL9535Component::digital_read(uint8_t pin) { return state; } - state = (port & (pin - 10)) != 0; + state = (port & (1 << (pin - 10))) != 0; } else { if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) { this->status_set_warning(); return state; } - state = (port & pin) != 0; + state = (port & (1 << pin)) != 0; } this->status_clear_warning(); diff --git a/esphome/components/xpt2046/binary_sensor.py b/esphome/components/xpt2046/binary_sensor.py deleted file mode 100644 index 5a6cfe4919ab..000000000000 --- a/esphome/components/xpt2046/binary_sensor.py +++ /dev/null @@ -1,3 +0,0 @@ -import esphome.config_validation as cv - -CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.") diff --git a/esphome/components/xpt2046/touchscreen.py b/esphome/components/xpt2046/touchscreen.py deleted file mode 100644 index e45b72317955..000000000000 --- a/esphome/components/xpt2046/touchscreen.py +++ /dev/null @@ -1,117 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv - -from esphome import pins -from esphome.components import spi, touchscreen -from esphome.const import CONF_ID, CONF_THRESHOLD, CONF_INTERRUPT_PIN - -CODEOWNERS = ["@numo68", "@nielsnl68"] -DEPENDENCIES = ["spi"] - -XPT2046_ns = cg.esphome_ns.namespace("xpt2046") -XPT2046Component = XPT2046_ns.class_( - "XPT2046Component", - touchscreen.Touchscreen, - cg.PollingComponent, - spi.SPIDevice, -) - -CONF_REPORT_INTERVAL = "report_interval" -CONF_CALIBRATION_X_MIN = "calibration_x_min" -CONF_CALIBRATION_X_MAX = "calibration_x_max" -CONF_CALIBRATION_Y_MIN = "calibration_y_min" -CONF_CALIBRATION_Y_MAX = "calibration_y_max" -CONF_SWAP_X_Y = "swap_x_y" - -# obsolete Keys -CONF_DIMENSION_X = "dimension_x" -CONF_DIMENSION_Y = "dimension_y" -CONF_IRQ_PIN = "irq_pin" - - -def validate_xpt2046(config): - if ( - abs( - cv.int_(config[CONF_CALIBRATION_X_MAX]) - - cv.int_(config[CONF_CALIBRATION_X_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration X values difference < 1000") - - if ( - abs( - cv.int_(config[CONF_CALIBRATION_Y_MAX]) - - cv.int_(config[CONF_CALIBRATION_Y_MIN]) - ) - < 1000 - ): - raise cv.Invalid("Calibration Y values difference < 1000") - - return config - - -def report_interval(value): - if value == "never": - return 4294967295 # uint32_t max - return cv.positive_time_period_milliseconds(value) - - -CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(XPT2046Component), - cv.Optional(CONF_INTERRUPT_PIN): cv.All( - pins.internal_gpio_input_pin_schema - ), - cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range( - min=0, max=4095 - ), - cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), - cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval, - cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean, - # obsolete Keys - cv.Optional(CONF_IRQ_PIN): cv.invalid("Rename IRQ_PIN to INTERUPT_PIN"), - cv.Optional(CONF_DIMENSION_X): cv.invalid( - "This key is now obsolete, please remove it" - ), - cv.Optional(CONF_DIMENSION_Y): cv.invalid( - "This key is now obsolete, please remove it" - ), - }, - ) - .extend(cv.polling_component_schema("50ms")) - .extend(spi.spi_device_schema()), -).add_extra(validate_xpt2046) - - -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) - await touchscreen.register_touchscreen(var, config) - - cg.add(var.set_threshold(config[CONF_THRESHOLD])) - cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL])) - cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y])) - cg.add( - var.set_calibration( - config[CONF_CALIBRATION_X_MIN], - config[CONF_CALIBRATION_X_MAX], - config[CONF_CALIBRATION_Y_MIN], - config[CONF_CALIBRATION_Y_MAX], - ) - ) - - if CONF_INTERRUPT_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) - cg.add(var.set_irq_pin(pin)) diff --git a/esphome/components/xpt2046/touchscreen/__init__.py b/esphome/components/xpt2046/touchscreen/__init__.py new file mode 100644 index 000000000000..d45f309a3b60 --- /dev/null +++ b/esphome/components/xpt2046/touchscreen/__init__.py @@ -0,0 +1,68 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import spi, touchscreen +from esphome.const import CONF_ID, CONF_THRESHOLD, CONF_INTERRUPT_PIN + +CODEOWNERS = ["@numo68", "@nielsnl68"] +DEPENDENCIES = ["spi"] + +XPT2046_ns = cg.esphome_ns.namespace("xpt2046") +XPT2046Component = XPT2046_ns.class_( + "XPT2046Component", + touchscreen.Touchscreen, + spi.SPIDevice, +) + +CONF_CALIBRATION_X_MIN = "calibration_x_min" +CONF_CALIBRATION_X_MAX = "calibration_x_max" +CONF_CALIBRATION_Y_MIN = "calibration_y_min" +CONF_CALIBRATION_Y_MAX = "calibration_y_max" + +CONFIG_SCHEMA = cv.All( + touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XPT2046Component), + cv.Optional(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095), + cv.Optional( + touchscreen.CONF_CALIBRATION + ): touchscreen.calibration_schema(4095), + cv.Optional(CONF_CALIBRATION_X_MIN): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_X_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MIN): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional(CONF_CALIBRATION_Y_MAX): cv.invalid( + "Deprecated: use the new 'calibration' configuration variable" + ), + cv.Optional("report_interval"): cv.invalid( + "Deprecated: use the 'update_interval' configuration variable" + ), + }, + ) + ).extend(spi.spi_device_schema()), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await spi.register_spi_device(var, config) + await touchscreen.register_touchscreen(var, config) + + cg.add(var.set_threshold(config[CONF_THRESHOLD])) + + if CONF_INTERRUPT_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_irq_pin(pin)) diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.cpp b/esphome/components/xpt2046/touchscreen/xpt2046.cpp new file mode 100644 index 000000000000..a4e2b8465602 --- /dev/null +++ b/esphome/components/xpt2046/touchscreen/xpt2046.cpp @@ -0,0 +1,112 @@ +#include "xpt2046.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace xpt2046 { + +static const char *const TAG = "xpt2046"; + +void XPT2046Component::setup() { + if (this->irq_pin_ != nullptr) { + // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state + // while the channels are read and wiring it as an interrupt is not straightforward and would + // need careful masking. A GPIO poll is cheap so we'll just use that. + + this->irq_pin_->setup(); // INPUT + this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->irq_pin_->setup(); + this->attach_interrupt_(this->irq_pin_, gpio::INTERRUPT_FALLING_EDGE); + } + this->spi_setup(); + this->read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin +} + +void XPT2046Component::update_touches() { + int16_t data[6], x_raw, y_raw, z_raw; + bool touch = false; + + enable(); + + int16_t touch_pressure_1 = this->read_adc_(0xB1 /* touch_pressure_1 */); + int16_t touch_pressure_2 = this->read_adc_(0xC1 /* touch_pressure_2 */); + z_raw = touch_pressure_1 + 0Xfff - touch_pressure_2; + ESP_LOGVV(TAG, "Touchscreen Update z = %d", z_raw); + touch = (z_raw >= this->threshold_); + if (touch) { + read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy + data[0] = this->read_adc_(0x91 /* Y */); + data[1] = this->read_adc_(0xD1 /* X */); // make 3 x-y measurements + data[2] = this->read_adc_(0x91 /* Y */); + data[3] = this->read_adc_(0xD1 /* X */); + data[4] = this->read_adc_(0x91 /* Y */); + } + + data[5] = this->read_adc_(0xD0 /* X */); // Last X touch power down + + disable(); + + if (touch) { + x_raw = best_two_avg(data[1], data[3], data[5]); + y_raw = best_two_avg(data[0], data[2], data[4]); + + ESP_LOGD(TAG, "Touchscreen Update [%d, %d], z = %d", x_raw, y_raw, z_raw); + + this->add_raw_touch_position_(0, x_raw, y_raw, z_raw); + } +} + +void XPT2046Component::dump_config() { + ESP_LOGCONFIG(TAG, "XPT2046:"); + + LOG_PIN(" IRQ Pin: ", this->irq_pin_); + ESP_LOGCONFIG(TAG, " X min: %d", this->x_raw_min_); + ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_); + ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_); + ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_); + + ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->swap_x_y_)); + ESP_LOGCONFIG(TAG, " Invert X: %s", YESNO(this->invert_x_)); + ESP_LOGCONFIG(TAG, " Invert Y: %s", YESNO(this->invert_y_)); + + ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_); + + LOG_UPDATE_INTERVAL(this); +} + +// float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } + +int16_t XPT2046Component::best_two_avg(int16_t value1, int16_t value2, int16_t value3) { + int16_t delta_a, delta_b, delta_c; + int16_t reta = 0; + + delta_a = (value1 > value2) ? value1 - value2 : value2 - value1; + delta_b = (value1 > value3) ? value1 - value3 : value3 - value1; + delta_c = (value3 > value2) ? value3 - value2 : value2 - value3; + + if (delta_a <= delta_b && delta_a <= delta_c) { + reta = (value1 + value2) >> 1; + } else if (delta_b <= delta_a && delta_b <= delta_c) { + reta = (value1 + value3) >> 1; + } else { + reta = (value2 + value3) >> 1; + } + + return reta; +} + +int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT + uint8_t data[2]; + + this->write_byte(ctrl); + delay(1); + data[0] = this->read_byte(); + data[1] = this->read_byte(); + + return ((data[0] << 8) | data[1]) >> 3; +} + +} // namespace xpt2046 +} // namespace esphome diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.h b/esphome/components/xpt2046/touchscreen/xpt2046.h new file mode 100644 index 000000000000..a635c08f823e --- /dev/null +++ b/esphome/components/xpt2046/touchscreen/xpt2046.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace xpt2046 { + +using namespace touchscreen; + +class XPT2046Component : public Touchscreen, + public spi::SPIDevice { + public: + /// Set the threshold for the touch detection. + void set_threshold(int16_t threshold) { this->threshold_ = threshold; } + /// Set the pin used to detect the touch. + void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; } + + void setup() override; + void dump_config() override; + // float get_setup_priority() const override; + + protected: + static int16_t best_two_avg(int16_t value1, int16_t value2, int16_t value3); + + int16_t read_adc_(uint8_t ctrl); + + void update_touches() override; + + int16_t threshold_; + + InternalGPIOPin *irq_pin_{nullptr}; +}; + +} // namespace xpt2046 +} // namespace esphome diff --git a/esphome/components/xpt2046/xpt2046.cpp b/esphome/components/xpt2046/xpt2046.cpp deleted file mode 100644 index 6c7c55a9950a..000000000000 --- a/esphome/components/xpt2046/xpt2046.cpp +++ /dev/null @@ -1,206 +0,0 @@ -#include "xpt2046.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" - -#include - -namespace esphome { -namespace xpt2046 { - -static const char *const TAG = "xpt2046"; - -void XPT2046TouchscreenStore::gpio_intr(XPT2046TouchscreenStore *store) { store->touch = true; } - -void XPT2046Component::setup() { - if (this->irq_pin_ != nullptr) { - // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state - // while the channels are read and wiring it as an interrupt is not straightforward and would - // need careful masking. A GPIO poll is cheap so we'll just use that. - - this->irq_pin_->setup(); // INPUT - this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - this->irq_pin_->setup(); - this->irq_pin_->attach_interrupt(XPT2046TouchscreenStore::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); - } - spi_setup(); - read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin -} - -void XPT2046Component::loop() { - if ((this->irq_pin_ != nullptr) && (this->store_.touch || this->touched)) { - this->store_.touch = false; - check_touch_(); - } -} - -void XPT2046Component::update() { - if (this->irq_pin_ == nullptr) - check_touch_(); -} - -void XPT2046Component::check_touch_() { - int16_t data[6]; - bool touch = false; - uint32_t now = millis(); - - enable(); - - int16_t touch_pressure_1 = read_adc_(0xB1 /* touch_pressure_1 */); - int16_t touch_pressure_2 = read_adc_(0xC1 /* touch_pressure_2 */); - - this->z_raw = touch_pressure_1 + 0Xfff - touch_pressure_2; - - touch = (this->z_raw >= this->threshold_); - if (touch) { - read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy - data[0] = read_adc_(0x91 /* Y */); - data[1] = read_adc_(0xD1 /* X */); // make 3 x-y measurements - data[2] = read_adc_(0x91 /* Y */); - data[3] = read_adc_(0xD1 /* X */); - data[4] = read_adc_(0x91 /* Y */); - } - - data[5] = read_adc_(0xD0 /* X */); // Last X touch power down - - disable(); - - if (touch) { - this->x_raw = best_two_avg(data[1], data[3], data[5]); - this->y_raw = best_two_avg(data[0], data[2], data[4]); - - ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw); - - TouchPoint touchpoint; - - touchpoint.x = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_); - touchpoint.y = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_); - - if (this->swap_x_y_) { - std::swap(touchpoint.x, touchpoint.y); - } - - if (this->invert_x_) { - touchpoint.x = 0xfff - touchpoint.x; - } - - if (this->invert_y_) { - touchpoint.y = 0xfff - touchpoint.y; - } - - switch (static_cast(this->display_->get_rotation())) { - case ROTATE_0_DEGREES: - break; - case ROTATE_90_DEGREES: - std::swap(touchpoint.x, touchpoint.y); - touchpoint.y = 0xfff - touchpoint.y; - break; - case ROTATE_180_DEGREES: - touchpoint.x = 0xfff - touchpoint.x; - touchpoint.y = 0xfff - touchpoint.y; - break; - case ROTATE_270_DEGREES: - std::swap(touchpoint.x, touchpoint.y); - touchpoint.x = 0xfff - touchpoint.x; - break; - } - - touchpoint.x = (int16_t) ((int) touchpoint.x * this->display_->get_width() / 0xfff); - touchpoint.y = (int16_t) ((int) touchpoint.y * this->display_->get_height() / 0xfff); - - if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) { - ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y); - - this->defer([this, touchpoint]() { this->send_touch_(touchpoint); }); - - this->x = touchpoint.x; - this->y = touchpoint.y; - this->touched = true; - this->last_pos_ms_ = now; - } - } - - if (!touch && this->touched) { - this->x_raw = this->y_raw = this->z_raw = 0; - ESP_LOGV(TAG, "Released [%d, %d]", this->x, this->y); - this->touched = false; - for (auto *listener : this->touch_listeners_) - listener->release(); - } -} - -void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { // NOLINT - this->x_raw_min_ = std::min(x_min, x_max); - this->x_raw_max_ = std::max(x_min, x_max); - this->y_raw_min_ = std::min(y_min, y_max); - this->y_raw_max_ = std::max(y_min, y_max); - this->invert_x_ = (x_min > x_max); - this->invert_y_ = (y_min > y_max); -} - -void XPT2046Component::dump_config() { - ESP_LOGCONFIG(TAG, "XPT2046:"); - - LOG_PIN(" IRQ Pin: ", this->irq_pin_); - ESP_LOGCONFIG(TAG, " X min: %d", this->x_raw_min_); - ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_); - ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_); - ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_); - - ESP_LOGCONFIG(TAG, " Swap X/Y: %s", YESNO(this->swap_x_y_)); - ESP_LOGCONFIG(TAG, " Invert X: %s", YESNO(this->invert_x_)); - ESP_LOGCONFIG(TAG, " Invert Y: %s", YESNO(this->invert_y_)); - - ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_); - ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_); - - LOG_UPDATE_INTERVAL(this); -} - -float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; } - -int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) { // NOLINT - int16_t da, db, dc; // NOLINT - int16_t reta = 0; - - da = (x > y) ? x - y : y - x; - db = (x > z) ? x - z : z - x; - dc = (z > y) ? z - y : y - z; - - if (da <= db && da <= dc) { - reta = (x + y) >> 1; - } else if (db <= da && db <= dc) { - reta = (x + z) >> 1; - } else { - reta = (y + z) >> 1; - } - - return reta; -} - -int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_val) { - int16_t ret; - - if (val <= min_val) { - ret = 0; - } else if (val >= max_val) { - ret = 0xfff; - } else { - ret = (int16_t) ((int) 0xfff * (val - min_val) / (max_val - min_val)); - } - - return ret; -} - -int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT - uint8_t data[2]; - - write_byte(ctrl); - delay(1); - data[0] = read_byte(); - data[1] = read_byte(); - - return ((data[0] << 8) | data[1]) >> 3; -} - -} // namespace xpt2046 -} // namespace esphome diff --git a/esphome/components/xpt2046/xpt2046.h b/esphome/components/xpt2046/xpt2046.h deleted file mode 100644 index e7d9caba21ee..000000000000 --- a/esphome/components/xpt2046/xpt2046.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "esphome/components/spi/spi.h" -#include "esphome/components/touchscreen/touchscreen.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace xpt2046 { - -using namespace touchscreen; - -struct XPT2046TouchscreenStore { - volatile bool touch; - static void gpio_intr(XPT2046TouchscreenStore *store); -}; - -class XPT2046Component : public Touchscreen, - public PollingComponent, - public spi::SPIDevice { - public: - /// Set the logical touch screen dimensions. - void set_dimensions(int16_t x, int16_t y) { - this->display_width_ = x; - this->display_height_ = y; - } - /// Set the coordinates for the touch screen edges. - void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max); - /// If true the x and y axes will be swapped - void set_swap_x_y(bool val) { this->swap_x_y_ = val; } - - /// Set the interval to report the touch point perodically. - void set_report_interval(uint32_t interval) { this->report_millis_ = interval; } - uint32_t get_report_interval() { return this->report_millis_; } - - /// Set the threshold for the touch detection. - void set_threshold(int16_t threshold) { this->threshold_ = threshold; } - /// Set the pin used to detect the touch. - void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; } - - void setup() override; - void dump_config() override; - float get_setup_priority() const override; - - /** Detect the touch if the irq pin is specified. - * - * If the touch is detected and the component does not already know about it - * the update() is called immediately. If the irq pin is not specified - * the loop() is a no-op. - */ - void loop() override; - - /** Read and process the values from the hardware. - * - * Read the raw x, y and touch pressure values from the chip, detect the touch, - * and if touched, transform to the user x and y coordinates. If the state has - * changed or if the value should be reported again due to the - * report interval, run the action and inform the virtual buttons. - */ - void update() override; - - /**@{*/ - /** Coordinates of the touch position. - * - * The values are set immediately before the on_state action with touched == true - * is triggered. The action with touched == false sends the coordinates of the last - * reported touch. - */ - int16_t x{0}, y{0}; - /**@}*/ - - /// True if the component currently detects the touch - bool touched{false}; - - /**@{*/ - /** Raw sensor values of the coordinates and the pressure. - * - * The values are set each time the update() method is called. - */ - int16_t x_raw{0}, y_raw{0}, z_raw{0}; - /**@}*/ - - protected: - static int16_t best_two_avg(int16_t x, int16_t y, int16_t z); - static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val); - - int16_t read_adc_(uint8_t ctrl); - void check_touch_(); - - int16_t threshold_; - int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_; - - bool invert_x_, invert_y_; - bool swap_x_y_; - - uint32_t report_millis_; - uint32_t last_pos_ms_{0}; - - InternalGPIOPin *irq_pin_{nullptr}; - XPT2046TouchscreenStore store_; -}; - -} // namespace xpt2046 -} // namespace esphome diff --git a/esphome/components/zhlt01/__init__.py b/esphome/components/zhlt01/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/zhlt01/climate.py b/esphome/components/zhlt01/climate.py new file mode 100644 index 000000000000..1451f8ec6975 --- /dev/null +++ b/esphome/components/zhlt01/climate.py @@ -0,0 +1,19 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@cfeenstra1024"] + +zhlt01_ns = cg.esphome_ns.namespace("zhlt01") +ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(ZHLT01Climate)} +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/zhlt01/zhlt01.cpp b/esphome/components/zhlt01/zhlt01.cpp new file mode 100644 index 000000000000..36d1737c14cc --- /dev/null +++ b/esphome/components/zhlt01/zhlt01.cpp @@ -0,0 +1,238 @@ +#include "zhlt01.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace zhlt01 { + +static const char *const TAG = "zhlt01.climate"; + +void ZHLT01Climate::transmit_state() { + uint8_t ir_message[12] = {0}; + + // Byte 1 : Timer + ir_message[1] = 0x00; // Timer off + + // Byte 3 : Turbo mode + if (this->preset.value() == climate::CLIMATE_PRESET_BOOST) { + ir_message[3] = AC1_FAN_TURBO; + } + + // Byte 5 : Last pressed button + ir_message[5] = 0x00; // fixed as power button + + // Byte 7 : Power | Swing | Fan + // -- Power + if (this->mode == climate::CLIMATE_MODE_OFF) { + ir_message[7] = AC1_POWER_OFF; + } else { + ir_message[7] = AC1_POWER_ON; + } + + // -- Swing + switch (this->swing_mode) { + case climate::CLIMATE_SWING_OFF: + ir_message[7] |= AC1_HDIR_FIXED | AC1_VDIR_FIXED; + break; + case climate::CLIMATE_SWING_HORIZONTAL: + ir_message[7] |= AC1_HDIR_SWING | AC1_VDIR_FIXED; + break; + case climate::CLIMATE_SWING_VERTICAL: + ir_message[7] |= AC1_HDIR_FIXED | AC1_VDIR_SWING; + break; + case climate::CLIMATE_SWING_BOTH: + ir_message[7] |= AC1_HDIR_SWING | AC1_VDIR_SWING; + break; + default: + break; + } + + // -- Fan + switch (this->preset.value()) { + case climate::CLIMATE_PRESET_BOOST: + ir_message[7] |= AC1_FAN3; + break; + case climate::CLIMATE_PRESET_SLEEP: + ir_message[7] |= AC1_FAN_SILENT; + break; + default: + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + ir_message[7] |= AC1_FAN1; + break; + case climate::CLIMATE_FAN_MEDIUM: + ir_message[7] |= AC1_FAN2; + break; + case climate::CLIMATE_FAN_HIGH: + ir_message[7] |= AC1_FAN3; + break; + case climate::CLIMATE_FAN_AUTO: + ir_message[7] |= AC1_FAN_AUTO; + break; + default: + break; + } + } + + // Byte 9 : AC Mode | Temperature + // -- AC Mode + switch (this->mode) { + case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: + ir_message[9] = AC1_MODE_AUTO; + break; + case climate::CLIMATE_MODE_COOL: + ir_message[9] = AC1_MODE_COOL; + break; + case climate::CLIMATE_MODE_HEAT: + ir_message[9] = AC1_MODE_HEAT; + break; + case climate::CLIMATE_MODE_DRY: + ir_message[9] = AC1_MODE_DRY; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + ir_message[9] = AC1_MODE_FAN; + break; + default: + break; + } + + // -- Temperature + ir_message[9] |= (uint8_t) (this->target_temperature - 16.0f); + + // Byte 11 : Remote control ID + ir_message[11] = 0xD5; + + // Set checksum bytes + for (int i = 0; i < 12; i += 2) { + ir_message[i] = ~ir_message[i + 1]; + } + + // Send the code + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + + data->set_carrier_frequency(38000); // 38 kHz PWM + + // Header + data->mark(AC1_HDR_MARK); + data->space(AC1_HDR_SPACE); + + // Data + for (uint8_t i : ir_message) { + for (uint8_t j = 0; j < 8; j++) { + data->mark(AC1_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? AC1_ONE_SPACE : AC1_ZERO_SPACE); + } + } + + // Footer + data->mark(AC1_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +bool ZHLT01Climate::on_receive(remote_base::RemoteReceiveData data) { + // Validate header + if (!data.expect_item(AC1_HDR_MARK, AC1_HDR_SPACE)) { + ESP_LOGV(TAG, "Header fail"); + return false; + } + + // Decode IR message + uint8_t ir_message[12] = {0}; + // Read all bytes + for (int i = 0; i < 12; i++) { + // Read bit + for (int j = 0; j < 8; j++) { + if (data.expect_item(AC1_BIT_MARK, AC1_ONE_SPACE)) { + ir_message[i] |= 1 << j; + } else if (!data.expect_item(AC1_BIT_MARK, AC1_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); + return false; + } + } + ESP_LOGVV(TAG, "Byte %d %02X", i, ir_message[i]); + } + + // Validate footer + if (!data.expect_mark(AC1_BIT_MARK)) { + ESP_LOGV(TAG, "Footer fail"); + return false; + } + + // Validate checksum + for (int i = 0; i < 12; i += 2) { + if (ir_message[i] != (uint8_t) (~ir_message[i + 1])) { + ESP_LOGV(TAG, "Byte %d checksum incorrect (%02X != %02X)", i, ir_message[i], (uint8_t) (~ir_message[i + 1])); + return false; + } + } + + // Validate remote control ID + if (ir_message[11] != 0xD5) { + ESP_LOGV(TAG, "Invalid remote control ID"); + return false; + } + + // All is good to go + + if ((ir_message[7] & AC1_POWER_ON) == 0) { + this->mode = climate::CLIMATE_MODE_OFF; + } else { + // Vertical swing + if ((ir_message[7] & 0x0C) == AC1_VDIR_FIXED) { + if ((ir_message[7] & 0x10) == AC1_HDIR_FIXED) { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } else { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } + } else { + if ((ir_message[7] & 0x10) == AC1_HDIR_FIXED) { + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + } else { + this->swing_mode = climate::CLIMATE_SWING_BOTH; + } + } + + // Preset + Fan speed + if ((ir_message[3] & AC1_FAN_TURBO) == AC1_FAN_TURBO) { + this->preset = climate::CLIMATE_PRESET_BOOST; + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } else if ((ir_message[7] & 0xE1) == AC1_FAN_SILENT) { + this->preset = climate::CLIMATE_PRESET_SLEEP; + this->fan_mode = climate::CLIMATE_FAN_LOW; + } else if ((ir_message[7] & 0xE1) == AC1_FAN_AUTO) { + this->fan_mode = climate::CLIMATE_FAN_AUTO; + } else if ((ir_message[7] & 0xE1) == AC1_FAN1) { + this->fan_mode = climate::CLIMATE_FAN_LOW; + } else if ((ir_message[7] & 0xE1) == AC1_FAN2) { + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + } else if ((ir_message[7] & 0xE1) == AC1_FAN3) { + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } + + // AC Mode + if ((ir_message[9] & 0xE0) == AC1_MODE_COOL) { + this->mode = climate::CLIMATE_MODE_COOL; + } else if ((ir_message[9] & 0xE0) == AC1_MODE_HEAT) { + this->mode = climate::CLIMATE_MODE_HEAT; + } else if ((ir_message[9] & 0xE0) == AC1_MODE_DRY) { + this->mode = climate::CLIMATE_MODE_DRY; + } else if ((ir_message[9] & 0xE0) == AC1_MODE_FAN) { + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + } else { + this->mode = climate::CLIMATE_MODE_AUTO; + } + + // Taregt Temperature + this->target_temperature = (ir_message[9] & 0x1F) + 16.0f; + } + + this->publish_state(); + return true; +} + +} // namespace zhlt01 +} // namespace esphome diff --git a/esphome/components/zhlt01/zhlt01.h b/esphome/components/zhlt01/zhlt01.h new file mode 100644 index 000000000000..4413be2835c3 --- /dev/null +++ b/esphome/components/zhlt01/zhlt01.h @@ -0,0 +1,167 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +/*********************************************************************************** + * SOURCE + *********************************************************************************** + * The IR codes and the functional description below were taken from + * 'arduino-heatpumpir/ZHLT01HeatpumpIR.h' as can be found on GitHub + * https://github.com/ToniA/arduino-heatpumpir/blob/master/ZHLT01HeatpumpIR.h + * + ************************************************************************************ + * Airconditional remote control encoder for: + * + * ZH/LT-01 Remote control https://www.google.com/search?q=zh/lt-01 + * + * The ZH/LT-01 remote control is used for many locally branded Split + * airconditioners, so it is better to name this protocol by the name of the + * REMOTE rather then the name of the Airconditioner. For this project I used + * a 2014 model Eurom-airconditioner, which is Dutch-branded and sold in + * the Netherlands at Hornbach. + * + * For airco-brands: + * Eurom + * Chigo + * Tristar + * Tecnomaster + * Elgin + * Geant + * Tekno + * Topair + * Proma + * Sumikura + * JBS + * Turbo Air + * Nakatomy + * Celestial Air + * Ager + * Blueway + * Airlux + * Etc. + * + *********************************************************************************** + * SUMMARY FUNCTIONAL DESCRIPTION + *********************************************************************************** + * The remote sends a 12 Byte message which contains all possible settings every + * time. + * + * Byte 11 (and 10) contain the remote control identifier and are always 0xD5 and + * 0x2A respectively for the ZH/LT-01 remote control. + * Every UNeven Byte (01,03,05,07 and 09) holds command data + * Every EVEN Byte (00,02,04,06,08 and 10) holds a checksum of the corresponding + * command-, or identifier-byte by _inverting_ the bits, for example: + * + * The identifier byte[11] = 0xD5 = B1101 0101 + * The checksum byte[10] = 0x2A = B0010 1010 + * + * So, you can check the message by: + * - inverting the bits of the checksum byte with the corresponding command-, or + * identifier byte, they should be the same, or + * - Summing up the checksum byte and the corresponding command-, or identifier byte, + * they should always add up to 0xFF = B11111111 = 255 + * + * Control bytes: + * [01] - Timer (1-24 hours, Off) + * Time is hardcoded to OFF + * + * [03] - LAMP ON/OFF, TURBO ON/OFF, HOLD ON/OFF + * Lamp and Hold are hardcoded to OFF + * Turbo is used for the BOOST preset + * + * [05] - Indicates which button the user _pressed_ on the remote control + * Hardcoded to POWER-button + * + * [07] - POWER ON/OFF, FAN AUTO/3/2/1, SLEEP ON/OFF, AIRFLOW ON/OFF, + * VERTICAL SWING/WIND/FIXED + * SLEEP is used for preset SLEEP + * Vertical Swing supports Fixed, Swing and "Wind". The Wind option + * is ignored in this implementation + * + * [09] - MODE AUTO/COOL/VENT/DRY/HEAT, TEMPERATURE (16 - 32°C) + * + ***********************************************************************************/ + +namespace esphome { +namespace zhlt01 { + +/******************************************************************************** + * TIMINGS + * Space: Not used + * Header Mark: 6100 us + * Header Space: 7400 us + * Bit Mark: 500 us + * Zero Space: 600 us + * One Space: 1800 us + * + * Note : These timings are slightly different than those of ZHLT01HeatpumpIR + * The values below were measured by taking the average of 2 different + * remote controls each sending 10 commands + *******************************************************************************/ +static const uint32_t AC1_HDR_MARK = 6100; +static const uint32_t AC1_HDR_SPACE = 7400; +static const uint32_t AC1_BIT_MARK = 500; +static const uint32_t AC1_ZERO_SPACE = 600; +static const uint32_t AC1_ONE_SPACE = 1800; + +/******************************************************************************** + * + * ZHLT01 codes + * + *******************************************************************************/ + +// Power +static const uint8_t AC1_POWER_OFF = 0x00; +static const uint8_t AC1_POWER_ON = 0x02; + +// Operating Modes +static const uint8_t AC1_MODE_AUTO = 0x00; +static const uint8_t AC1_MODE_COOL = 0x20; +static const uint8_t AC1_MODE_DRY = 0x40; +static const uint8_t AC1_MODE_FAN = 0x60; +static const uint8_t AC1_MODE_HEAT = 0x80; + +// Fan control +static const uint8_t AC1_FAN_AUTO = 0x00; +static const uint8_t AC1_FAN_SILENT = 0x01; +static const uint8_t AC1_FAN1 = 0x60; +static const uint8_t AC1_FAN2 = 0x40; +static const uint8_t AC1_FAN3 = 0x20; +static const uint8_t AC1_FAN_TURBO = 0x08; + +// Vertical Swing +static const uint8_t AC1_VDIR_WIND = 0x00; // "Natural Wind", ignore +static const uint8_t AC1_VDIR_SWING = 0x04; // Swing +static const uint8_t AC1_VDIR_FIXED = 0x08; // Fixed + +// Horizontal Swing +static const uint8_t AC1_HDIR_SWING = 0x00; // Swing +static const uint8_t AC1_HDIR_FIXED = 0x10; // Fixed + +// Temperature range +static const float AC1_TEMP_MIN = 16.0f; +static const float AC1_TEMP_MAX = 32.0f; +static const float AC1_TEMP_INC = 1.0f; + +class ZHLT01Climate : public climate_ir::ClimateIR { + public: + ZHLT01Climate() + : climate_ir::ClimateIR( + AC1_TEMP_MIN, AC1_TEMP_MAX, AC1_TEMP_INC, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, + climate::CLIMATE_SWING_BOTH}, + {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_SLEEP, climate::CLIMATE_PRESET_BOOST}) {} + + void setup() override { climate_ir::ClimateIR::setup(); } + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + /// Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; +}; + +} // namespace zhlt01 +} // namespace esphome diff --git a/esphome/config.py b/esphome/config.py index b04de020e0c8..2b231fc402ee 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1,16 +1,18 @@ +from __future__ import annotations import abc import functools import heapq import logging import re -from typing import Optional, Union +from typing import Union, Any from contextlib import contextmanager +import contextvars import voluptuous as vol -from esphome import core, yaml_util, loader +from esphome import core, yaml_util, loader, pins import esphome.core.config as core_config from esphome.const import ( CONF_ESPHOME, @@ -25,19 +27,30 @@ from esphome.helpers import indent from esphome.util import safe_print, OrderedDict -from esphome.config_helpers import Extend +from esphome.config_helpers import Extend, Remove from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore import esphome.final_validate as fv import esphome.config_validation as cv -from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType +from esphome.types import ConfigType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) def iter_components(config): + for domain, conf in config.items(): + component = get_component(domain) + yield domain, component + if component.is_platform_component: + for p_config in conf: + p_name = f"{domain}.{p_config[CONF_PLATFORM]}" + platform = get_platform(domain, p_config[CONF_PLATFORM]) + yield p_name, platform + + +def iter_component_configs(config): for domain, conf in config.items(): component = get_component(domain) if component.multi_conf: @@ -53,6 +66,7 @@ def iter_components(config): ConfigPath = list[Union[str, int]] +path_context = contextvars.ContextVar("Config path") def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool: @@ -63,7 +77,7 @@ def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool: @functools.total_ordering class _ValidationStepTask: - def __init__(self, priority: float, id_number: int, step: "ConfigValidationStep"): + def __init__(self, priority: float, id_number: int, step: ConfigValidationStep): self.priority = priority self.id_number = id_number self.step = step @@ -109,10 +123,15 @@ def add_error(self, error: vol.Invalid) -> None: last_root = max( i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH ) - error.path = error.path[last_root + 1 :] + # can't change the path so re-create the error + error = vol.Invalid( + message=error.error_message, + path=error.path[last_root + 1 :], + error_type=error.error_type, + ) self.errors.append(error) - def add_validation_step(self, step: "ConfigValidationStep"): + def add_validation_step(self, step: ConfigValidationStep): id_num = self._validation_tasks_id self._validation_tasks_id += 1 heapq.heappush( @@ -129,6 +148,8 @@ def catch_error(self, path=None): path = path or [] try: yield + except cv.FinalExternalInvalid as e: + self.add_error(e) except vol.Invalid as e: e.prepend(path) self.add_error(e) @@ -154,7 +175,7 @@ def set_by_path(self, path, value): conf = conf[key] conf[path[-1]] = value - def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]: + def get_error_for_path(self, path: ConfigPath) -> vol.Invalid | None: for err in self.errors: if self.get_deepest_path(err.path) == path: self.errors.remove(err) @@ -163,7 +184,7 @@ def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]: def get_deepest_document_range_for_path( self, path: ConfigPath, get_key: bool = False - ) -> Optional[ESPHomeDataBase]: + ) -> ESPHomeDataBase | None: data = self doc_range = None for index, path_item in enumerate(path): @@ -192,7 +213,7 @@ def get_deepest_document_range_for_path( return doc_range def get_nested_item( - self, path: ConfigPathType, raise_error: bool = False + self, path: ConfigPath, raise_error: bool = False ) -> ConfigFragmentType: data = self for item_index in path: @@ -223,7 +244,7 @@ def get_path_for_id(self, id: core.ID): return path raise KeyError(f"ID {id} not found in configuration") - def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + def get_config_for_path(self, path: ConfigPath) -> ConfigFragmentType: return self.get_nested_item(path, raise_error=True) @property @@ -274,8 +295,7 @@ class ConfigValidationStep(abc.ABC): priority: float = 0.0 @abc.abstractmethod - def run(self, result: Config) -> None: - ... + def run(self, result: Config) -> None: ... # noqa: E704 class LoadValidationStep(ConfigValidationStep): @@ -296,8 +316,14 @@ def run(self, result: Config) -> None: # Ignore top-level keys starting with a dot return result.add_output_path([self.domain], self.domain) - result[self.domain] = self.conf component = get_component(self.domain) + if ( + component is not None + and component.multi_conf_no_default + and isinstance(self.conf, core.AutoLoad) + ): + self.conf = [] + result[self.domain] = self.conf path = [self.domain] if component is None: result.add_str_error(f"Component not found: {self.domain}", path) @@ -309,10 +335,11 @@ def run(self, result: Config) -> None: if load not in result: result.add_validation_step(AutoLoadValidationStep(load)) + result.add_validation_step( + MetadataValidationStep([self.domain], self.domain, self.conf, component) + ) + if not component.is_platform_component: - result.add_validation_step( - MetadataValidationStep([self.domain], self.domain, self.conf, component) - ) return # This is a platform component, proceed to reading platform entries @@ -343,7 +370,16 @@ def run(self, result: Config) -> None: path + [CONF_ID], ) continue - result.add_str_error("No platform specified! See 'platform' key.", path) + if isinstance(p_id, Remove): + result.add_str_error( + f"Source for removal of ID '{p_id.value}' was not found.", + path + [CONF_ID], + ) + continue + result.add_str_error( + f"'{self.domain}' requires a 'platform' key but it was not specified.", + path, + ) continue # Remove temp output path and construct new one result.remove_output_path(path, p_domain) @@ -411,13 +447,35 @@ def __init__( def run(self, result: Config) -> None: if self.conf is None: - result[self.domain] = self.conf = {} + if self.comp.multi_conf and self.comp.multi_conf_no_default: + result[self.domain] = self.conf = [] + else: + result[self.domain] = self.conf = {} success = True for dependency in self.comp.dependencies: - if dependency not in result: + dependency_parts = dependency.split(".") + if len(dependency_parts) > 2: + result.add_str_error( + "Dependencies must be specified as a single component or in component.platform format only", + self.path, + ) + return + component_dep = dependency_parts[0] + platform_dep = dependency_parts[-1] + if component_dep not in result: + result.add_str_error( + f"Component {self.domain} requires component {component_dep}", + self.path, + ) + success = False + elif component_dep != platform_dep and ( + not isinstance(platform_list := result.get(component_dep), list) + or not any(CONF_PLATFORM in p for p in platform_list) + or not any(p[CONF_PLATFORM] == platform_dep for p in platform_list) + ): result.add_str_error( - f"Component {self.domain} requires component {dependency}", + f"Component {self.domain} requires 'platform: {platform_dep}' in component '{component_dep}'", self.path, ) success = False @@ -487,8 +545,7 @@ def __init__( self.comp = comp def run(self, result: Config) -> None: - if self.comp.config_schema is None: - return + token = path_context.set(self.path) with result.catch_error(self.path): if self.comp.is_platform: # Remove 'platform' key for validation @@ -502,11 +559,12 @@ def run(self, result: Config) -> None: validated["platform"] = platform_val validated.move_to_end("platform", last=False) result.set_by_path(self.path, validated) - else: + elif self.comp.config_schema is not None: schema = cv.Schema(self.comp.config_schema) validated = schema(self.conf) result.set_by_path(self.path, validated) + path_context.reset(token) result.add_validation_step(FinalValidateValidationStep(self.path, self.comp)) @@ -630,6 +688,35 @@ def run(self, result: Config) -> None: ) +class RemoveReferenceValidationStep(ConfigValidationStep): + """ + Make sure all !remove references have been removed from the config. + Any left overs mean the merge step couldn't find corresponding previously existing id/key + """ + + def run(self, result: Config) -> None: + if result.errors: + # If result already has errors, skip this step + return + + def recursive_check_remove_tag(config: Config, path: ConfigPath = None): + path = path or [] + + if isinstance(config, Remove): + result.add_str_error( + f"Source for removal at '{'->'.join([str(p) for p in path])}' was not found.", + path, + ) + elif isinstance(config, list): + for i, item in enumerate(config): + recursive_check_remove_tag(item, path + [i]) + elif isinstance(config, dict): + for key, value in config.items(): + recursive_check_remove_tag(value, path + [key]) + + recursive_check_remove_tag(result) + + class FinalValidateValidationStep(ConfigValidationStep): """Run final_validate_schema for all components.""" @@ -645,19 +732,34 @@ def run(self, result: Config) -> None: # If result already has errors, skip this step return - if self.comp.final_validate_schema is None: - return - token = fv.full_config.set(result) conf = result.get_nested_item(self.path) with result.catch_error(self.path): - self.comp.final_validate_schema(conf) + if self.comp.final_validate_schema is not None: + self.comp.final_validate_schema(conf) fv.full_config.reset(token) -def validate_config(config, command_line_substitutions) -> Config: +class PinUseValidationCheck(ConfigValidationStep): + """Check for pin reuse""" + + priority = -30 # Should happen after component final validations + + def __init__(self) -> None: + pass + + def run(self, result: Config) -> None: + if result.errors: + # If result already has errors, skip this step + return + pins.PIN_SCHEMA_REGISTRY.final_validate(result) + + +def validate_config( + config: dict[str, Any], command_line_substitutions: dict[str, Any] +) -> Config: result = Config() loader.clear_component_meta_finders() @@ -678,11 +780,11 @@ def validate_config(config, command_line_substitutions) -> Config: CORE.raw_config = config # 1. Load substitutions - if CONF_SUBSTITUTIONS in config: + if CONF_SUBSTITUTIONS in config or command_line_substitutions: from esphome.components import substitutions result[CONF_SUBSTITUTIONS] = { - **config[CONF_SUBSTITUTIONS], + **config.get(CONF_SUBSTITUTIONS, {}), **command_line_substitutions, } result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS) @@ -752,6 +854,9 @@ def validate_config(config, command_line_substitutions) -> Config: for domain, conf in config.items(): result.add_validation_step(LoadValidationStep(domain, conf)) result.add_validation_step(IDPassValidationStep()) + result.add_validation_step(PinUseValidationCheck()) + + result.add_validation_step(RemoveReferenceValidationStep()) result.run_validation_steps() @@ -780,6 +885,9 @@ def _get_parent_name(path, config): # Sub-item break return domain + # When processing a list, skip back over the index + while len(path) > 1 and isinstance(path[-1], int): + path = path[:-1] return path[-1] @@ -818,24 +926,23 @@ def __init__(self, base_exc): self.base_exc = base_exc -def _load_config(command_line_substitutions): +def _load_config(command_line_substitutions: dict[str, Any]) -> Config: + """Load the configuration file.""" try: config = yaml_util.load_yaml(CORE.config_path) except EsphomeError as e: raise InvalidYAMLError(e) from e try: - result = validate_config(config, command_line_substitutions) + return validate_config(config, command_line_substitutions) except EsphomeError: raise except Exception: _LOGGER.error("Unexpected exception while reading configuration:") raise - return result - -def load_config(command_line_substitutions): +def load_config(command_line_substitutions: dict[str, Any]) -> Config: try: return _load_config(command_line_substitutions) except vol.Invalid as err: @@ -1002,7 +1109,14 @@ def read_config(command_line_substitutions): if errline: errstr += f" {errline}" safe_print(errstr) - safe_print(indent(dump_dict(res, path)[0])) + split_dump = dump_dict(res, path)[0].splitlines() + # find the last error message + i = len(split_dump) - 1 + while i > 10 and "\033[" not in split_dump[i]: + i = i - 1 + # discard lines more than 4 beyond the last error + i = min(i + 4, len(split_dump)) + safe_print(indent("\n".join(split_dump[:i]))) for err in res.errors: safe_print(color(Fore.BOLD_RED, err.msg)) diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index e1d63775bb1d..7b47e097c81b 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -1,9 +1,4 @@ -import json -import os - from esphome.const import CONF_ID -from esphome.core import CORE -from esphome.helpers import read_file class Extend: @@ -22,23 +17,20 @@ def __eq__(self, b): return isinstance(b, Extend) and self.value == b.value -def read_config_file(path: str) -> str: - if CORE.vscode and ( - not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) - ): - print( - json.dumps( - { - "type": "read_file", - "path": path, - } - ) - ) - data = json.loads(input()) - assert data["type"] == "file_response" - return data["content"] +class Remove: + def __init__(self, value=None): + self.value = value + + def __str__(self): + return f"!remove {self.value}" + + def __eq__(self, b): + """ + Check if two Remove objects contain the same ID. - return read_file(path) + Only used in unit tests. + """ + return isinstance(b, Remove) and self.value == b.value def merge_config(full_old, full_new): @@ -48,7 +40,10 @@ def merge(old, new): return new res = old.copy() for k, v in new.items(): - res[k] = merge(old[k], v) if k in old else v + if isinstance(v, Remove) and k in old: + del res[k] + else: + res[k] = merge(old[k], v) if k in old else v return res if isinstance(new, list): if not isinstance(old, list): @@ -59,6 +54,7 @@ def merge(old, new): for i, v in enumerate(res) if CONF_ID in v and isinstance(v[CONF_ID], str) } + ids_to_delete = [] for v in new: if CONF_ID in v: new_id = v[CONF_ID] @@ -68,9 +64,15 @@ def merge(old, new): v[CONF_ID] = new_id res[ids[new_id]] = merge(res[ids[new_id]], v) continue + elif isinstance(new_id, Remove): + new_id = new_id.value + if new_id in ids: + ids_to_delete.append(ids[new_id]) + continue else: ids[new_id] = len(res) res.append(v) + res = [v for i, v in enumerate(res) if i not in ids_to_delete] return res if new is None: return old diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 344d61b812e7..5fc72921e1f6 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -13,7 +13,7 @@ from esphome import core import esphome.codegen as cg -from esphome.config_helpers import Extend +from esphome.config_helpers import Extend, Remove from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, @@ -29,9 +29,13 @@ CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, + CONF_QOS, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, + CONF_YEAR, + CONF_MONTH, + CONF_DAY, CONF_HOUR, CONF_MINUTE, CONF_SECOND, @@ -51,9 +55,13 @@ KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, TYPE_GIT, TYPE_LOCAL, VALID_SUBSTITUTIONS_CHARACTERS, + __version__ as ESPHOME_VERSION, ) from esphome.core import ( CORE, @@ -63,6 +71,7 @@ TimePeriod, TimePeriodMicroseconds, TimePeriodMilliseconds, + TimePeriodNanoseconds, TimePeriodSeconds, TimePeriodMinutes, ) @@ -179,6 +188,7 @@ "struct", "switch", "template", + "text", "this", "thread_local", "throw", @@ -257,6 +267,10 @@ def __init__(self, key, msg=None): super().__init__(key, msg=msg) +class FinalExternalInvalid(Invalid): + """Represents an invalid value in the final validation phase where the path should not be prepended.""" + + def check_not_templatable(value): if isinstance(value, Lambda): raise Invalid("This option is not templatable!") @@ -294,7 +308,7 @@ def string(value): """Validate that a configuration value is a string. If not, automatically converts to a string. Note that this can be lossy, for example the input value 60.00 (float) will be turned into - "60.0" (string). For values where this could be a problem `string_string` has to be used. + "60.0" (string). For values where this could be a problem `string_strict` has to be used. """ check_not_templatable(value) if isinstance(value, (dict, list)): @@ -527,6 +541,10 @@ def validator(value): if isinstance(value, Extend): raise Invalid(f"Source for extension of ID '{value.value}' was not found.") + + if isinstance(value, Remove): + raise Invalid(f"Source for Removal of ID '{value.value}' was not found.") + return core.ID(validate_id_name(value), is_declaration=True, type=type) return validator @@ -583,9 +601,9 @@ def validator_(obj): return validator_ -only_on_esp32 = only_on("esp32") -only_on_esp8266 = only_on("esp8266") -only_on_rp2040 = only_on("rp2040") +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("arduino") only_with_esp_idf = only_with_framework("esp-idf") @@ -714,6 +732,8 @@ def time_period_str_unit(value): raise Invalid("Expected string for time period with unit.") unit_to_kwarg = { + "ns": "nanoseconds", + "nanoseconds": "nanoseconds", "us": "microseconds", "microseconds": "microseconds", "ms": "milliseconds", @@ -735,7 +755,10 @@ def time_period_str_unit(value): raise Invalid(f"Expected time period with unit, got {value}") kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] - return TimePeriod(**{kwarg: float(match.group(1))}) + try: + return TimePeriod(**{kwarg: float(match.group(1))}) + except ValueError as e: + raise Invalid(e) from e def time_period_in_milliseconds_(value): @@ -745,10 +768,18 @@ def time_period_in_milliseconds_(value): def time_period_in_microseconds_(value): + if value.nanoseconds is not None and value.nanoseconds != 0: + raise Invalid("Maximum precision is microseconds") return TimePeriodMicroseconds(**value.as_dict()) +def time_period_in_nanoseconds_(value): + return TimePeriodNanoseconds(**value.as_dict()) + + def time_period_in_seconds_(value): + if value.nanoseconds is not None and value.nanoseconds != 0: + raise Invalid("Maximum precision is seconds") if value.microseconds is not None and value.microseconds != 0: raise Invalid("Maximum precision is seconds") if value.milliseconds is not None and value.milliseconds != 0: @@ -757,6 +788,8 @@ def time_period_in_seconds_(value): def time_period_in_minutes_(value): + if value.nanoseconds is not None and value.nanoseconds != 0: + raise Invalid("Maximum precision is minutes") if value.microseconds is not None and value.microseconds != 0: raise Invalid("Maximum precision is minutes") if value.milliseconds is not None and value.milliseconds != 0: @@ -783,27 +816,114 @@ def update_interval(value): positive_time_period_microseconds = All( positive_time_period, time_period_in_microseconds_ ) +positive_time_period_nanoseconds = All( + positive_time_period, time_period_in_nanoseconds_ +) positive_not_null_time_period = All( time_period, Range(min=TimePeriod(), min_included=False) ) def time_of_day(value): - value = string(value) - try: - date = datetime.strptime(value, "%H:%M:%S") - except ValueError as err: + return date_time(date=False, time=True)(value) + + +def date_time(date: bool, time: bool): + + pattern_str = r"^" # Start of string + if date: + pattern_str += r"\d{4}-\d{1,2}-\d{1,2}" + if time: + pattern_str += r" " + if time: + pattern_str += ( + r"\d{1,2}:\d{2}" # Hour/Minute + r"(:\d{2})?" # 1. Seconds + r"(" # 2. Optional AM/PM group + r"(\s)?" # 3. Optional Space + r"(?:AM|PM|am|pm)" # AM/PM string matching + r")?" # End optional AM/PM group + ) + pattern_str += r"$" # End of string + + pattern = re.compile(pattern_str) + + exc_message = "" + if date: + exc_message += "date" + if time: + exc_message += "time" + + schema = Schema({}) + if date: + schema = schema.extend( + { + Required(CONF_YEAR): int_range(min=1970, max=3000), + Required(CONF_MONTH): int_range(min=1, max=12), + Required(CONF_DAY): int_range(min=1, max=31), + } + ) + if time: + schema = schema.extend( + { + Required(CONF_HOUR): int_range(min=0, max=23), + Required(CONF_MINUTE): int_range(min=0, max=59), + Required(CONF_SECOND): int_range(min=0, max=59), + } + ) + + def validator(value): + if isinstance(value, dict): + return schema(value) + value = string(value) + + match = pattern.match(value) + if match is None: + # pylint: disable=raise-missing-from + raise Invalid(f"Invalid {exc_message}: {value}") + + if time: + has_seconds = match[1] is not None + has_ampm = match[2] is not None + has_ampm_space = match[3] is not None + + format = "" + if date: + format += "%Y-%m-%d" + if time: + format += " " + if time: + if has_ampm: + format += "%I:%M" + else: + format += "%H:%M" + if has_seconds: + format += ":%S" + if has_ampm_space: + format += " " + if has_ampm: + format += "%p" + try: - date = datetime.strptime(value, "%H:%M:%S %p") - except ValueError: + date_obj = datetime.strptime(value, format) + except ValueError as err: # pylint: disable=raise-missing-from - raise Invalid(f"Invalid time of day: {err}") + raise Invalid(f"Invalid {exc_message}: {err}") - return { - CONF_HOUR: date.hour, - CONF_MINUTE: date.minute, - CONF_SECOND: date.second, - } + return_value = {} + if date: + return_value[CONF_YEAR] = date_obj.year + return_value[CONF_MONTH] = date_obj.month + return_value[CONF_DAY] = date_obj.day + + if time: + return_value[CONF_HOUR] = date_obj.hour + return_value[CONF_MINUTE] = date_obj.minute + return_value[CONF_SECOND] = date_obj.second if has_seconds else 0 + + return schema(return_value) + + return validator def mac_address(value): @@ -929,6 +1049,27 @@ def temperature(value): raise err +def temperature_delta(value): + err = None + try: + return _temperature_c(value) + except Invalid as orig_err: + err = orig_err + + try: + return _temperature_k(value) + except Invalid: + pass + + try: + fahrenheit = _temperature_f(value) + return fahrenheit * (5 / 9) + except Invalid: + pass + + raise err + + _color_temperature_mireds = float_with_unit("Color Temperature", r"(mireds|Mireds)") _color_temperature_kelvin = float_with_unit("Color Temperature", r"(K|Kelvin)") @@ -1022,6 +1163,8 @@ def ipv4(value): def _valid_topic(value): """Validate that this is a valid topic name/filter.""" + if value is None: # Used to disable publishing and subscribing + return "" if isinstance(value, dict): raise Invalid("Can't use dictionary with topic") value = string(value) @@ -1444,6 +1587,10 @@ def typed_schema(schemas, **kwargs): """Create a schema that has a key to distinguish between schemas""" key = kwargs.pop("key", CONF_TYPE) default_schema_option = kwargs.pop("default_type", None) + enum_mapping = kwargs.pop("enum", None) + if enum_mapping is not None: + assert isinstance(enum_mapping, dict) + assert set(enum_mapping.keys()) == set(schemas.keys()) key_validator = one_of(*schemas, **kwargs) def validator(value): @@ -1454,6 +1601,9 @@ def validator(value): if schema_option is None: raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) + if enum_mapping is not None: + key_v = add_class_to_obj(key_v, core.EnumValue) + key_v.enum_value = enum_mapping[key_v] value = Schema(schemas[key_v])(value) value[key] = key_v return value @@ -1468,6 +1618,13 @@ def __init__(self, key=CONF_ID): super().__init__(key, default=lambda: None) +def _get_priority_default(*args): + for arg in args: + if arg is not vol.UNDEFINED: + return arg + return vol.UNDEFINED + + class SplitDefault(Optional): """Mark this key to have a split default for ESP8266/ESP32.""" @@ -1478,6 +1635,15 @@ def __init__( esp32=vol.UNDEFINED, esp32_arduino=vol.UNDEFINED, esp32_idf=vol.UNDEFINED, + esp32_s2=vol.UNDEFINED, + esp32_s2_arduino=vol.UNDEFINED, + esp32_s2_idf=vol.UNDEFINED, + esp32_s3=vol.UNDEFINED, + esp32_s3_arduino=vol.UNDEFINED, + esp32_s3_idf=vol.UNDEFINED, + esp32_c3=vol.UNDEFINED, + esp32_c3_arduino=vol.UNDEFINED, + esp32_c3_idf=vol.UNDEFINED, rp2040=vol.UNDEFINED, bk72xx=vol.UNDEFINED, rtl87xx=vol.UNDEFINED, @@ -1486,10 +1652,28 @@ def __init__( super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) self._esp32_arduino_default = vol.default_factory( - esp32_arduino if esp32 is vol.UNDEFINED else esp32 + _get_priority_default(esp32_arduino, esp32) ) self._esp32_idf_default = vol.default_factory( - esp32_idf if esp32 is vol.UNDEFINED else esp32 + _get_priority_default(esp32_idf, esp32) + ) + self._esp32_s2_arduino_default = vol.default_factory( + _get_priority_default(esp32_s2_arduino, esp32_s2, esp32_arduino, esp32) + ) + self._esp32_s2_idf_default = vol.default_factory( + _get_priority_default(esp32_s2_idf, esp32_s2, esp32_idf, esp32) + ) + self._esp32_s3_arduino_default = vol.default_factory( + _get_priority_default(esp32_s3_arduino, esp32_s3, esp32_arduino, esp32) + ) + self._esp32_s3_idf_default = vol.default_factory( + _get_priority_default(esp32_s3_idf, esp32_s3, esp32_idf, esp32) + ) + self._esp32_c3_arduino_default = vol.default_factory( + _get_priority_default(esp32_c3_arduino, esp32_c3, esp32_arduino, esp32) + ) + self._esp32_c3_idf_default = vol.default_factory( + _get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32) ) self._rp2040_default = vol.default_factory(rp2040) self._bk72xx_default = vol.default_factory(bk72xx) @@ -1500,10 +1684,35 @@ def __init__( def default(self): if CORE.is_esp8266: return self._esp8266_default - if CORE.is_esp32 and CORE.using_arduino: - return self._esp32_arduino_default - if CORE.is_esp32 and CORE.using_esp_idf: - return self._esp32_idf_default + if CORE.is_esp32: + from esphome.components.esp32 import get_esp32_variant + from esphome.components.esp32.const import ( + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, + ) + + variant = get_esp32_variant() + if variant == VARIANT_ESP32S2: + if CORE.using_arduino: + return self._esp32_s2_arduino_default + if CORE.using_esp_idf: + return self._esp32_s2_idf_default + elif variant == VARIANT_ESP32S3: + if CORE.using_arduino: + return self._esp32_s3_arduino_default + if CORE.using_esp_idf: + return self._esp32_s3_idf_default + elif variant == VARIANT_ESP32C3: + if CORE.using_arduino: + return self._esp32_c3_arduino_default + if CORE.using_esp_idf: + return self._esp32_c3_idf_default + else: + if CORE.using_arduino: + return self._esp32_arduino_default + if CORE.using_esp_idf: + return self._esp32_idf_default if CORE.is_rp2040: return self._rp2040_default if CORE.is_bk72xx: @@ -1648,7 +1857,7 @@ def validate(value): return validate -_ENTITY_CATEGORIES = { +ENTITY_CATEGORIES = { ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE, ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC, @@ -1656,7 +1865,7 @@ def validate(value): def entity_category(value): - return enum(_ENTITY_CATEGORIES, lower=True)(value) + return enum(ENTITY_CATEGORIES, lower=True)(value) MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( @@ -1669,6 +1878,7 @@ def entity_category(value): MQTT_COMPONENT_SCHEMA = Schema( { + Optional(CONF_QOS): All(requires_component("mqtt"), int_range(min=0, max=2)), Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), @@ -1786,6 +1996,16 @@ def version_number(value): raise Invalid("Not a valid version number") from e +def validate_esphome_version(value: str): + min_version = Version.parse(value) + current_version = Version.parse(ESPHOME_VERSION) + if current_version < min_version: + raise Invalid( + f"Your ESPHome version is too old. Please update to at least {min_version}" + ) + return value + + def platformio_version_constraint(value): # for documentation on valid version constraints: # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install @@ -1895,15 +2115,20 @@ def suppress_invalid(): pass -GIT_SCHEMA = { - Required(CONF_URL): url, - Optional(CONF_REF): git_ref, - Optional(CONF_USERNAME): string, - Optional(CONF_PASSWORD): string, -} -LOCAL_SCHEMA = { - Required(CONF_PATH): directory, -} +GIT_SCHEMA = Schema( + { + Required(CONF_URL): url, + Optional(CONF_REF): git_ref, + Optional(CONF_USERNAME): string, + Optional(CONF_PASSWORD): string, + Optional(CONF_PATH): string, + } +) +LOCAL_SCHEMA = Schema( + { + Required(CONF_PATH): directory, + } +) def validate_source_shorthand(value): @@ -1944,8 +2169,8 @@ def validate_source_shorthand(value): validate_source_shorthand, typed_schema( { - TYPE_GIT: Schema(GIT_SCHEMA), - TYPE_LOCAL: Schema(LOCAL_SCHEMA), + TYPE_GIT: GIT_SCHEMA, + TYPE_LOCAL: LOCAL_SCHEMA, } ), ) diff --git a/esphome/const.py b/esphome/const.py index b0ee7a0f42f6..542183d3b2b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.9.0-dev" +__version__ = "2024.6.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -46,13 +46,16 @@ CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADVANCED = "advanced" CONF_AFTER = "after" +CONF_ALLOW_OTHER_USES = "allow_other_uses" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" CONF_ANALOG = "analog" CONF_AND = "and" +CONF_ANGLE = "angle" CONF_AP = "ap" CONF_APPARENT_POWER = "apparent_power" CONF_ARDUINO_VERSION = "arduino_version" +CONF_AREA = "area" CONF_ARGS = "args" CONF_ASSUMED_STATE = "assumed_state" CONF_AT = "at" @@ -94,6 +97,7 @@ CONF_BUILD_PATH = "build_path" CONF_BUS_VOLTAGE = "bus_voltage" CONF_BUSY_PIN = "busy_pin" +CONF_BUTTON = "button" CONF_BYTES = "bytes" CONF_CALCULATED_LUX = "calculated_lux" CONF_CALIBRATE_LINEAR = "calibrate_linear" @@ -108,8 +112,11 @@ CONF_CHANNEL = "channel" CONF_CHANNELS = "channels" CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_CHECK = "check" CONF_CHIPSET = "chipset" CONF_CLEAR_IMPEDANCE = "clear_impedance" +CONF_CLIENT_CERTIFICATE = "client_certificate" +CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" CONF_CLIENT_ID = "client_id" CONF_CLK_PIN = "clk_pin" CONF_CLOCK_PIN = "clock_pin" @@ -118,6 +125,7 @@ CONF_CLOSE_ENDSTOP = "close_endstop" CONF_CO2 = "co2" CONF_CODE = "code" +CONF_COL = "col" CONF_COLD_WHITE = "cold_white" CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLOR = "color" @@ -125,14 +133,17 @@ CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" +CONF_COLOR_ORDER = "color_order" CONF_COLOR_PALETTE = "color_palette" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command" +CONF_COMMAND_REPEATS = "command_repeats" CONF_COMMAND_RETAIN = "command_retain" CONF_COMMAND_TOPIC = "command_topic" CONF_COMMENT = "comment" CONF_COMMIT = "commit" +CONF_COMPENSATION = "compensation" CONF_COMPILE_PROCESS_LIMIT = "compile_process_limit" CONF_COMPONENT_ID = "component_id" CONF_COMPONENTS = "components" @@ -154,6 +165,7 @@ CONF_CSS_INCLUDE = "css_include" CONF_CSS_URL = "css_url" CONF_CURRENT = "current" +CONF_CURRENT_HUMIDITY_STATE_TOPIC = "current_humidity_state_topic" CONF_CURRENT_OPERATION = "current_operation" CONF_CURRENT_RESISTOR = "current_resistor" CONF_CURRENT_TEMPERATURE_STATE_TOPIC = "current_temperature_state_topic" @@ -169,6 +181,9 @@ CONF_DATA_PINS = "data_pins" CONF_DATA_RATE = "data_rate" CONF_DATA_TEMPLATE = "data_template" +CONF_DATE = "date" +CONF_DATETIME = "datetime" +CONF_DAY = "day" CONF_DAYS_OF_MONTH = "days_of_month" CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" @@ -181,6 +196,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" +CONF_DEFAULTS = "defaults" CONF_DELAY = "delay" CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" @@ -203,6 +219,8 @@ CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" +CONF_DISPLAY = "display" +CONF_DISPLAY_ID = "display_id" CONF_DISTANCE = "distance" CONF_DITHER = "dither" CONF_DIV_RATIO = "div_ratio" @@ -210,6 +228,7 @@ CONF_DNS1 = "dns1" CONF_DNS2 = "dns2" CONF_DOMAIN = "domain" +CONF_DOOYA = "dooya" CONF_DRY_ACTION = "dry_action" CONF_DRY_MODE = "dry_mode" CONF_DUMMY_RECEIVER = "dummy_receiver" @@ -225,7 +244,9 @@ CONF_ELSE = "else" CONF_ENABLE_BTM = "enable_btm" CONF_ENABLE_IPV6 = "enable_ipv6" +CONF_ENABLE_ON_BOOT = "enable_on_boot" CONF_ENABLE_PIN = "enable_pin" +CONF_ENABLE_PRIVATE_NETWORK_ACCESS = "enable_private_network_access" CONF_ENABLE_RRM = "enable_rrm" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" @@ -237,12 +258,16 @@ CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" CONF_EVENT = "event" +CONF_EVENT_TYPE = "event_type" +CONF_EVENT_TYPES = "event_types" CONF_EXPIRE_AFTER = "expire_after" CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy" CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy" CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input" CONF_EXTERNAL_COMPONENTS = "external_components" +CONF_EXTERNAL_TEMPERATURE = "external_temperature" CONF_EXTERNAL_VCC = "external_vcc" +CONF_FACTORY_RESET = "factory_reset" CONF_FALLING_EDGE = "falling_edge" CONF_FAMILY = "family" CONF_FAN_MODE = "fan_mode" @@ -265,6 +290,9 @@ CONF_FAN_WITH_COOLING = "fan_with_cooling" CONF_FAN_WITH_HEATING = "fan_with_heating" CONF_FAST_CONNECT = "fast_connect" +CONF_FIELD_STRENGTH_X = "field_strength_x" +CONF_FIELD_STRENGTH_Y = "field_strength_y" +CONF_FIELD_STRENGTH_Z = "field_strength_z" CONF_FILE = "file" CONF_FILES = "files" CONF_FILTER = "filter" @@ -298,8 +326,15 @@ CONF_GPIO = "gpio" CONF_GREEN = "green" CONF_GROUP = "group" +CONF_GYROSCOPE_X = "gyroscope_x" +CONF_GYROSCOPE_Y = "gyroscope_y" +CONF_GYROSCOPE_Z = "gyroscope_z" CONF_HARDWARE_UART = "hardware_uart" +CONF_HAS_MOVING_TARGET = "has_moving_target" +CONF_HAS_STILL_TARGET = "has_still_target" +CONF_HAS_TARGET = "has_target" CONF_HEAD = "head" +CONF_HEADING = "heading" CONF_HEARTBEAT = "heartbeat" CONF_HEAT_ACTION = "heat_action" CONF_HEAT_DEADBAND = "heat_deadband" @@ -313,7 +348,9 @@ CONF_HIGH_VOLTAGE_REFERENCE = "high_voltage_reference" CONF_HOUR = "hour" CONF_HOURS = "hours" +CONF_HSYNC_PIN = "hsync_pin" CONF_HUMIDITY = "humidity" +CONF_HUMIDITY_SENSOR = "humidity_sensor" CONF_HYSTERESIS = "hysteresis" CONF_I2C = "i2c" CONF_I2C_ID = "i2c_id" @@ -329,6 +366,8 @@ CONF_IDLE_TIME = "idle_time" CONF_IF = "if" CONF_IGNORE_EFUSE_MAC_CRC = "ignore_efuse_mac_crc" +CONF_IGNORE_OUT_OF_RANGE = "ignore_out_of_range" +CONF_IGNORE_STRAPPING_WARNING = "ignore_strapping_warning" CONF_IIR_FILTER = "iir_filter" CONF_ILLUMINANCE = "illuminance" CONF_IMPEDANCE = "impedance" @@ -350,13 +389,16 @@ CONF_INTERNAL = "internal" CONF_INTERNAL_FILTER = "internal_filter" CONF_INTERNAL_FILTER_MODE = "internal_filter_mode" +CONF_INTERNAL_TEMPERATURE = "internal_temperature" CONF_INTERRUPT = "interrupt" CONF_INTERRUPT_PIN = "interrupt_pin" CONF_INTERVAL = "interval" CONF_INVALID_COOLDOWN = "invalid_cooldown" CONF_INVERT = "invert" +CONF_INVERT_COLORS = "invert_colors" CONF_INVERTED = "inverted" CONF_IP_ADDRESS = "ip_address" +CONF_IRQ_PIN = "irq_pin" CONF_JS_INCLUDE = "js_include" CONF_JS_URL = "js_url" CONF_JVC = "jvc" @@ -417,6 +459,7 @@ CONF_MEDIA_PLAYER = "media_player" CONF_MEDIUM = "medium" CONF_MEMORY_BLOCKS = "memory_blocks" +CONF_MESSAGE = "message" CONF_METHOD = "method" CONF_MICROPHONE = "microphone" CONF_MIN_BRIGHTNESS = "min_brightness" @@ -428,6 +471,7 @@ CONF_MIN_HEATING_OFF_TIME = "min_heating_off_time" CONF_MIN_HEATING_RUN_TIME = "min_heating_run_time" CONF_MIN_IDLE_TIME = "min_idle_time" +CONF_MIN_IPV6_ADDR_COUNT = "min_ipv6_addr_count" CONF_MIN_LENGTH = "min_length" CONF_MIN_LEVEL = "min_level" CONF_MIN_POWER = "min_power" @@ -438,12 +482,15 @@ CONF_MIN_VERSION = "min_version" CONF_MINUTE = "minute" CONF_MINUTES = "minutes" +CONF_MIRROR_X = "mirror_x" +CONF_MIRROR_Y = "mirror_y" CONF_MISO_PIN = "miso_pin" CONF_MODE = "mode" CONF_MODE_COMMAND_TOPIC = "mode_command_topic" CONF_MODE_STATE_TOPIC = "mode_state_topic" CONF_MODEL = "model" CONF_MOISTURE = "moisture" +CONF_MONTH = "month" CONF_MONTHS = "months" CONF_MOSI_PIN = "mosi_pin" CONF_MOTION = "motion" @@ -466,29 +513,44 @@ CONF_NUM_SCANS = "num_scans" CONF_NUMBER = "number" CONF_NUMBER_DATAPOINT = "number_datapoint" +CONF_OE_PIN = "oe_pin" CONF_OFF_MODE = "off_mode" +CONF_OFF_SPEED_CYCLE = "off_speed_cycle" CONF_OFFSET = "offset" +CONF_OFFSET_HEIGHT = "offset_height" +CONF_OFFSET_WIDTH = "offset_width" CONF_ON = "on" CONF_ON_BLE_ADVERTISE = "on_ble_advertise" CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise" CONF_ON_BLE_SERVICE_DATA_ADVERTISE = "on_ble_service_data_advertise" CONF_ON_BOOT = "on_boot" CONF_ON_CLICK = "on_click" +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_DIRECTION_SET = "on_direction_set" CONF_ON_DISCONNECT = "on_disconnect" CONF_ON_DOUBLE_CLICK = "on_double_click" CONF_ON_ENROLLMENT_DONE = "on_enrollment_done" CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed" CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" +CONF_ON_EVENT = "on_event" +CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" +CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced" +CONF_ON_FINGER_SCAN_START = "on_finger_scan_start" CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" +CONF_ON_FINISHED_WRITE = "on_finished_write" +CONF_ON_IDLE = "on_idle" CONF_ON_JSON_MESSAGE = "on_json_message" CONF_ON_LOCK = "on_lock" CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" CONF_ON_OPEN = "on_open" +CONF_ON_OSCILLATING_SET = "on_oscillating_set" +CONF_ON_PRESET_SET = "on_preset_set" CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" CONF_ON_RELEASE = "on_release" @@ -504,6 +566,7 @@ CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" CONF_ON_UNLOCK = "on_unlock" +CONF_ON_UPDATE = "on_update" CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" @@ -522,6 +585,7 @@ CONF_OSCILLATION_OUTPUT = "oscillation_output" CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic" CONF_OTA = "ota" +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_OUTPUT = "output" CONF_OUTPUT_ID = "output_id" CONF_OUTPUTS = "outputs" @@ -533,13 +597,17 @@ CONF_PARAMETERS = "parameters" CONF_PASSWORD = "password" CONF_PATH = "path" +CONF_PATTERN = "pattern" CONF_PAYLOAD = "payload" CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PERIOD = "period" CONF_PH = "ph" +CONF_PHASE_A = "phase_a" CONF_PHASE_ANGLE = "phase_angle" +CONF_PHASE_B = "phase_b" CONF_PHASE_BALANCER = "phase_balancer" +CONF_PHASE_C = "phase_c" CONF_PIN = "pin" CONF_PIN_A = "pin_a" CONF_PIN_B = "pin_b" @@ -548,6 +616,7 @@ CONF_PINS = "pins" CONF_PIXEL_MAPPER = "pixel_mapper" CONF_PLATFORM = "platform" +CONF_PLATFORM_VERSION = "platform_version" CONF_PLATFORMIO_OPTIONS = "platformio_options" CONF_PM_0_3UM = "pm_0_3um" CONF_PM_0_5UM = "pm_0_5um" @@ -582,6 +651,7 @@ CONF_PRESET_BOOST = "preset_boost" CONF_PRESET_COMMAND_TOPIC = "preset_command_topic" CONF_PRESET_ECO = "preset_eco" +CONF_PRESET_MODES = "preset_modes" CONF_PRESET_SLEEP = "preset_sleep" CONF_PRESET_STATE_TOPIC = "preset_state_topic" CONF_PRESSURE = "pressure" @@ -614,6 +684,7 @@ CONF_REF = "ref" CONF_REFERENCE_RESISTANCE = "reference_resistance" CONF_REFERENCE_TEMPERATURE = "reference_temperature" +CONF_REFERENCE_VOLTAGE = "reference_voltage" CONF_REFRESH = "refresh" CONF_RELABEL = "relabel" CONF_REPEAT = "repeat" @@ -622,6 +693,7 @@ CONF_RESET_PIN = "reset_pin" CONF_RESIZE = "resize" CONF_RESOLUTION = "resolution" +CONF_RESTART = "restart" CONF_RESTORE = "restore" CONF_RESTORE_MODE = "restore_mode" CONF_RESTORE_STATE = "restore_state" @@ -632,7 +704,9 @@ CONF_RGB_ORDER = "rgb_order" CONF_RGBW = "rgbw" CONF_RISING_EDGE = "rising_edge" +CONF_RMT_CHANNEL = "rmt_channel" CONF_ROTATION = "rotation" +CONF_ROW = "row" CONF_RS_PIN = "rs_pin" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_WIRES = "rtd_wires" @@ -658,6 +732,7 @@ CONF_SEND_EVERY = "send_every" CONF_SEND_FIRST_AT = "send_first_at" CONF_SENSING_PIN = "sensing_pin" +CONF_SENSITIVITY = "sensitivity" CONF_SENSOR = "sensor" CONF_SENSOR_DATAPOINT = "sensor_datapoint" CONF_SENSOR_ID = "sensor_id" @@ -667,6 +742,7 @@ CONF_SERVICE = "service" CONF_SERVICE_UUID = "service_uuid" CONF_SERVICES = "services" +CONF_SET_ACTION = "set_action" CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential" CONF_SETUP_MODE = "setup_mode" CONF_SETUP_PRIORITY = "setup_priority" @@ -692,6 +768,7 @@ CONF_SPEED_LEVEL_COMMAND_TOPIC = "speed_level_command_topic" CONF_SPEED_LEVEL_STATE_TOPIC = "speed_level_state_topic" CONF_SPEED_STATE_TOPIC = "speed_state_topic" +CONF_SPI = "spi" CONF_SPI_ID = "spi_id" CONF_SPIKE_REJECTION = "spike_rejection" CONF_SSID = "ssid" @@ -722,6 +799,7 @@ CONF_SUPPORTED_SWING_MODES = "supported_swing_modes" CONF_SUPPORTS_COOL = "supports_cool" CONF_SUPPORTS_HEAT = "supports_heat" +CONF_SWAP_XY = "swap_xy" CONF_SWING_BOTH_ACTION = "swing_both_action" CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action" CONF_SWING_MODE = "swing_mode" @@ -735,6 +813,8 @@ CONF_TABLET = "tablet" CONF_TAG = "tag" CONF_TARGET = "target" +CONF_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic" +CONF_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic" CONF_TARGET_TEMPERATURE = "target_temperature" CONF_TARGET_TEMPERATURE_CHANGE_ACTION = "target_temperature_change_action" CONF_TARGET_TEMPERATURE_COMMAND_TOPIC = "target_temperature_command_topic" @@ -746,6 +826,7 @@ CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" +CONF_TEMPERATURE_OFFSET = "temperature_offset" CONF_TEMPERATURE_SOURCE = "temperature_source" CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT_SENSORS = "text_sensors" @@ -770,9 +851,11 @@ CONF_TOTAL = "total" CONF_TOTAL_POWER = "total_power" CONF_TRACES = "traces" +CONF_TRANSFORM = "transform" CONF_TRANSITION_LENGTH = "transition_length" CONF_TRIGGER_ID = "trigger_id" CONF_TRIGGER_PIN = "trigger_pin" +CONF_TUNE_ANTENNA = "tune_antenna" CONF_TURN_OFF_ACTION = "turn_off_action" CONF_TURN_ON_ACTION = "turn_on_action" CONF_TVOC = "tvoc" @@ -792,6 +875,7 @@ CONF_URL = "url" CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" +CONF_USE_FAHRENHEIT = "use_fahrenheit" CONF_USERNAME = "username" CONF_UUID = "uuid" CONF_VALIDITY_PERIOD = "validity_period" @@ -800,11 +884,15 @@ CONF_VARIABLES = "variables" CONF_VARIANT = "variant" CONF_VERSION = "version" +CONF_VIBRATIONS = "vibrations" CONF_VISIBLE = "visible" CONF_VISUAL = "visual" CONF_VOLTAGE = "voltage" CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" CONF_VOLTAGE_DIVIDER = "voltage_divider" +CONF_VOLTAGE_GAIN = "voltage_gain" +CONF_VOLUME = "volume" +CONF_VSYNC_PIN = "vsync_pin" CONF_WAIT_TIME = "wait_time" CONF_WAIT_UNTIL = "wait_until" CONF_WAKEUP_PIN = "wakeup_pin" @@ -825,6 +913,7 @@ CONF_WRITE_PIN = "write_pin" CONF_X_GRID = "x_grid" CONF_Y_GRID = "y_grid" +CONF_YEAR = "year" CONF_ZERO = "zero" TYPE_GIT = "git" @@ -855,6 +944,7 @@ ICON_CURRENT_AC = "mdi:current-ac" ICON_DATABASE = "mdi:database" ICON_EMPTY = "" +ICON_FAN = "mdi:fan" ICON_FINGERPRINT = "mdi:fingerprint" ICON_FLASH = "mdi:flash" ICON_FLASK = "mdi:flask" @@ -863,6 +953,10 @@ ICON_GAS_CYLINDER = "mdi:gas-cylinder" ICON_GAUGE = "mdi:gauge" ICON_GRAIN = "mdi:grain" +ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" +ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" +ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" +ICON_HEATING_COIL = "mdi:heating-coil" ICON_KEY_PLUS = "mdi:key-plus" ICON_LIGHTBULB = "mdi:lightbulb" ICON_MAGNET = "mdi:magnet" @@ -890,6 +984,7 @@ ICON_THERMOMETER = "mdi:thermometer" ICON_TIMELAPSE = "mdi:timelapse" ICON_TIMER = "mdi:timer-outline" +ICON_VIBRATE = "mdi:vibrate" ICON_WATER = "mdi:water" ICON_WATER_PERCENT = "mdi:water-percent" ICON_WEATHER_SUNSET = "mdi:weather-sunset" @@ -904,7 +999,7 @@ UNIT_CELSIUS = "°C" UNIT_CENTIMETER = "cm" UNIT_COUNT_DECILITRE = "/dL" -UNIT_COUNTS_PER_CUBIC_METER = "#/m³" +UNIT_COUNTS_PER_CUBIC_CENTIMETER = "#/cm³" UNIT_CUBIC_METER = "m³" UNIT_CUBIC_METER_PER_HOUR = "m³/h" UNIT_DECIBEL = "dB" @@ -931,6 +1026,7 @@ UNIT_MICROGRAMS_PER_CUBIC_METER = "µg/m³" UNIT_MICROMETER = "µm" UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" +UNIT_MICROSILVERTS_PER_HOUR = "µSv/h" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" UNIT_MILLISECOND = "ms" @@ -962,6 +1058,7 @@ DEVICE_CLASS_BATTERY = "battery" DEVICE_CLASS_BATTERY_CHARGING = "battery_charging" DEVICE_CLASS_BLIND = "blind" +DEVICE_CLASS_BUTTON = "button" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_COLD = "cold" @@ -974,6 +1071,7 @@ DEVICE_CLASS_DATE = "date" DEVICE_CLASS_DISTANCE = "distance" DEVICE_CLASS_DOOR = "door" +DEVICE_CLASS_DOORBELL = "doorbell" DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_ENERGY = "energy" @@ -1035,6 +1133,7 @@ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts" DEVICE_CLASS_VOLTAGE = "voltage" DEVICE_CLASS_VOLUME = "volume" +DEVICE_CLASS_VOLUME_FLOW_RATE = "volume_flow_rate" DEVICE_CLASS_VOLUME_STORAGE = "volume_storage" DEVICE_CLASS_WATER = "water" DEVICE_CLASS_WEIGHT = "weight" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index d9b16038948d..8d010e53cfdd 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -15,13 +15,19 @@ KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, + PLATFORM_RP2040, + PLATFORM_HOST, ) from esphome.coroutine import FakeAwaitable as _FakeAwaitable from esphome.coroutine import FakeEventLoop as _FakeEventLoop # pylint: disable=unused-import from esphome.coroutine import coroutine, coroutine_with_priority # noqa -from esphome.helpers import ensure_unique_string, is_ha_addon +from esphome.helpers import ensure_unique_string, get_str_env, is_ha_addon from esphome.util import OrderedDict if TYPE_CHECKING: @@ -81,6 +87,7 @@ def is_approximately_integer(value): class TimePeriod: def __init__( self, + nanoseconds=None, microseconds=None, milliseconds=None, seconds=None, @@ -130,13 +137,23 @@ def __init__( if microseconds is not None: if not is_approximately_integer(microseconds): - raise ValueError("Maximum precision is microseconds") + frac_microseconds, microseconds = math.modf(microseconds) + nanoseconds = (nanoseconds or 0) + frac_microseconds * 1000 self.microseconds = int(round(microseconds)) else: self.microseconds = None + if nanoseconds is not None: + if not is_approximately_integer(nanoseconds): + raise ValueError("Maximum precision is nanoseconds") + self.nanoseconds = int(round(nanoseconds)) + else: + self.nanoseconds = None + def as_dict(self): out = OrderedDict() + if self.nanoseconds is not None: + out["nanoseconds"] = self.nanoseconds if self.microseconds is not None: out["microseconds"] = self.microseconds if self.milliseconds is not None: @@ -152,6 +169,8 @@ def as_dict(self): return out def __str__(self): + if self.nanoseconds is not None: + return f"{self.total_nanoseconds}ns" if self.microseconds is not None: return f"{self.total_microseconds}us" if self.milliseconds is not None: @@ -167,7 +186,11 @@ def __str__(self): return "0s" def __repr__(self): - return f"TimePeriod<{self.total_microseconds}>" + return f"TimePeriod<{self.total_nanoseconds}ns>" + + @property + def total_nanoseconds(self): + return self.total_microseconds * 1000 + (self.nanoseconds or 0) @property def total_microseconds(self): @@ -195,35 +218,39 @@ def total_days(self): def __eq__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds == other.total_microseconds + return self.total_nanoseconds == other.total_nanoseconds return NotImplemented def __ne__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds != other.total_microseconds + return self.total_nanoseconds != other.total_nanoseconds return NotImplemented def __lt__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds < other.total_microseconds + return self.total_nanoseconds < other.total_nanoseconds return NotImplemented def __gt__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds > other.total_microseconds + return self.total_nanoseconds > other.total_nanoseconds return NotImplemented def __le__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds <= other.total_microseconds + return self.total_nanoseconds <= other.total_nanoseconds return NotImplemented def __ge__(self, other): if isinstance(other, TimePeriod): - return self.total_microseconds >= other.total_microseconds + return self.total_nanoseconds >= other.total_nanoseconds return NotImplemented +class TimePeriodNanoseconds(TimePeriod): + pass + + class TimePeriodMicroseconds(TimePeriod): pass @@ -313,6 +340,8 @@ def resolve(self, registered_ids): if self.id is None: base = str(self.type).replace("::", "_").lower() + if base == self.type: + base = base + "_id" name = "".join(c for c in base if c.isalnum() or c == "_") used = set(registered_ids) | set(RESERVED_IDS) | CORE.loaded_integrations self.id = ensure_unique_string(name, used) @@ -458,6 +487,8 @@ def __init__(self): self.name: Optional[str] = None # The friendly name of the node self.friendly_name: Optional[str] = None + # The area / zone of the node + self.area: Optional[str] = None # Additional data components can store temporary data in # The first key to this dict should always be the integration name self.data = {} @@ -493,11 +524,16 @@ def __init__(self): self.component_ids = set() # Whether ESPHome was started in verbose mode self.verbose = False + # Whether ESPHome was started in quiet mode + self.quiet = False def reset(self): + from esphome.pins import PIN_SCHEMA_REGISTRY + self.dashboard = False self.name = None self.friendly_name = None + self.area = None self.data = {} self.config_path = None self.build_path = None @@ -513,6 +549,7 @@ def reset(self): self.platformio_options = {} self.loaded_integrations = set() self.component_ids = set() + PIN_SCHEMA_REGISTRY.reset() @property def address(self) -> Optional[str]: @@ -554,6 +591,14 @@ def comment(self) -> Optional[str]: def config_dir(self): return os.path.dirname(self.config_path) + @property + def data_dir(self): + if is_ha_addon(): + return os.path.join("/data") + if "ESPHOME_DATA_DIR" in os.environ: + return get_str_env("ESPHOME_DATA_DIR", None) + return self.relative_config_path(".esphome") + @property def config_filename(self): return os.path.basename(self.config_path) @@ -563,7 +608,7 @@ def relative_config_path(self, *path): return os.path.join(self.config_dir, path_) def relative_internal_path(self, *path: str) -> str: - return self.relative_config_path(".esphome", *path) + return os.path.join(self.data_dir, *path) def relative_build_path(self, *path): path_ = os.path.expanduser(os.path.join(*path)) @@ -573,13 +618,9 @@ def relative_src_path(self, *path): return self.relative_build_path("src", *path) def relative_pioenvs_path(self, *path): - if is_ha_addon(): - return os.path.join("/data", self.name, ".pioenvs", *path) return self.relative_build_path(".pioenvs", *path) def relative_piolibdeps_path(self, *path): - if is_ha_addon(): - return os.path.join("/data", self.name, ".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path) @property @@ -594,15 +635,27 @@ def target_platform(self): @property def is_esp8266(self): - return self.target_platform == "esp8266" + return self.target_platform == PLATFORM_ESP8266 @property def is_esp32(self): - return self.target_platform == "esp32" + return self.target_platform == PLATFORM_ESP32 @property def is_rp2040(self): - return self.target_platform == "rp2040" + return self.target_platform == PLATFORM_RP2040 + + @property + def is_bk72xx(self): + return self.target_platform == PLATFORM_BK72XX + + @property + def is_rtl87xx(self): + return self.target_platform == PLATFORM_RTL87XX + + @property + def is_libretiny(self): + return self.is_bk72xx or self.is_rtl87xx @property def is_bk72xx(self): @@ -618,7 +671,7 @@ def is_libretiny(self): @property def is_host(self): - return self.target_platform == "host" + return self.target_platform == PLATFORM_HOST @property def target_framework(self): diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d82a7a5d3782..a4550bcd9ed3 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -81,13 +81,11 @@ void Application::loop() { const uint32_t now = millis(); - if (HighFrequencyLoopRequester::is_high_frequency()) { + auto elapsed = now - this->last_loop_; + if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) { yield(); } else { - uint32_t delay_time = this->loop_interval_; - if (now - this->last_loop_ < this->loop_interval_) - delay_time = this->loop_interval_ - (now - this->last_loop_); - + uint32_t delay_time = this->loop_interval_ - elapsed; uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); // next_schedule is max 0.5*delay_time // otherwise interval=0 schedules result in constant looping with almost no sleep diff --git a/esphome/core/application.h b/esphome/core/application.h index 054f2ea64831..c4c745b68754 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -39,25 +39,43 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_DATETIME_DATE +#include "esphome/components/datetime/date_entity.h" +#endif +#ifdef USE_DATETIME_TIME +#include "esphome/components/datetime/time_entity.h" +#endif +#ifdef USE_DATETIME_DATETIME +#include "esphome/components/datetime/datetime_entity.h" +#endif +#ifdef USE_TEXT +#include "esphome/components/text/text.h" +#endif #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif #ifdef USE_LOCK #include "esphome/components/lock/lock.h" #endif +#ifdef USE_VALVE +#include "esphome/components/valve/valve.h" +#endif #ifdef USE_MEDIA_PLAYER #include "esphome/components/media_player/media_player.h" #endif #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_EVENT +#include "esphome/components/event/event.h" +#endif namespace esphome { 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) { + void pre_setup(const std::string &name, const std::string &friendly_name, const std::string &area, + const char *comment, const char *compilation_time, bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { @@ -71,6 +89,7 @@ class Application { this->name_ = name; this->friendly_name_ = friendly_name; } + this->area_ = area; this->comment_ = comment; this->compilation_time_ = compilation_time; } @@ -117,6 +136,22 @@ class Application { void register_number(number::Number *number) { this->numbers_.push_back(number); } #endif +#ifdef USE_DATETIME_DATE + void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); } +#endif + +#ifdef USE_DATETIME_TIME + void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); } +#endif + +#ifdef USE_DATETIME_DATETIME + void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); } +#endif + +#ifdef USE_TEXT + void register_text(text::Text *text) { this->texts_.push_back(text); } +#endif + #ifdef USE_SELECT void register_select(select::Select *select) { this->selects_.push_back(select); } #endif @@ -125,6 +160,10 @@ class Application { void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); } #endif +#ifdef USE_VALVE + void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); } +#endif + #ifdef USE_MEDIA_PLAYER void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); } #endif @@ -135,6 +174,10 @@ class Application { } #endif +#ifdef USE_EVENT + void register_event(event::Event *event) { this->events_.push_back(event); } +#endif + /// Register the component in this Application instance. template C *register_component(C *c) { static_assert(std::is_base_of::value, "Only Component subclasses can be registered"); @@ -153,6 +196,10 @@ class Application { /// Get the friendly name of this Application set by pre_setup(). const std::string &get_friendly_name() const { return this->friendly_name_; } + + /// Get the area of this Application set by pre_setup(). + const std::string &get_area() const { return this->area_; } + /// Get the comment of this Application set by pre_setup(). std::string get_comment() const { return this->comment_; } @@ -175,6 +222,8 @@ class Application { */ void set_loop_interval(uint32_t loop_interval) { this->loop_interval_ = loop_interval; } + uint32_t get_loop_interval() const { return this->loop_interval_; } + void schedule_dump_config() { this->dump_config_at_ = 0; } void feed_wdt(); @@ -277,6 +326,42 @@ class Application { return nullptr; } #endif +#ifdef USE_DATETIME_DATE + const std::vector &get_dates() { return this->dates_; } + datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->dates_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif +#ifdef USE_DATETIME_TIME + const std::vector &get_times() { return this->times_; } + datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->times_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif +#ifdef USE_DATETIME_DATETIME + const std::vector &get_datetimes() { return this->datetimes_; } + datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->datetimes_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif +#ifdef USE_TEXT + const std::vector &get_texts() { return this->texts_; } + text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->texts_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_SELECT const std::vector &get_selects() { return this->selects_; } select::Select *get_select_by_key(uint32_t key, bool include_internal = false) { @@ -295,6 +380,15 @@ class Application { return nullptr; } #endif +#ifdef USE_VALVE + const std::vector &get_valves() { return this->valves_; } + valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->valves_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif #ifdef USE_MEDIA_PLAYER const std::vector &get_media_players() { return this->media_players_; } media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) { @@ -317,6 +411,16 @@ class Application { } #endif +#ifdef USE_EVENT + const std::vector &get_events() { return this->events_; } + event::Event *get_event_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->events_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif + Scheduler scheduler; protected: @@ -340,6 +444,9 @@ class Application { #ifdef USE_BUTTON std::vector buttons_{}; #endif +#ifdef USE_EVENT + std::vector events_{}; +#endif #ifdef USE_SENSOR std::vector sensors_{}; #endif @@ -361,12 +468,27 @@ class Application { #ifdef USE_NUMBER std::vector numbers_{}; #endif +#ifdef USE_DATETIME_DATE + std::vector dates_{}; +#endif +#ifdef USE_DATETIME_TIME + std::vector times_{}; +#endif +#ifdef USE_DATETIME_DATETIME + std::vector datetimes_{}; +#endif #ifdef USE_SELECT std::vector selects_{}; #endif +#ifdef USE_TEXT + std::vector texts_{}; +#endif #ifdef USE_LOCK std::vector locks_{}; #endif +#ifdef USE_VALVE + std::vector valves_{}; +#endif #ifdef USE_MEDIA_PLAYER std::vector media_players_{}; #endif @@ -376,6 +498,7 @@ class Application { std::string name_; std::string friendly_name_; + std::string area_; const char *comment_{nullptr}; const char *compilation_time_{nullptr}; bool name_add_mac_suffix_; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 84c754e081d8..9b62640a0c73 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -24,7 +24,7 @@ template struct gens<0, S...> { using type = seq; }; // NOLINT template class TemplatableValue { public: - TemplatableValue() : type_(EMPTY) {} + TemplatableValue() : type_(NONE) {} template::value, int> = 0> TemplatableValue(F value) : type_(VALUE), value_(value) {} @@ -32,13 +32,13 @@ template class TemplatableValue { template::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA), f_(f) {} - bool has_value() { return this->type_ != EMPTY; } + bool has_value() { return this->type_ != NONE; } T value(X... x) { if (this->type_ == LAMBDA) { return this->f_(x...); } - // return value also when empty + // return value also when none return this->value_; } @@ -58,7 +58,7 @@ template class TemplatableValue { protected: enum { - EMPTY, + NONE, VALUE, LAMBDA, } type_; @@ -218,7 +218,7 @@ template class ActionList { /// Return the number of actions in this action list that are currently running. int num_running() { if (this->actions_begin_ == nullptr) - return false; + return 0; return this->actions_begin_->num_running_total(); } diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index daa09b912e74..1bf0efb9a457 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -2,6 +2,8 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/preferences.h" #include @@ -48,6 +50,22 @@ template class NotCondition : public Condition { Condition *condition_; }; +template class XorCondition : public Condition { + public: + explicit XorCondition(const std::vector *> &conditions) : conditions_(conditions) {} + bool check(Ts... x) override { + size_t result = 0; + for (auto *condition : this->conditions_) { + result += condition->check(x...); + } + + return result == 1; + } + + protected: + std::vector *> conditions_; +}; + template class LambdaCondition : public Condition { public: explicit LambdaCondition(std::function &&f) : f_(std::move(f)) {} @@ -109,6 +127,27 @@ class LoopTrigger : public Trigger<>, public Component { float get_setup_priority() const override { return setup_priority::DATA; } }; +#ifdef ESPHOME_PROJECT_NAME +class ProjectUpdateTrigger : public Trigger, public Component { + public: + void setup() override { + uint32_t hash = fnv1_hash(ESPHOME_PROJECT_NAME); + ESPPreferenceObject pref = global_preferences->make_preference(hash, true); + char previous_version[30]; + char current_version[30] = ESPHOME_PROJECT_VERSION_30; + if (pref.load(&previous_version)) { + int cmp = strcmp(previous_version, current_version); + if (cmp < 0) { + this->trigger(previous_version); + } + } + pref.save(¤t_version); + global_preferences->sync(); + } + float get_setup_priority() const override { return setup_priority::PROCESSOR; } +}; +#endif + template class DelayAction : public Action, public Component { public: explicit DelayAction() = default; @@ -249,7 +288,11 @@ template class RepeatAction : public Action { void play_complex(Ts... x) override { this->num_running_++; this->var_ = std::make_tuple(x...); - this->then_.play(0, x...); + if (this->count_.value(x...) > 0) { + this->then_.play(0, x...); + } else { + this->play_next_tuple_(this->var_); + } } void play(Ts... x) override { /* ignore - see play_complex */ @@ -326,4 +369,38 @@ template class UpdateComponentAction : public Action { PollingComponent *component_; }; +template class SuspendComponentAction : public Action { + public: + SuspendComponentAction(PollingComponent *component) : component_(component) {} + + void play(Ts... x) override { + if (!this->component_->is_ready()) + return; + this->component_->stop_poller(); + } + + protected: + PollingComponent *component_; +}; + +template class ResumeComponentAction : public Action { + public: + ResumeComponentAction(PollingComponent *component) : component_(component) {} + TEMPLATABLE_VALUE(uint32_t, update_interval) + + void play(Ts... x) override { + if (!this->component_->is_ready()) { + return; + } + optional update_interval = this->update_interval_.optional_value(x...); + if (update_interval.has_value()) { + this->component_->set_update_interval(update_interval.value()); + } + this->component_->start_poller(); + } + + protected: + PollingComponent *component_; +}; + } // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index 45b2d4c8711c..8965d9fc8382 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -31,19 +31,19 @@ struct Color { uint32_t raw_32; }; - inline Color() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT - inline Color(uint8_t red, uint8_t green, uint8_t blue) ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {} + inline Color() ESPHOME_ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT + inline Color(uint8_t red, uint8_t green, uint8_t blue) ESPHOME_ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {} - inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red), - g(green), - b(blue), - w(white) {} - inline explicit Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), - g((colorcode >> 8) & 0xFF), - b((colorcode >> 0) & 0xFF), - w((colorcode >> 24) & 0xFF) {} + inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ESPHOME_ALWAYS_INLINE : r(red), + g(green), + b(blue), + w(white) {} + inline explicit Color(uint32_t colorcode) ESPHOME_ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), + g((colorcode >> 8) & 0xFF), + b((colorcode >> 0) & 0xFF), + w((colorcode >> 24) & 0xFF) {} - inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; } + inline bool is_on() ESPHOME_ALWAYS_INLINE { return this->raw_32 != 0; } inline bool operator==(const Color &rhs) { // NOLINT return this->raw_32 == rhs.raw_32; @@ -57,30 +57,33 @@ struct Color { inline bool operator!=(uint32_t colorcode) { // NOLINT return this->raw_32 != colorcode; } - inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; } - inline Color operator*(uint8_t scale) const ALWAYS_INLINE { + inline uint8_t &operator[](uint8_t x) ESPHOME_ALWAYS_INLINE { return this->raw[x]; } + inline Color operator*(uint8_t scale) const ESPHOME_ALWAYS_INLINE { return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale), esp_scale8(this->white, scale)); } - inline Color &operator*=(uint8_t scale) ALWAYS_INLINE { + inline Color operator~() const ESPHOME_ALWAYS_INLINE { + return Color(255 - this->red, 255 - this->green, 255 - this->blue); + } + inline Color &operator*=(uint8_t scale) ESPHOME_ALWAYS_INLINE { this->red = esp_scale8(this->red, scale); this->green = esp_scale8(this->green, scale); this->blue = esp_scale8(this->blue, scale); this->white = esp_scale8(this->white, scale); return *this; } - inline Color operator*(const Color &scale) const ALWAYS_INLINE { + inline Color operator*(const Color &scale) const ESPHOME_ALWAYS_INLINE { return Color(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green), esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white)); } - inline Color &operator*=(const Color &scale) ALWAYS_INLINE { + inline Color &operator*=(const Color &scale) ESPHOME_ALWAYS_INLINE { this->red = esp_scale8(this->red, scale.red); this->green = esp_scale8(this->green, scale.green); this->blue = esp_scale8(this->blue, scale.blue); this->white = esp_scale8(this->white, scale.white); return *this; } - inline Color operator+(const Color &add) const ALWAYS_INLINE { + inline Color operator+(const Color &add) const ESPHOME_ALWAYS_INLINE { Color ret; if (uint8_t(add.r + this->r) < this->r) ret.r = 255; @@ -100,10 +103,10 @@ struct Color { ret.w = this->w + add.w; return ret; } - inline Color &operator+=(const Color &add) ALWAYS_INLINE { return *this = (*this) + add; } - inline Color operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + Color(add, add, add, add); } - inline Color &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; } - inline Color operator-(const Color &subtract) const ALWAYS_INLINE { + inline Color &operator+=(const Color &add) ESPHOME_ALWAYS_INLINE { return *this = (*this) + add; } + inline Color operator+(uint8_t add) const ESPHOME_ALWAYS_INLINE { return (*this) + Color(add, add, add, add); } + inline Color &operator+=(uint8_t add) ESPHOME_ALWAYS_INLINE { return *this = (*this) + add; } + inline Color operator-(const Color &subtract) const ESPHOME_ALWAYS_INLINE { Color ret; if (subtract.r > this->r) ret.r = 0; @@ -123,11 +126,11 @@ struct Color { ret.w = this->w - subtract.w; return ret; } - inline Color &operator-=(const Color &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; } - inline Color operator-(uint8_t subtract) const ALWAYS_INLINE { + inline Color &operator-=(const Color &subtract) ESPHOME_ALWAYS_INLINE { return *this = (*this) - subtract; } + inline Color operator-(uint8_t subtract) const ESPHOME_ALWAYS_INLINE { return (*this) - Color(subtract, subtract, subtract, subtract); } - inline Color &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; } + inline Color &operator-=(uint8_t subtract) ESPHOME_ALWAYS_INLINE { return *this = (*this) - subtract; } static Color random_color() { uint32_t rand = random_uint32(); uint8_t w = rand >> 24; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index ae85d5549867..594e8ff7dfea 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -5,6 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include namespace esphome { @@ -75,7 +76,12 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT void Component::call_loop() { this->loop(); } void Component::call_setup() { this->setup(); } -void Component::call_dump_config() { this->dump_config(); } +void Component::call_dump_config() { + this->dump_config(); + if (this->is_failed()) { + ESP_LOGE(TAG, " Component %s is marked FAILED", this->get_component_source()); + } +} uint32_t Component::get_component_state() const { return this->component_state_; } void Component::call() { @@ -140,18 +146,35 @@ bool Component::is_ready() { (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; } bool Component::can_proceed() { return true; } -bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; } -bool Component::status_has_error() { return this->component_state_ & STATUS_LED_ERROR; } -void Component::status_set_warning() { +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; } +void Component::status_set_warning(const char *message) { + // Don't spam the log. This risks missing different warning messages though. + if ((this->component_state_ & STATUS_LED_WARNING) != 0) + return; this->component_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING; + ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message); } -void Component::status_set_error() { +void Component::status_set_error(const char *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, "Component %s set Error flag: %s", this->get_component_source(), message); +} +void Component::status_clear_warning() { + if ((this->component_state_ & STATUS_LED_WARNING) == 0) + return; + this->component_state_ &= ~STATUS_LED_WARNING; + ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source()); +} +void Component::status_clear_error() { + if ((this->component_state_ & STATUS_LED_ERROR) == 0) + return; + this->component_state_ &= ~STATUS_LED_ERROR; + ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source()); } -void Component::status_clear_warning() { this->component_state_ &= ~STATUS_LED_WARNING; } -void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; } void Component::status_momentary_warning(const std::string &name, uint32_t length) { this->status_set_warning(); this->set_timeout(name, length, [this]() { this->status_clear_warning(); }); @@ -169,7 +192,7 @@ float Component::get_actual_setup_priority() const { void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; } bool Component::has_overridden_loop() const { -#ifdef CLANG_TIDY +#if defined(USE_HOST) || defined(CLANG_TIDY) bool loop_overridden = true; bool call_loop_overridden = true; #else @@ -188,10 +211,20 @@ void PollingComponent::call_setup() { // Let the polling component subclass setup their HW. this->setup(); + // init the poller + this->start_poller(); +} + +void PollingComponent::start_poller() { // Register interval. this->set_interval("update", this->get_update_interval(), [this]() { this->update(); }); } +void PollingComponent::stop_poller() { + // Clear the interval to suspend component + this->cancel_interval("update"); +} + uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } @@ -201,8 +234,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { uint32_t now = millis(); if (now - started_ > 50) { const char *src = component_ == nullptr ? "" : component_->get_component_source(); - ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); - ESP_LOGW(TAG, "Components should block for at most 20-30ms."); + ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, (now - started_)); + ESP_LOGW(TAG, "Components should block for at most 30 ms."); ; } } diff --git a/esphome/core/component.h b/esphome/core/component.h index 7382f1c617d4..e6ffe96d1ea5 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -1,8 +1,9 @@ #pragma once -#include -#include #include +#include +#include +#include #include "esphome/core/optional.h" @@ -84,7 +85,7 @@ class Component { /** priority of setup(). higher -> executed earlier * - * Defaults to 0. + * Defaults to setup_priority::DATA, i.e. 600. * * @return The setup priority of this component */ @@ -123,13 +124,13 @@ class Component { virtual bool can_proceed(); - bool status_has_warning(); + bool status_has_warning() const; - bool status_has_error(); + bool status_has_error() const; - void status_set_warning(); + void status_set_warning(const char *message = "unspecified"); - void status_set_error(); + void status_set_error(const char *message = "unspecified"); void status_clear_warning(); @@ -192,7 +193,7 @@ class Component { * again in the future. * * The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is - * increased by multipling by `backoff_increase_factor` each time. If no backoff_increase_factor is + * increased by multiplying by `backoff_increase_factor` each time. If no backoff_increase_factor is * supplied (default = 1.0), the wait time will stay constant. * * The retry function f needs to accept a single argument: the number of attempts remaining. On the @@ -308,6 +309,12 @@ class PollingComponent : public Component { /// Get the update interval in ms of this sensor virtual uint32_t get_update_interval() const; + // Start the poller, used for component.suspend + void start_poller(); + + // Stop the poller, used for component.suspend + void stop_poller(); + protected: uint32_t update_interval_; }; diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index 871f6d6c0ecb..9b02bf527b13 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -202,6 +202,66 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_DATETIME_DATE + case IteratorState::DATETIME_DATE: + if (this->at_ >= App.get_dates().size()) { + advance_platform = true; + } else { + auto *date = App.get_dates()[this->at_]; + if (date->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_date(date); + } + } + break; +#endif +#ifdef USE_DATETIME_TIME + case IteratorState::DATETIME_TIME: + if (this->at_ >= App.get_times().size()) { + advance_platform = true; + } else { + auto *time = App.get_times()[this->at_]; + if (time->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_time(time); + } + } + break; +#endif +#ifdef USE_DATETIME_DATETIME + case IteratorState::DATETIME_DATETIME: + if (this->at_ >= App.get_datetimes().size()) { + advance_platform = true; + } else { + auto *datetime = App.get_datetimes()[this->at_]; + if (datetime->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_datetime(datetime); + } + } + break; +#endif +#ifdef USE_TEXT + case IteratorState::TEXT: + if (this->at_ >= App.get_texts().size()) { + advance_platform = true; + } else { + auto *text = App.get_texts()[this->at_]; + if (text->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_text(text); + } + } + break; +#endif #ifdef USE_SELECT case IteratorState::SELECT: if (this->at_ >= App.get_selects().size()) { @@ -232,6 +292,21 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_VALVE + case IteratorState::VALVE: + if (this->at_ >= App.get_valves().size()) { + advance_platform = true; + } else { + auto *valve = App.get_valves()[this->at_]; + if (valve->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_valve(valve); + } + } + break; +#endif #ifdef USE_MEDIA_PLAYER case IteratorState::MEDIA_PLAYER: if (this->at_ >= App.get_media_players().size()) { @@ -261,6 +336,21 @@ void ComponentIterator::advance() { } } break; +#endif +#ifdef USE_EVENT + case IteratorState::EVENT: + if (this->at_ >= App.get_events().size()) { + advance_platform = true; + } else { + auto *event = App.get_events()[this->at_]; + if (event->is_internal() && !this->include_internal_) { + success = true; + break; + } else { + success = this->on_event(event); + } + } + break; #endif case IteratorState::MAX: if (this->on_end()) { diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 8b2da6218cba..2b847bc08852 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -57,17 +57,35 @@ class ComponentIterator { #ifdef USE_NUMBER virtual bool on_number(number::Number *number) = 0; #endif +#ifdef USE_DATETIME_DATE + virtual bool on_date(datetime::DateEntity *date) = 0; +#endif +#ifdef USE_DATETIME_TIME + virtual bool on_time(datetime::TimeEntity *time) = 0; +#endif +#ifdef USE_DATETIME_DATETIME + virtual bool on_datetime(datetime::DateTimeEntity *datetime) = 0; +#endif +#ifdef USE_TEXT + virtual bool on_text(text::Text *text) = 0; +#endif #ifdef USE_SELECT virtual bool on_select(select::Select *select) = 0; #endif #ifdef USE_LOCK virtual bool on_lock(lock::Lock *a_lock) = 0; #endif +#ifdef USE_VALVE + virtual bool on_valve(valve::Valve *valve) = 0; +#endif #ifdef USE_MEDIA_PLAYER virtual bool on_media_player(media_player::MediaPlayer *media_player); #endif #ifdef USE_ALARM_CONTROL_PANEL virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0; +#endif +#ifdef USE_EVENT + virtual bool on_event(event::Event *event) = 0; #endif virtual bool on_end(); @@ -111,17 +129,35 @@ class ComponentIterator { #ifdef USE_NUMBER NUMBER, #endif +#ifdef USE_DATETIME_DATE + DATETIME_DATE, +#endif +#ifdef USE_DATETIME_TIME + DATETIME_TIME, +#endif +#ifdef USE_DATETIME_DATETIME + DATETIME_DATETIME, +#endif +#ifdef USE_TEXT + TEXT, +#endif #ifdef USE_SELECT SELECT, #endif #ifdef USE_LOCK LOCK, #endif +#ifdef USE_VALVE + VALVE, +#endif #ifdef USE_MEDIA_PLAYER MEDIA_PLAYER, #endif #ifdef USE_ALARM_CONTROL_PANEL ALARM_CONTROL_PANEL, +#endif +#ifdef USE_EVENT + EVENT, #endif MAX, } state_{IteratorState::NONE}; diff --git a/esphome/core/config.py b/esphome/core/config.py index a09252e4b4ca..80b731b905a2 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -8,6 +8,7 @@ from esphome import automation from esphome.const import ( CONF_ARDUINO_VERSION, + CONF_AREA, CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_BUILD_PATH, @@ -23,6 +24,7 @@ CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, + CONF_ON_UPDATE, CONF_PLATFORM, CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, @@ -37,7 +39,7 @@ __version__ as ESPHOME_VERSION, ) from esphome.core import CORE, coroutine_with_priority -from esphome.helpers import copy_file_if_changed, walk_files +from esphome.helpers import copy_file_if_changed, get_str_env, walk_files _LOGGER = logging.getLogger(__name__) @@ -51,6 +53,9 @@ LoopTrigger = cg.esphome_ns.class_( "LoopTrigger", cg.Component, automation.Trigger.template() ) +ProjectUpdateTrigger = cg.esphome_ns.class_( + "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) +) VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") @@ -101,16 +106,6 @@ def valid_project_name(value: str): return value -def validate_version(value: str): - min_version = cv.Version.parse(value) - current_version = cv.Version.parse(ESPHOME_VERSION) - if current_version < min_version: - raise cv.Invalid( - f"Your ESPHome version is too old. Please update to at least {min_version}" - ) - return value - - if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ: _compile_process_limit_default = min( int(os.environ["ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT"]), @@ -126,6 +121,7 @@ def validate_version(value: str): { cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, + cv.Optional(CONF_AREA, ""): cv.string, cv.Optional(CONF_COMMENT): cv.string, cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( @@ -159,10 +155,17 @@ def validate_version(value: str): cv.string_strict, valid_project_name ), cv.Required(CONF_VERSION): cv.string_strict, + cv.Optional(CONF_ON_UPDATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ProjectUpdateTrigger + ), + } + ), } ), cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All( - cv.version_number, validate_version + cv.version_number, cv.validate_esphome_version ), cv.Optional( CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default @@ -198,8 +201,9 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" - CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) + build_path = get_str_env("ESPHOME_BUILD_PATH", "build") + conf[CONF_BUILD_PATH] = os.path.join(build_path, CORE.name) + CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf newstyle_found = [key for key in TARGET_PLATFORMS if key in config] @@ -350,6 +354,7 @@ async def to_code(config): cg.App.pre_setup( config[CONF_NAME], config[CONF_FRIENDLY_NAME], + config[CONF_AREA], config.get(CONF_COMMENT, ""), cg.RawExpression('__DATE__ ", " __TIME__'), config[CONF_NAME_ADD_MAC_SUFFIX], @@ -386,9 +391,16 @@ async def to_code(config): if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) - if CONF_PROJECT in config: - cg.add_define("ESPHOME_PROJECT_NAME", config[CONF_PROJECT][CONF_NAME]) - cg.add_define("ESPHOME_PROJECT_VERSION", config[CONF_PROJECT][CONF_VERSION]) + if project_conf := config.get(CONF_PROJECT): + cg.add_define("ESPHOME_PROJECT_NAME", project_conf[CONF_NAME]) + cg.add_define("ESPHOME_PROJECT_VERSION", project_conf[CONF_VERSION]) + cg.add_define("ESPHOME_PROJECT_VERSION_30", project_conf[CONF_VERSION][:29]) + for conf in project_conf.get(CONF_ON_UPDATE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + await cg.register_component(trigger, conf) + await automation.build_automation( + trigger, [(cg.std_string, "version")], conf + ) if config[CONF_PLATFORMIO_OPTIONS]: CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 18d427b40c06..095732950051 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -1,6 +1,6 @@ #include "controller.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" namespace esphome { @@ -59,6 +59,30 @@ void Controller::setup_controller(bool include_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()) { @@ -73,6 +97,12 @@ void Controller::setup_controller(bool include_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()) @@ -85,6 +115,12 @@ void Controller::setup_controller(bool include_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 } } // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 25a4acb36e02..e1bf93193a57 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -31,18 +31,36 @@ #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif +#ifdef USE_DATETIME_DATE +#include "esphome/components/datetime/date_entity.h" +#endif +#ifdef USE_DATETIME_TIME +#include "esphome/components/datetime/time_entity.h" +#endif +#ifdef USE_DATETIME_DATETIME +#include "esphome/components/datetime/datetime_entity.h" +#endif +#ifdef USE_TEXT +#include "esphome/components/text/text.h" +#endif #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif #ifdef USE_LOCK #include "esphome/components/lock/lock.h" #endif +#ifdef USE_VALVE +#include "esphome/components/valve/valve.h" +#endif #ifdef USE_MEDIA_PLAYER #include "esphome/components/media_player/media_player.h" #endif #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_EVENT +#include "esphome/components/event/event.h" +#endif namespace esphome { @@ -76,18 +94,36 @@ class Controller { #ifdef USE_NUMBER virtual void on_number_update(number::Number *obj, float state){}; #endif +#ifdef USE_DATETIME_DATE + virtual void on_date_update(datetime::DateEntity *obj){}; +#endif +#ifdef USE_DATETIME_TIME + virtual void on_time_update(datetime::TimeEntity *obj){}; +#endif +#ifdef USE_DATETIME_DATETIME + virtual void on_datetime_update(datetime::DateTimeEntity *obj){}; +#endif +#ifdef USE_TEXT + virtual void on_text_update(text::Text *obj, const std::string &state){}; +#endif #ifdef USE_SELECT virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){}; #endif #ifdef USE_LOCK virtual void on_lock_update(lock::Lock *obj){}; #endif +#ifdef USE_VALVE + virtual void on_valve_update(valve::Valve *obj){}; +#endif #ifdef USE_MEDIA_PLAYER virtual void on_media_player_update(media_player::MediaPlayer *obj){}; #endif #ifdef USE_ALARM_CONTROL_PANEL 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){}; +#endif }; } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1e0df74eece5..c2ad0f641c24 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -11,20 +11,27 @@ #define ESPHOME_BOARD "dummy_board" #define ESPHOME_PROJECT_NAME "dummy project" #define ESPHOME_PROJECT_VERSION "v2" +#define ESPHOME_PROJECT_VERSION_30 "v2" #define ESPHOME_VARIANT "ESP32" // Feature flags +#define USE_ALARM_CONTROL_PANEL #define USE_API #define USE_API_NOISE #define USE_API_PLAINTEXT -#define USE_ALARM_CONTROL_PANEL #define USE_BINARY_SENSOR #define USE_BUTTON #define USE_CLIMATE #define USE_COVER +#define USE_DATETIME +#define USE_DATETIME_DATE +#define USE_DATETIME_DATETIME +#define USE_DATETIME_TIME #define USE_DEEP_SLEEP +#define USE_EVENT #define USE_FAN #define USE_GRAPH +#define USE_GRAPHICAL_DISPLAY_MENU #define USE_HOMEASSISTANT_TIME #define USE_JSON #define USE_LIGHT @@ -33,26 +40,31 @@ #define USE_MDNS #define USE_MEDIA_PLAYER #define USE_MQTT +#define USE_NEXTION_TFT_UPLOAD #define USE_NUMBER #define USE_OTA #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK +#define USE_OTA_VERSION 1 +#define USE_OUTPUT #define USE_POWER_SUPPLY #define USE_QR_CODE #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED #define USE_SWITCH +#define USE_TEXT #define USE_TEXT_SENSOR #define USE_TIME #define USE_TOUCHSCREEN #define USE_UART_DEBUGGER +#define USE_VALVE #define USE_WIFI +#define USE_WIFI_AP // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL -#define USE_NEXTION_TFT_UPLOAD #define USE_PROMETHEUS #define USE_WEBSERVER #define USE_WEBSERVER_PORT 80 // NOLINT @@ -66,16 +78,19 @@ // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_BLUETOOTH_PROXY +#define USE_ESP32_BLE #define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA #define USE_IMPROV -#define USE_SOCKET_IMPL_BSD_SOCKETS -#define USE_WIFI_11KV_SUPPORT -#define USE_BLUETOOTH_PROXY -#define USE_VOICE_ASSISTANT #define USE_MICROPHONE +#define USE_PSRAM +#define USE_SOCKET_IMPL_BSD_SOCKETS #define USE_SPEAKER +#define USE_SPI +#define USE_VOICE_ASSISTANT +#define USE_WIFI_11KV_SUPPORT #ifdef USE_ARDUINO #define USE_ARDUINO_VERSION_CODE VERSION_CODE(2, 0, 5) @@ -90,14 +105,12 @@ // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC -#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 2) +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP -#ifdef USE_LIBRETINY -#define USE_SOCKET_IMPL_LWIP_SOCKETS -#endif +#define USE_SPI // Dummy firmware payload for shelly_dimmer #define USE_SHD_FIRMWARE_MAJOR_VERSION 56 @@ -110,6 +123,11 @@ #ifdef USE_RP2040 #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 0) #define USE_SOCKET_IMPL_LWIP_TCP +#define USE_SPI +#endif + +#ifdef USE_LIBRETINY +#define USE_SOCKET_IMPL_LWIP_SOCKETS #endif #ifdef USE_HOST @@ -117,6 +135,6 @@ #endif // Disabled feature flags -//#define USE_BSEC // Requires a library with proprietary license. +// #define USE_BSEC // Requires a library with proprietary license. #define USE_DASHBOARD_IMPORT diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 1b6f2ba1e644..b3f6b0019667 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -62,6 +62,24 @@ class GPIOPin { virtual bool is_internal() { return false; } }; +/** + * A pin to replace those that don't exist. + */ +class NullPin : public GPIOPin { + public: + void setup() override {} + + void pin_mode(gpio::Flags _) override {} + + bool digital_read() override { return false; } + + void digital_write(bool _) override {} + + std::string dump_summary() const override { return {"Not used"}; } +}; + +static GPIOPin *const NULL_PIN = new NullPin(); + /// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) class ISRInternalGPIOPin { public: diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 714a1642f80f..fdc0eed774d0 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -11,6 +11,14 @@ #include #include +#ifdef USE_HOST +#ifndef _WIN32 +#include +#include +#include +#endif +#include +#endif #if defined(USE_ESP8266) #include #include @@ -278,10 +286,13 @@ std::string str_snake_case(const std::string &str) { return result; } std::string str_sanitize(const std::string &str) { - std::string out; - std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { - return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); - }); + 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 str_snprintf(const char *fmt, size_t len, ...) { @@ -412,7 +423,7 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { int8_t step_to_accuracy_decimals(float step) { // use printf %g to find number of digits based on temperature step char buf[32]; - sprintf(buf, "%.5g", step); + snprintf(buf, sizeof buf, "%.5g", step); std::string str{buf}; size_t dot_pos = str.find('.'); @@ -422,6 +433,103 @@ int8_t step_to_accuracy_decimals(float step) { return str.length() - dot_pos - 1; } +static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } + +std::string base64_encode(const std::vector &buf) { return base64_encode(buf.data(), buf.size()); } + +std::string base64_encode(const uint8_t *buf, size_t buf_len) { + std::string ret; + int i = 0; + int j = 0; + char char_array_3[3]; + char char_array_4[4]; + + while (buf_len--) { + char_array_3[i++] = *(buf++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + ret += BASE64_CHARS[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += BASE64_CHARS[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + + return ret; +} + +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; + uint8_t char_array_4[4], char_array_3[3]; + std::vector ret; + + while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) { + char_array_4[i++] = encoded_string[in]; + in++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = BASE64_CHARS.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + 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]); + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) + char_array_4[j] = 0; + + for (j = 0; j < 4; j++) + char_array_4[j] = BASE64_CHARS.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + 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]); + } + + return ret; +} + // Colors float gamma_correct(float value, float gamma) { @@ -548,7 +656,10 @@ void HighFrequencyLoopRequester::stop() { bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) -#if defined(USE_ESP32) +#if defined(USE_HOST) + static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS; + memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address)); +#elif defined(USE_ESP32) #if defined(CONFIG_SOC_IEEE802154_SUPPORTED) || defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) // When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default // returns the 802.15.4 EUI-64 address. Read directly from eFuse instead. @@ -566,6 +677,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame WiFi.macAddress(mac); #elif defined(USE_LIBRETINY) WiFi.macAddress(mac); +#else +// this should be an error, but that messes with CI checks. #error No mac address method defined #endif } std::string get_mac_address() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c3ed443bf01a..809e7d6767e1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -24,7 +24,7 @@ #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) -#define ALWAYS_INLINE __attribute__((always_inline)) +#define ESPHOME_ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) // Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement). @@ -435,6 +435,16 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); +static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +std::string base64_encode(const uint8_t *buf, size_t buf_len); +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); + ///@} /// @name Colors diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp new file mode 100644 index 000000000000..9bd3d9d85328 --- /dev/null +++ b/esphome/core/ring_buffer.cpp @@ -0,0 +1,50 @@ +#include "ring_buffer.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +#include "helpers.h" + +namespace esphome { + +static const char *const TAG = "ring_buffer"; + +std::unique_ptr RingBuffer::create(size_t len) { + std::unique_ptr rb = make_unique(); + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + rb->storage_ = allocator.allocate(len + 1); + if (rb->storage_ == nullptr) { + return nullptr; + } + + rb->handle_ = xStreamBufferCreateStatic(len + 1, 0, rb->storage_, &rb->structure_); + ESP_LOGD(TAG, "Created ring buffer with size %u", len); + return rb; +} + +size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { + return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); +} + +size_t RingBuffer::write(void *data, size_t len) { + size_t free = this->free(); + if (free < len) { + size_t needed = len - free; + uint8_t discard[needed]; + xStreamBufferReceive(this->handle_, discard, needed, 0); + } + return xStreamBufferSend(this->handle_, data, len, 0); +} + +size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } + +size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } + +BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } + +} // namespace esphome + +#endif diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h new file mode 100644 index 000000000000..e60206884426 --- /dev/null +++ b/esphome/core/ring_buffer.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include +#include + +namespace esphome { + +class RingBuffer { + public: + size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0); + + size_t write(void *data, size_t len); + + size_t available() const; + size_t free() const; + + BaseType_t reset(); + + static std::unique_ptr create(size_t len); + + protected: + StreamBufferHandle_t handle_; + StaticStreamBuffer_t structure_; + uint8_t *storage_; +}; + +} // namespace esphome + +#endif diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index bc5bfa173e46..add671701f48 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -1,10 +1,13 @@ #include "time.h" // NOLINT +#include "helpers.h" + +#include namespace esphome { -static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } +bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } -static uint8_t days_in_month(uint8_t month, uint16_t year) { +uint8_t days_in_month(uint8_t month, uint16_t year) { static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; uint8_t days = DAYS_IN_MONTH[month]; if (month == 2 && is_leap_year(year)) @@ -49,6 +52,11 @@ std::string ESPTime::strftime(const std::string &format) { struct tm c_tm = this->to_c_tm(); size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); while (len == 0) { + if (timestr.size() >= 128) { + // strftime has failed for reasons unrelated to the size of the buffer + // so return a formatting error + return "ERROR"; + } timestr.resize(timestr.size() * 2); len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); } @@ -56,6 +64,47 @@ std::string ESPTime::strftime(const std::string &format) { return timestr; } +bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int num; + + if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu:%02hhu %n", &year, &month, &day, // NOLINT + &hour, // NOLINT + &minute, // NOLINT + &second, &num) == 6 && // NOLINT + num == time_to_parse.size()) { + esp_time.year = year; + esp_time.month = month; + esp_time.day_of_month = day; + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = second; + } else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT + num == time_to_parse.size()) { + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = second; + } else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT + num == time_to_parse.size()) { + esp_time.hour = hour; + esp_time.minute = minute; + esp_time.second = 0; + } else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT + num == time_to_parse.size()) { + esp_time.year = year; + esp_time.month = month; + esp_time.day_of_month = day; + } else { + return false; + } + return true; +} + void ESPTime::increment_second() { this->timestamp++; if (!increment_time_value(this->second, 0, 60)) @@ -129,6 +178,15 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { this->timestamp = res; } +void ESPTime::recalc_timestamp_local(bool use_day_of_year) { + this->recalc_timestamp_utc(use_day_of_year); + this->timestamp -= ESPTime::timezone_offset(); + ESPTime temp = ESPTime::from_epoch_local(this->timestamp); + if (temp.is_dst) { + this->timestamp -= 3600; + } +} + int32_t ESPTime::timezone_offset() { int32_t offset = 0; time_t now = ::time(nullptr); diff --git a/esphome/core/time.h b/esphome/core/time.h index e16e449f0bbf..bce1108d93c6 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,6 +9,10 @@ namespace esphome { template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); +bool is_leap_year(uint32_t year); + +uint8_t days_in_month(uint8_t month, uint16_t year); + /// A more user-friendly version of struct tm from time.h struct ESPTime { /** seconds after the minute [0-60] @@ -45,6 +50,10 @@ struct ESPTime { * * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some * microcontrollers. + * + * @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the + * format string contains unsupported specifiers or when the format string doesn't produce any + * output. */ std::string strftime(const std::string &format); @@ -58,6 +67,13 @@ struct ESPTime { this->day_of_year < 367 && this->month > 0 && this->month < 13; } + /** Convert a string to ESPTime struct as specified by the format argument. + * @param time_to_parse null-terminated c string formatet like this: 2020-08-25 05:30:00. + * @param esp_time an instance of a ESPTime struct + * @return the success sate of the parsing + */ + static bool strptime(const std::string &time_to_parse, ESPTime &esp_time); + /// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance. static ESPTime from_c_tm(struct tm *c_tm, time_t c_time); @@ -83,6 +99,9 @@ struct ESPTime { /// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC). void recalc_timestamp_utc(bool use_day_of_year = true); + /// Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields. + void recalc_timestamp_local(bool use_day_of_year = true); + /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 789bd58e5cde..9a4cb2269ae6 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -2,7 +2,7 @@ import inspect import math import re -from collections.abc import Generator, Sequence +from collections.abc import Sequence from typing import Any, Callable, Optional, Union from esphome.core import ( @@ -17,6 +17,7 @@ TimePeriodMicroseconds, TimePeriodMilliseconds, TimePeriodMinutes, + TimePeriodNanoseconds, TimePeriodSeconds, ) from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last @@ -351,6 +352,8 @@ def safe_exp(obj: SafeExpType) -> Expression: return IntLiteral(obj) if isinstance(obj, float): return FloatLiteral(obj) + if isinstance(obj, TimePeriodNanoseconds): + return IntLiteral(int(obj.total_nanoseconds)) if isinstance(obj, TimePeriodMicroseconds): return IntLiteral(int(obj.total_microseconds)) if isinstance(obj, TimePeriodMilliseconds): @@ -474,8 +477,9 @@ def 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 register: If true register the variable with the core - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ assert isinstance(id_, ID) rhs = safe_exp(rhs) @@ -489,9 +493,7 @@ def variable( return obj -def with_local_variable( - id_: ID, rhs: SafeExpType, callback: Callable[["MockObj"], None], *args -) -> None: +def with_local_variable(id_: ID, rhs: SafeExpType, callback: Callable, *args) -> None: """Declare a new variable, not pointer type, in the code generation, within a scoped block The variable is only usable within the callback The callback cannot be async. @@ -523,7 +525,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj :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). - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ assert isinstance(id_, ID) rhs = safe_exp(rhs) @@ -546,7 +548,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": :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). - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ rhs = safe_exp(rhs) obj = MockObj(id_, "->") @@ -567,7 +569,7 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable: :param id_: The ID used to declare the variable (also specifies the type). :param args: The values to pass to the constructor. - :returns The new variable as a MockObj. + :return: The new variable as a MockObj. """ if args and isinstance(args[0], TemplateArguments): id_ = id_.copy() @@ -596,6 +598,7 @@ def add_library(name: str, version: Optional[str], repository: Optional[str] = N :param name: The name of the library (for example 'AsyncTCP') :param version: The version of the library, may be None. + :param repository: The repository for the library """ CORE.add_library(Library(name, version, repository)) @@ -651,7 +654,7 @@ async def process_lambda( parameters: list[tuple[SafeExpType, str]], capture: str = "=", return_type: SafeExpType = None, -) -> Generator[LambdaExpression, None, None]: +) -> Union[LambdaExpression, None]: """Process the given lambda value into a LambdaExpression. This is a coroutine because lambdas can depend on other IDs, @@ -663,10 +666,14 @@ async def process_lambda( :param return_type: The return type of the lambda. :return: The generated lambda expression. """ - from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent + from esphome.components.globals import ( + GlobalsComponent, + RestoringGlobalsComponent, + RestoringGlobalStringComponent, + ) if value is None: - return + return None parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = await get_variable_with_full_id(id) @@ -676,6 +683,7 @@ async def process_lambda( and ( full_id.type.inherits_from(GlobalsComponent) or full_id.type.inherits_from(RestoringGlobalsComponent) + or full_id.type.inherits_from(RestoringGlobalStringComponent) ) ): parts[i * 3 + 1] = var.value() @@ -704,7 +712,7 @@ async def templatable( value: Any, args: list[tuple[SafeExpType, str]], output_type: Optional[SafeExpType], - to_exp: Any = None, + to_exp: Union[Callable, dict] = None, ): """Generate code for a templatable config option. diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index cc53f491f51b..4b3716e223ac 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -35,7 +35,7 @@ async def gpio_pin_expression(conf): return None from esphome import pins - for key, (func, _) in pins.PIN_SCHEMA_REGISTRY.items(): + for key, (func, _, _) in pins.PIN_SCHEMA_REGISTRY.items(): if key in conf: return await coroutine(func)(conf) return await coroutine(pins.PIN_SCHEMA_REGISTRY[CORE.target_platform][0])(conf) diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 7d0e386b6633..0f1b7f236bc2 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -38,3 +38,4 @@ gpio_Flags = gpio_ns.enum("Flags", is_class=True) EntityCategory = esphome_ns.enum("EntityCategory") Parented = esphome_ns.class_("Parented") +ESPTime = esphome_ns.struct("ESPTime") diff --git a/esphome/dashboard/const.py b/esphome/dashboard/const.py new file mode 100644 index 000000000000..db66cb5eadf4 --- /dev/null +++ b/esphome/dashboard/const.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +EVENT_ENTRY_ADDED = "entry_added" +EVENT_ENTRY_REMOVED = "entry_removed" +EVENT_ENTRY_UPDATED = "entry_updated" +EVENT_ENTRY_STATE_CHANGED = "entry_state_changed" +MAX_EXECUTOR_WORKERS = 48 + + +SENTINEL = object() + +DASHBOARD_COMMAND = ["esphome", "--dashboard"] diff --git a/esphome/dashboard/core.py b/esphome/dashboard/core.py new file mode 100644 index 000000000000..875ff6b91fe2 --- /dev/null +++ b/esphome/dashboard/core.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +import asyncio +import contextlib +import logging +import threading +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Coroutine + +from ..zeroconf import DiscoveredImport +from .dns import DNSCache +from .entries import DashboardEntries +from .settings import DashboardSettings + +if TYPE_CHECKING: + from .status.mdns import MDNSStatus + + +_LOGGER = logging.getLogger(__name__) + + +@dataclass +class Event: + """Dashboard Event.""" + + event_type: str + data: dict[str, Any] + + +class EventBus: + """Dashboard event bus.""" + + def __init__(self) -> None: + """Initialize the Dashboard event bus.""" + self._listeners: dict[str, set[Callable[[Event], None]]] = {} + + def async_add_listener( + self, event_type: str, listener: Callable[[Event], None] + ) -> Callable[[], None]: + """Add a listener to the event bus.""" + self._listeners.setdefault(event_type, set()).add(listener) + return partial(self._async_remove_listener, event_type, listener) + + def _async_remove_listener( + self, event_type: str, listener: Callable[[Event], None] + ) -> None: + """Remove a listener from the event bus.""" + self._listeners[event_type].discard(listener) + + def async_fire(self, event_type: str, event_data: dict[str, Any]) -> None: + """Fire an event.""" + event = Event(event_type, event_data) + + _LOGGER.debug("Firing event: %s", event) + + for listener in self._listeners.get(event_type, set()): + listener(event) + + +class ESPHomeDashboard: + """Class that represents the dashboard.""" + + __slots__ = ( + "bus", + "entries", + "loop", + "import_result", + "stop_event", + "ping_request", + "mqtt_ping_request", + "mdns_status", + "settings", + "dns_cache", + "_background_tasks", + ) + + def __init__(self) -> None: + """Initialize the ESPHomeDashboard.""" + self.bus = EventBus() + self.entries: DashboardEntries | None = None + self.loop: asyncio.AbstractEventLoop | None = None + self.import_result: dict[str, DiscoveredImport] = {} + self.stop_event = threading.Event() + self.ping_request: asyncio.Event | None = None + self.mqtt_ping_request = threading.Event() + self.mdns_status: MDNSStatus | None = None + self.settings = DashboardSettings() + self.dns_cache = DNSCache() + self._background_tasks: set[asyncio.Task] = set() + + async def async_setup(self) -> None: + """Setup the dashboard.""" + self.loop = asyncio.get_running_loop() + self.ping_request = asyncio.Event() + self.entries = DashboardEntries(self) + + async def async_run(self) -> None: + """Run the dashboard.""" + settings = self.settings + mdns_task: asyncio.Task | None = None + ping_status_task: asyncio.Task | None = None + await self.entries.async_update_entries() + + if settings.status_use_ping: + from .status.ping import PingStatus + + ping_status = PingStatus() + ping_status_task = asyncio.create_task(ping_status.async_run()) + else: + from .status.mdns import MDNSStatus + + mdns_status = MDNSStatus() + await mdns_status.async_refresh_hosts() + self.mdns_status = mdns_status + mdns_task = asyncio.create_task(mdns_status.async_run()) + + if settings.status_use_mqtt: + from .status.mqtt import MqttStatusThread + + status_thread_mqtt = MqttStatusThread() + status_thread_mqtt.start() + + shutdown_event = asyncio.Event() + try: + await shutdown_event.wait() + finally: + _LOGGER.info("Shutting down...") + self.stop_event.set() + self.ping_request.set() + if ping_status_task: + ping_status_task.cancel() + if mdns_task: + mdns_task.cancel() + if settings.status_use_mqtt: + status_thread_mqtt.join() + self.mqtt_ping_request.set() + for task in self._background_tasks: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + await asyncio.sleep(0) + + def async_create_background_task( + self, coro: Coroutine[Any, Any, Any] + ) -> asyncio.Task: + """Create a background task.""" + task = self.loop.create_task(coro) + task.add_done_callback(self._background_tasks.discard) + return task + + +DASHBOARD = ESPHomeDashboard() diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 1ad54bbe72f0..2be98ab3e427 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -1,1351 +1,153 @@ -import base64 -import binascii -import codecs -import collections -import functools -import gzip -import hashlib -import hmac -import json +from __future__ import annotations + +import asyncio import logging -import multiprocessing import os -import secrets -import shutil -import subprocess +import socket import threading -from pathlib import Path -from typing import Optional - -import tornado -import tornado.concurrent -import tornado.gen -import tornado.httpserver -import tornado.ioloop -import tornado.iostream -import tornado.netutil -import tornado.process -import tornado.queues -import tornado.web -import tornado.websocket -import yaml -from tornado.log import access_log - -from esphome import const, platformio_api, util, yaml_util -from esphome.helpers import get_bool_env, mkdir_p, run_system_command -from esphome.storage_json import ( - EsphomeStorageJSON, - StorageJSON, - esphome_storage_path, - ext_storage_path, - trash_storage_path, -) -from esphome.util import get_serial_ports, shlex_quote -from esphome.zeroconf import DashboardImportDiscovery, DashboardStatus, EsphomeZeroconf +import traceback +from asyncio import events +from concurrent.futures import ThreadPoolExecutor +from time import monotonic +from typing import Any -from .util import friendly_name_slugify, password_hash +from esphome.storage_json import EsphomeStorageJSON, esphome_storage_path -_LOGGER = logging.getLogger(__name__) +from .const import MAX_EXECUTOR_WORKERS +from .core import DASHBOARD +from .web_server import make_app, start_web_server ENV_DEV = "ESPHOME_DASHBOARD_DEV" +settings = DASHBOARD.settings -class DashboardSettings: - def __init__(self): - self.config_dir = "" - self.password_hash = "" - self.username = "" - self.using_password = False - self.on_ha_addon = False - self.cookie_secret = None - self.absolute_config_dir = None - - def parse_args(self, args): - self.on_ha_addon = args.ha_addon - password = args.password or os.getenv("PASSWORD", "") - if not self.on_ha_addon: - self.username = args.username or os.getenv("USERNAME", "") - self.using_password = bool(password) - if self.using_password: - self.password_hash = password_hash(password) - self.config_dir = args.configuration - self.absolute_config_dir = Path(self.config_dir).resolve() - - @property - def relative_url(self): - return os.getenv("ESPHOME_DASHBOARD_RELATIVE_URL", "/") - - @property - def status_use_ping(self): - return get_bool_env("ESPHOME_DASHBOARD_USE_PING") - - @property - def status_use_mqtt(self): - return get_bool_env("ESPHOME_DASHBOARD_USE_MQTT") - - @property - def using_ha_addon_auth(self): - if not self.on_ha_addon: - return False - return not get_bool_env("DISABLE_HA_AUTHENTICATION") - - @property - def using_auth(self): - return self.using_password or self.using_ha_addon_auth - - @property - def streamer_mode(self): - return get_bool_env("ESPHOME_STREAMER_MODE") - - def check_password(self, username, password): - if not self.using_auth: - return True - if username != self.username: - return False - # Compare password in constant running time (to prevent timing attacks) - return hmac.compare_digest(self.password_hash, password_hash(password)) +def can_use_pidfd() -> bool: + """Check if pidfd_open is available. - def rel_path(self, *args): - joined_path = os.path.join(self.config_dir, *args) - # Raises ValueError if not relative to ESPHome config folder - Path(joined_path).resolve().relative_to(self.absolute_config_dir) - return joined_path - - def list_yaml_files(self): - return util.list_yaml_files([self.config_dir]) - - -settings = DashboardSettings() - -cookie_authenticated_yes = b"yes" - - -def template_args(): - version = const.__version__ - if "b" in version: - docs_link = "https://beta.esphome.io/" - elif "dev" in version: - docs_link = "https://next.esphome.io/" - else: - docs_link = "https://www.esphome.io/" - - return { - "version": version, - "docs_link": docs_link, - "get_static_file_url": get_static_file_url, - "relative_url": settings.relative_url, - "streamer_mode": settings.streamer_mode, - "config_dir": settings.config_dir, - } - - -def authenticated(func): - @functools.wraps(func) - def decorator(self, *args, **kwargs): - if not is_authenticated(self): - self.redirect("./login") - return None - return func(self, *args, **kwargs) - - return decorator - - -def is_authenticated(request_handler): - if settings.on_ha_addon: - # Handle ingress - disable auth on ingress port - # X-HA-Ingress is automatically stripped on the non-ingress server in nginx - header = request_handler.request.headers.get("X-HA-Ingress", "NO") - if str(header) == "YES": - return True - if settings.using_auth: - return ( - request_handler.get_secure_cookie("authenticated") - == cookie_authenticated_yes - ) + Back ported from cpython 3.12 + """ + if not hasattr(os, "pidfd_open"): + return False + try: + pid = os.getpid() + os.close(os.pidfd_open(pid, 0)) + except OSError: + # blocked by security policy like SECCOMP + return False return True -def bind_config(func): - def decorator(self, *args, **kwargs): - configuration = self.get_argument("configuration") - kwargs = kwargs.copy() - kwargs["configuration"] = configuration - return func(self, *args, **kwargs) - - return decorator +class DashboardEventLoopPolicy(asyncio.DefaultEventLoopPolicy): + """Event loop policy for Home Assistant.""" + def __init__(self, debug: bool) -> None: + """Init the event loop policy.""" + super().__init__() + self.debug = debug + self._watcher: asyncio.AbstractChildWatcher | None = None -# pylint: disable=abstract-method -class BaseHandler(tornado.web.RequestHandler): - pass + def _init_watcher(self) -> None: + """Initialize the watcher for child processes. - -def websocket_class(cls): - # pylint: disable=protected-access - if not hasattr(cls, "_message_handlers"): - cls._message_handlers = {} - - for _, method in cls.__dict__.items(): - if hasattr(method, "_message_handler"): - cls._message_handlers[method._message_handler] = method - - return cls - - -def websocket_method(name): - def wrap(fn): - # pylint: disable=protected-access - fn._message_handler = name - return fn - - return wrap - - -@websocket_class -class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): - def __init__(self, application, request, **kwargs): - super().__init__(application, request, **kwargs) - self._proc = None - self._queue = None - self._is_closed = False - # Windows doesn't support non-blocking pipes, - # use Popen() with a reading thread instead - self._use_popen = os.name == "nt" - - @authenticated - def on_message(self, message): - # Messages are always JSON, 500 when not - json_message = json.loads(message) - type_ = json_message["type"] - # pylint: disable=no-member - handlers = type(self)._message_handlers - if type_ not in handlers: - _LOGGER.warning("Requested unknown message type %s", type_) - return - - handlers[type_](self, json_message) - - @websocket_method("spawn") - def handle_spawn(self, json_message): - if self._proc is not None: - # spawn can only be called once - return - command = self.build_command(json_message) - _LOGGER.info("Running command '%s'", " ".join(shlex_quote(x) for x in command)) - - if self._use_popen: - self._queue = tornado.queues.Queue() - # pylint: disable=consider-using-with - self._proc = subprocess.Popen( - command, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - stdout_thread = threading.Thread(target=self._stdout_thread) - stdout_thread.daemon = True - stdout_thread.start() - else: - self._proc = tornado.process.Subprocess( - command, - stdout=tornado.process.Subprocess.STREAM, - stderr=subprocess.STDOUT, - stdin=tornado.process.Subprocess.STREAM, - ) - self._proc.set_exit_callback(self._proc_on_exit) - - tornado.ioloop.IOLoop.current().spawn_callback(self._redirect_stdout) - - @property - def is_process_active(self): - return self._proc is not None and self._proc.returncode is None - - @websocket_method("stdin") - def handle_stdin(self, json_message): - if not self.is_process_active: - return - data = json_message["data"] - data = codecs.encode(data, "utf8", "replace") - _LOGGER.debug("< stdin: %s", data) - self._proc.stdin.write(data) - - @tornado.gen.coroutine - def _redirect_stdout(self): - reg = b"[\n\r]" - - while True: - try: - if self._use_popen: - data = yield self._queue.get() - if data is None: - self._proc_on_exit(self._proc.poll()) - break + Back ported from cpython 3.12 + """ + with events._lock: # type: ignore[attr-defined] # pylint: disable=protected-access + if self._watcher is None: # pragma: no branch + if can_use_pidfd(): + self._watcher = asyncio.PidfdChildWatcher() else: - data = yield self._proc.stdout.read_until_regex(reg) - except tornado.iostream.StreamClosedError: - break - data = codecs.decode(data, "utf8", "replace") - - _LOGGER.debug("> stdout: %s", data) - self.write_message({"event": "line", "data": data}) - - def _stdout_thread(self): - if not self._use_popen: - return - while True: - data = self._proc.stdout.readline() - if data: - data = data.replace(b"\r", b"") - self._queue.put_nowait(data) - if self._proc.poll() is not None: - break - self._proc.wait(1.0) - self._queue.put_nowait(None) - - def _proc_on_exit(self, returncode): - if not self._is_closed: - # Check if the proc was not forcibly closed - _LOGGER.info("Process exited with return code %s", returncode) - self.write_message({"event": "exit", "code": returncode}) - - def on_close(self): - # Check if proc exists (if 'start' has been run) - if self.is_process_active: - _LOGGER.debug("Terminating process") - if self._use_popen: - self._proc.terminate() - else: - self._proc.proc.terminate() - # Shutdown proc on WS close - self._is_closed = True - - def build_command(self, json_message): - raise NotImplementedError - - -class EsphomeLogsHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - return [ - "esphome", - "--dashboard", - "logs", - config_file, - "--device", - json_message["port"], - ] - - -class EsphomeRenameHandler(EsphomeCommandWebSocket): - old_name: str - - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - self.old_name = json_message["configuration"] - return [ - "esphome", - "--dashboard", - "rename", - config_file, - json_message["newName"], - ] - - def _proc_on_exit(self, returncode): - super()._proc_on_exit(returncode) - - if returncode != 0: - return - - # Remove the old ping result from the cache - PING_RESULT.pop(self.old_name, None) - - -class EsphomeUploadHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - return [ - "esphome", - "--dashboard", - "upload", - config_file, - "--device", - json_message["port"], - ] - - -class EsphomeRunHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - return [ - "esphome", - "--dashboard", - "run", - config_file, - "--device", - json_message["port"], - ] - - -class EsphomeCompileHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - command = ["esphome", "--dashboard", "compile"] - if json_message.get("only_generate", False): - command.append("--only-generate") - command.append(config_file) - return command - - -class EsphomeValidateHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - command = ["esphome", "--dashboard", "config", config_file] - if not settings.streamer_mode: - command.append("--show-secrets") - return command - - -class EsphomeCleanMqttHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - return ["esphome", "--dashboard", "clean-mqtt", config_file] - - -class EsphomeCleanHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - config_file = settings.rel_path(json_message["configuration"]) - return ["esphome", "--dashboard", "clean", config_file] - - -class EsphomeVscodeHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - return ["esphome", "--dashboard", "-q", "vscode", "dummy"] - - -class EsphomeAceEditorHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - return ["esphome", "--dashboard", "-q", "vscode", "--ace", settings.config_dir] - - -class EsphomeUpdateAllHandler(EsphomeCommandWebSocket): - def build_command(self, json_message): - return ["esphome", "--dashboard", "update-all", settings.config_dir] - - -class SerialPortRequestHandler(BaseHandler): - @authenticated - def get(self): - ports = get_serial_ports() - data = [] - for port in ports: - desc = port.description - if port.path == "/dev/ttyAMA0": - desc = "UART pins on GPIO header" - split_desc = desc.split(" - ") - if len(split_desc) == 2 and split_desc[0] == split_desc[1]: - # Some serial ports repeat their values - desc = split_desc[0] - data.append({"port": port.path, "desc": desc}) - data.append({"port": "OTA", "desc": "Over-The-Air"}) - data.sort(key=lambda x: x["port"], reverse=True) - self.set_header("content-type", "application/json") - self.write(json.dumps(data)) - - -class WizardRequestHandler(BaseHandler): - @authenticated - def post(self): - from esphome import wizard - - kwargs = { - k: v - for k, v in json.loads(self.request.body.decode()).items() - if k in ("name", "platform", "board", "ssid", "psk", "password") - } - if not kwargs["name"]: - self.set_status(422) - self.set_header("content-type", "application/json") - self.write(json.dumps({"error": "Name is required"})) - return - - kwargs["friendly_name"] = kwargs["name"] - kwargs["name"] = friendly_name_slugify(kwargs["friendly_name"]) - - kwargs["ota_password"] = secrets.token_hex(16) - noise_psk = secrets.token_bytes(32) - kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode() - filename = f"{kwargs['name']}.yaml" - destination = settings.rel_path(filename) - wizard.wizard_write(path=destination, **kwargs) - self.set_status(200) - self.set_header("content-type", "application/json") - self.write(json.dumps({"configuration": filename})) - self.finish() - - -class ImportRequestHandler(BaseHandler): - @authenticated - def post(self): - from esphome.components.dashboard_import import import_config - - args = json.loads(self.request.body.decode()) - try: - name = args["name"] - friendly_name = args.get("friendly_name") - encryption = args.get("encryption", False) - - imported_device = next( - (res for res in IMPORT_RESULT.values() if res.device_name == name), None - ) - - if imported_device is not None: - network = imported_device.network - if friendly_name is None: - friendly_name = imported_device.friendly_name - else: - network = const.CONF_WIFI - - import_config( - settings.rel_path(f"{name}.yaml"), - name, - friendly_name, - args["project_name"], - args["package_import_url"], - network, - encryption, - ) - except FileExistsError: - self.set_status(500) - self.write("File already exists") - return - except ValueError: - self.set_status(422) - self.write("Invalid package url") - return - - self.set_status(200) - self.set_header("content-type", "application/json") - self.write(json.dumps({"configuration": f"{name}.yaml"})) - self.finish() - - -class DownloadBinaryRequestHandler(BaseHandler): - @authenticated - @bind_config - def get(self, configuration=None): - type = self.get_argument("type", "firmware.bin") - compressed = self.get_argument("compressed", "0") == "1" - - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error(404) - return - - platforms_uf2 = [ - const.PLATFORM_RP2040, - const.PLATFORM_BK72XX, - const.PLATFORM_RTL87XX, - ] - - if storage_json.target_platform.lower() in platforms_uf2: - filename = f"{storage_json.name}.uf2" - path = storage_json.firmware_bin_path.replace( - "firmware.bin", "firmware.uf2" - ) - - elif storage_json.target_platform.lower() == const.PLATFORM_ESP8266: - filename = f"{storage_json.name}.bin" - path = storage_json.firmware_bin_path - - elif type == "firmware.bin": - filename = f"{storage_json.name}.bin" - path = storage_json.firmware_bin_path - - elif type == "firmware-factory.bin": - filename = f"{storage_json.name}-factory.bin" - path = storage_json.firmware_bin_path.replace( - "firmware.bin", "firmware-factory.bin" - ) - - else: - args = ["esphome", "idedata", settings.rel_path(configuration)] - rc, stdout, _ = run_system_command(*args) - - if rc != 0: - self.send_error(404 if rc == 2 else 500) - return - - idedata = platformio_api.IDEData(json.loads(stdout)) - - found = False - for image in idedata.extra_flash_images: - if image.path.endswith(type): - path = image.path - filename = type - found = True - break - - if not found: - self.send_error(404) - return - - filename = filename + ".gz" if compressed else filename - - self.set_header("Content-Type", "application/octet-stream") - self.set_header("Content-Disposition", f'attachment; filename="{filename}"') - self.set_header("Cache-Control", "no-cache") - if not Path(path).is_file(): - self.send_error(404) - return - - with open(path, "rb") as f: - data = f.read() - if compressed: - data = gzip.compress(data, 9) - self.write(data) - - self.finish() - - -class EsphomeVersionHandler(BaseHandler): - @authenticated - def get(self): - self.set_header("Content-Type", "application/json") - self.write(json.dumps({"version": const.__version__})) - self.finish() - - -def _list_dashboard_entries(): - files = settings.list_yaml_files() - return [DashboardEntry(file) for file in files] - - -class DashboardEntry: - def __init__(self, path): - self.path = path - self._storage = None - self._loaded_storage = False - - @property - def filename(self): - return os.path.basename(self.path) - - @property - def storage(self) -> Optional[StorageJSON]: - if not self._loaded_storage: - self._storage = StorageJSON.load( - ext_storage_path(settings.config_dir, self.filename) - ) - self._loaded_storage = True - return self._storage - - @property - def address(self): - if self.storage is None: - return None - return self.storage.address - - @property - def no_mdns(self): - if self.storage is None: - return None - return self.storage.no_mdns - - @property - def web_port(self): - if self.storage is None: - return None - return self.storage.web_port - - @property - def name(self): - if self.storage is None: - return self.filename.replace(".yml", "").replace(".yaml", "") - return self.storage.name - - @property - def friendly_name(self): - if self.storage is None: - return self.name - return self.storage.friendly_name - - @property - def comment(self): - if self.storage is None: - return None - return self.storage.comment - - @property - def target_platform(self): - if self.storage is None: - return None - return self.storage.target_platform - - @property - def update_available(self): - if self.storage is None: - return True - return self.update_old != self.update_new - - @property - def update_old(self): - if self.storage is None: - return "" - return self.storage.esphome_version or "" - - @property - def update_new(self): - return const.__version__ - - @property - def loaded_integrations(self): - if self.storage is None: - return [] - return self.storage.loaded_integrations - - -class ListDevicesHandler(BaseHandler): - @authenticated - def get(self): - entries = _list_dashboard_entries() - self.set_header("content-type", "application/json") - configured = {entry.name for entry in entries} - self.write( - json.dumps( - { - "configured": [ - { - "name": entry.name, - "friendly_name": entry.friendly_name, - "configuration": entry.filename, - "loaded_integrations": entry.loaded_integrations, - "deployed_version": entry.update_old, - "current_version": entry.update_new, - "path": entry.path, - "comment": entry.comment, - "address": entry.address, - "web_port": entry.web_port, - "target_platform": entry.target_platform, - } - for entry in entries - ], - "importable": [ - { - "name": res.device_name, - "friendly_name": res.friendly_name, - "package_import_url": res.package_import_url, - "project_name": res.project_name, - "project_version": res.project_version, - "network": res.network, - } - for res in IMPORT_RESULT.values() - if res.device_name not in configured - ], - } - ) - ) - - -class MainRequestHandler(BaseHandler): - @authenticated - def get(self): - begin = bool(self.get_argument("begin", False)) - - self.render( - "index.template.html", - begin=begin, - **template_args(), - login_enabled=settings.using_password, - ) - - -def _ping_func(filename, address): - if os.name == "nt": - command = ["ping", "-n", "1", address] - else: - command = ["ping", "-c", "1", address] - rc, _, _ = run_system_command(*command) - return filename, rc == 0 - - -class PrometheusServiceDiscoveryHandler(BaseHandler): - @authenticated - def get(self): - entries = _list_dashboard_entries() - self.set_header("content-type", "application/json") - sd = [] - for entry in entries: - if entry.web_port is None: - continue - labels = { - "__meta_name": entry.name, - "__meta_esp_platform": entry.target_platform, - "__meta_esphome_version": entry.storage.esphome_version, - } - for integration in entry.storage.loaded_integrations: - labels[f"__meta_integration_{integration}"] = "true" - sd.append( - { - "targets": [ - f"{entry.address}:{entry.web_port}", - ], - "labels": labels, - } - ) - self.write(json.dumps(sd)) - - -class BoardsRequestHandler(BaseHandler): - @authenticated - def get(self, platform: str): - from esphome.components.esp32.boards import BOARDS as ESP32_BOARDS - from esphome.components.esp8266.boards import BOARDS as ESP8266_BOARDS - from esphome.components.rp2040.boards import BOARDS as RP2040_BOARDS - from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS - from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS - - platform_to_boards = { - "esp32": ESP32_BOARDS, - "esp8266": ESP8266_BOARDS, - "rp2040": RP2040_BOARDS, - "bk72xx": BK72XX_BOARDS, - "rtl87xx": RTL87XX_BOARDS, - } - # filter all ESP32 variants by requested platform - if platform.startswith("esp32"): - boards = { - k: v - for k, v in platform_to_boards["esp32"].items() - if v[const.KEY_VARIANT] == platform.upper() - } - else: - boards = platform_to_boards[platform] - - # map to a {board_name: board_title} dict - platform_boards = {key: val[const.KEY_NAME] for key, val in boards.items()} - # sort by board title - boards_items = sorted(platform_boards.items(), key=lambda item: item[1]) - output = [{"items": dict(boards_items)}] - - self.set_header("content-type", "application/json") - self.write(json.dumps(output)) - - -class MDNSStatusThread(threading.Thread): - def run(self): - global IMPORT_RESULT - - zc = EsphomeZeroconf() - - def on_update(dat): - for key, b in dat.items(): - PING_RESULT[key] = b - - stat = DashboardStatus(zc, on_update) - imports = DashboardImportDiscovery(zc) - - stat.start() - while not STOP_EVENT.is_set(): - entries = _list_dashboard_entries() - hosts = {} - for entry in entries: - if entry.no_mdns is not True: - hosts[entry.filename] = f"{entry.name}.local." - - stat.request_query(hosts) - IMPORT_RESULT = imports.import_state - - PING_REQUEST.wait() - PING_REQUEST.clear() - - stat.stop() - stat.join() - imports.cancel() - zc.close() - - -class PingStatusThread(threading.Thread): - def run(self): - with multiprocessing.Pool(processes=8) as pool: - while not STOP_EVENT.wait(2): - # Only do pings if somebody has the dashboard open - - def callback(ret): - PING_RESULT[ret[0]] = ret[1] - - entries = _list_dashboard_entries() - queue = collections.deque() - for entry in entries: - if entry.address is None: - PING_RESULT[entry.filename] = None - continue - - result = pool.apply_async( - _ping_func, (entry.filename, entry.address), callback=callback + self._watcher = asyncio.ThreadedChildWatcher() + if threading.current_thread() is threading.main_thread(): + self._watcher.attach_loop( + self._local._loop # type: ignore[attr-defined] # pylint: disable=protected-access ) - queue.append(result) - - while queue: - item = queue[0] - if item.ready(): - queue.popleft() - continue - - try: - item.get(0.1) - except OSError: - # ping not installed - pass - except multiprocessing.TimeoutError: - pass - - if STOP_EVENT.is_set(): - pool.terminate() - return - - PING_REQUEST.wait() - PING_REQUEST.clear() - - -class MqttStatusThread(threading.Thread): - def run(self): - from esphome import mqtt - - entries = _list_dashboard_entries() - - config = mqtt.config_from_env() - topic = "esphome/discover/#" - - def on_message(client, userdata, msg): - nonlocal entries - - payload = msg.payload.decode(errors="backslashreplace") - if len(payload) > 0: - data = json.loads(payload) - if "name" not in data: - return - for entry in entries: - if entry.name == data["name"]: - PING_RESULT[entry.filename] = True - return - - def on_connect(client, userdata, flags, return_code): - client.publish("esphome/discover", None, retain=False) - - mqttid = str(binascii.hexlify(os.urandom(6)).decode()) - - client = mqtt.prepare( - config, - [topic], - on_message, - on_connect, - None, - None, - f"esphome-dashboard-{mqttid}", - ) - client.loop_start() - - while not STOP_EVENT.wait(2): - # update entries - entries = _list_dashboard_entries() - - # will be set to true on on_message - for entry in entries: - if entry.no_mdns: - PING_RESULT[entry.filename] = False - - client.publish("esphome/discover", None, retain=False) - MQTT_PING_REQUEST.wait() - MQTT_PING_REQUEST.clear() - - client.disconnect() - client.loop_stop() - - -class PingRequestHandler(BaseHandler): - @authenticated - def get(self): - PING_REQUEST.set() - if settings.status_use_mqtt: - MQTT_PING_REQUEST.set() - self.set_header("content-type", "application/json") - self.write(json.dumps(PING_RESULT)) - - -class InfoRequestHandler(BaseHandler): - @authenticated - @bind_config - def get(self, configuration=None): - yaml_path = settings.rel_path(configuration) - all_yaml_files = settings.list_yaml_files() - if yaml_path not in all_yaml_files: - self.set_status(404) - return - - self.set_header("content-type", "application/json") - self.write(DashboardEntry(yaml_path).storage.to_json()) - - -class EditRequestHandler(BaseHandler): - @authenticated - @bind_config - def get(self, configuration=None): - filename = settings.rel_path(configuration) - content = "" - if os.path.isfile(filename): - with open(file=filename, encoding="utf-8") as f: - content = f.read() - self.write(content) - - @authenticated - @bind_config - def post(self, configuration=None): - with open(file=settings.rel_path(configuration), mode="wb") as f: - f.write(self.request.body) - self.set_status(200) - - -class DeleteRequestHandler(BaseHandler): - @authenticated - @bind_config - def post(self, configuration=None): - config_file = settings.rel_path(configuration) - storage_path = ext_storage_path(settings.config_dir, configuration) - - trash_path = trash_storage_path(settings.config_dir) - mkdir_p(trash_path) - shutil.move(config_file, os.path.join(trash_path, configuration)) - - storage_json = StorageJSON.load(storage_path) - if storage_json is not None: - # Delete build folder (if exists) - name = storage_json.name - build_folder = os.path.join(settings.config_dir, name) - if build_folder is not None: - shutil.rmtree(build_folder, os.path.join(trash_path, name)) - - # Remove the old ping result from the cache - PING_RESULT.pop(configuration, None) - - -class UndoDeleteRequestHandler(BaseHandler): - @authenticated - @bind_config - def post(self, configuration=None): - config_file = settings.rel_path(configuration) - trash_path = trash_storage_path(settings.config_dir) - shutil.move(os.path.join(trash_path, configuration), config_file) - - -PING_RESULT: dict = {} -IMPORT_RESULT = {} -STOP_EVENT = threading.Event() -PING_REQUEST = threading.Event() -MQTT_PING_REQUEST = threading.Event() + @property + def loop_name(self) -> str: + """Return name of the loop.""" + return self._loop_factory.__name__ # type: ignore[no-any-return,attr-defined] + def new_event_loop(self) -> asyncio.AbstractEventLoop: + """Get the event loop.""" + loop: asyncio.AbstractEventLoop = super().new_event_loop() + loop.set_exception_handler(_async_loop_exception_handler) -class LoginHandler(BaseHandler): - def get(self): - if is_authenticated(self): - self.redirect("./") - else: - self.render_login_page() + if self.debug: + loop.set_debug(True) - def render_login_page(self, error=None): - self.render( - "login.template.html", - error=error, - ha_addon=settings.using_ha_addon_auth, - has_username=bool(settings.username), - **template_args(), + executor = ThreadPoolExecutor( + thread_name_prefix="SyncWorker", max_workers=MAX_EXECUTOR_WORKERS ) - - def post_ha_addon_login(self): - import requests - - headers = { - "X-Supervisor-Token": os.getenv("SUPERVISOR_TOKEN"), - } - - data = { - "username": self.get_argument("username", ""), - "password": self.get_argument("password", ""), - } - try: - req = requests.post( - "http://supervisor/auth", headers=headers, json=data, timeout=30 - ) - if req.status_code == 200: - self.set_secure_cookie("authenticated", cookie_authenticated_yes) - self.redirect("/") - return - except Exception as err: # pylint: disable=broad-except - _LOGGER.warning("Error during Hass.io auth request: %s", err) - self.set_status(500) - self.render_login_page(error="Internal server error") - return - self.set_status(401) - self.render_login_page(error="Invalid username or password") - - def post_native_login(self): - username = self.get_argument("username", "") - password = self.get_argument("password", "") - if settings.check_password(username, password): - self.set_secure_cookie("authenticated", cookie_authenticated_yes) - self.redirect("./") - return - error_str = ( - "Invalid username or password" if settings.username else "Invalid password" + loop.set_default_executor(executor) + # bind the built-in time.monotonic directly as loop.time to avoid the + # overhead of the additional method call since its the most called loop + # method and its roughly 10%+ of all the call time in base_events.py + loop.time = monotonic # type: ignore[method-assign] + return loop + + +def _async_loop_exception_handler(_: Any, context: dict[str, Any]) -> None: + """Handle all exception inside the core loop.""" + kwargs = {} + if exception := context.get("exception"): + kwargs["exc_info"] = (type(exception), exception, exception.__traceback__) + + logger = logging.getLogger(__package__) + if source_traceback := context.get("source_traceback"): + stack_summary = "".join(traceback.format_list(source_traceback)) + logger.error( + "Error doing job: %s: %s", + context["message"], + stack_summary, + **kwargs, # type: ignore[arg-type] ) - self.set_status(401) - self.render_login_page(error=error_str) - - def post(self): - if settings.using_ha_addon_auth: - self.post_ha_addon_login() - else: - self.post_native_login() - - -class LogoutHandler(BaseHandler): - @authenticated - def get(self): - self.clear_cookie("authenticated") - self.redirect("./login") - - -class SecretKeysRequestHandler(BaseHandler): - @authenticated - def get(self): - filename = None - - for secret_filename in const.SECRETS_FILES: - relative_filename = settings.rel_path(secret_filename) - if os.path.isfile(relative_filename): - filename = relative_filename - break - - if filename is None: - self.send_error(404) - return - - secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False)) - - self.set_header("content-type", "application/json") - self.write(json.dumps(secret_keys)) - - -class SafeLoaderIgnoreUnknown(yaml.SafeLoader): - def ignore_unknown(self, node): - return f"{node.tag} {node.value}" - - def construct_yaml_binary(self, node) -> str: - return super().construct_yaml_binary(node).decode("ascii") - - -SafeLoaderIgnoreUnknown.add_constructor(None, SafeLoaderIgnoreUnknown.ignore_unknown) -SafeLoaderIgnoreUnknown.add_constructor( - "tag:yaml.org,2002:binary", SafeLoaderIgnoreUnknown.construct_yaml_binary -) - + return -class JsonConfigRequestHandler(BaseHandler): - @authenticated - @bind_config - def get(self, configuration=None): - filename = settings.rel_path(configuration) - if not os.path.isfile(filename): - self.send_error(404) - return - - args = ["esphome", "config", filename, "--show-secrets"] - - rc, stdout, _ = run_system_command(*args) - - if rc != 0: - self.send_error(422) - return - - data = yaml.load(stdout, Loader=SafeLoaderIgnoreUnknown) - self.set_header("content-type", "application/json") - self.write(json.dumps(data)) - self.finish() - - -def get_base_frontend_path(): - if ENV_DEV not in os.environ: - import esphome_dashboard - - return esphome_dashboard.where() - - static_path = os.environ[ENV_DEV] - if not static_path.endswith("/"): - static_path += "/" - - # This path can be relative, so resolve against the root or else templates don't work - return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard")) - - -def get_static_path(*args): - return os.path.join(get_base_frontend_path(), "static", *args) - - -@functools.cache -def get_static_file_url(name): - base = f"./static/{name}" - - if ENV_DEV in os.environ: - return base - - # Module imports can't deduplicate if stuff added to url - if name == "js/esphome/index.js": - import esphome_dashboard - - return base.replace("index.js", esphome_dashboard.entrypoint()) - - path = get_static_path(name) - with open(path, "rb") as f_handle: - hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] - return f"{base}?hash={hash_}" - - -def make_app(debug=get_bool_env(ENV_DEV)): - def log_function(handler): - if handler.get_status() < 400: - log_method = access_log.info - - if isinstance(handler, SerialPortRequestHandler) and not debug: - return - if isinstance(handler, PingRequestHandler) and not debug: - return - elif handler.get_status() < 500: - log_method = access_log.warning - else: - log_method = access_log.error - - request_time = 1000.0 * handler.request.request_time() - # pylint: disable=protected-access - log_method( - "%d %s %.2fms", - handler.get_status(), - handler._request_summary(), - request_time, - ) - - class StaticFileHandler(tornado.web.StaticFileHandler): - def set_extra_headers(self, path): - if "favicon.ico" in path: - self.set_header("Cache-Control", "max-age=84600, public") - else: - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) - - app_settings = { - "debug": debug, - "cookie_secret": settings.cookie_secret, - "log_function": log_function, - "websocket_ping_interval": 30.0, - "template_path": get_base_frontend_path(), - } - rel = settings.relative_url - app = tornado.web.Application( - [ - (f"{rel}", MainRequestHandler), - (f"{rel}login", LoginHandler), - (f"{rel}logout", LogoutHandler), - (f"{rel}logs", EsphomeLogsHandler), - (f"{rel}upload", EsphomeUploadHandler), - (f"{rel}run", EsphomeRunHandler), - (f"{rel}compile", EsphomeCompileHandler), - (f"{rel}validate", EsphomeValidateHandler), - (f"{rel}clean-mqtt", EsphomeCleanMqttHandler), - (f"{rel}clean", EsphomeCleanHandler), - (f"{rel}vscode", EsphomeVscodeHandler), - (f"{rel}ace", EsphomeAceEditorHandler), - (f"{rel}update-all", EsphomeUpdateAllHandler), - (f"{rel}info", InfoRequestHandler), - (f"{rel}edit", EditRequestHandler), - (f"{rel}download.bin", DownloadBinaryRequestHandler), - (f"{rel}serial-ports", SerialPortRequestHandler), - (f"{rel}ping", PingRequestHandler), - (f"{rel}delete", DeleteRequestHandler), - (f"{rel}undo-delete", UndoDeleteRequestHandler), - (f"{rel}wizard", WizardRequestHandler), - (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), - (f"{rel}devices", ListDevicesHandler), - (f"{rel}import", ImportRequestHandler), - (f"{rel}secret_keys", SecretKeysRequestHandler), - (f"{rel}json-config", JsonConfigRequestHandler), - (f"{rel}rename", EsphomeRenameHandler), - (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), - (f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler), - (f"{rel}version", EsphomeVersionHandler), - ], - **app_settings, + logger.error( + "Error doing job: %s", + context["message"], + **kwargs, # type: ignore[arg-type] ) - return app - -def start_web_server(args): +def start_dashboard(args) -> None: + """Start the dashboard.""" settings.parse_args(args) - mkdir_p(settings.rel_path(".esphome")) if settings.using_auth: - path = esphome_storage_path(settings.config_dir) + path = esphome_storage_path() storage = EsphomeStorageJSON.load(path) if storage is None: storage = EsphomeStorageJSON.get_default() storage.save(path) settings.cookie_secret = storage.cookie_secret - app = make_app(args.verbose) - if args.socket is not None: - _LOGGER.info( - "Starting dashboard web server on unix socket %s and configuration dir %s...", - args.socket, - settings.config_dir, - ) - server = tornado.httpserver.HTTPServer(app) - socket = tornado.netutil.bind_unix_socket(args.socket, mode=0o666) - server.add_socket(socket) - else: - _LOGGER.info( - "Starting dashboard web server on http://%s:%s and configuration dir %s...", - args.address, - args.port, - settings.config_dir, - ) - app.listen(args.port, args.address) + asyncio.set_event_loop_policy(DashboardEventLoopPolicy(settings.verbose)) + + try: + asyncio.run(async_start(args)) + except KeyboardInterrupt: + pass - if args.open_ui: - import webbrowser - webbrowser.open(f"http://{args.address}:{args.port}") +async def async_start(args) -> None: + """Start the dashboard.""" + dashboard = DASHBOARD + await dashboard.async_setup() + sock: socket.socket | None = args.socket + address: str | None = args.address + port: int | None = args.port - if settings.status_use_ping: - status_thread = PingStatusThread() - else: - status_thread = MDNSStatusThread() - status_thread.start() + start_web_server(make_app(args.verbose), sock, address, port, settings.config_dir) - if settings.status_use_mqtt: - status_thread_mqtt = MqttStatusThread() - status_thread_mqtt.start() + if args.open_ui: + import webbrowser + + webbrowser.open(f"http://{args.address}:{args.port}") try: - tornado.ioloop.IOLoop.current().start() - except KeyboardInterrupt: - _LOGGER.info("Shutting down...") - STOP_EVENT.set() - PING_REQUEST.set() - status_thread.join() - if settings.status_use_mqtt: - status_thread_mqtt.join() - MQTT_PING_REQUEST.set() - if args.socket is not None: - os.remove(args.socket) + await dashboard.async_run() + finally: + if sock: + os.remove(sock) diff --git a/esphome/dashboard/dns.py b/esphome/dashboard/dns.py new file mode 100644 index 000000000000..b78a909220d1 --- /dev/null +++ b/esphome/dashboard/dns.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import asyncio +import sys + +from icmplib import NameLookupError, async_resolve + +if sys.version_info >= (3, 11): + from asyncio import timeout as async_timeout +else: + from async_timeout import timeout as async_timeout + + +async def _async_resolve_wrapper(hostname: str) -> list[str] | Exception: + """Wrap the icmplib async_resolve function.""" + try: + async with async_timeout(2): + return await async_resolve(hostname) + except (asyncio.TimeoutError, NameLookupError, UnicodeError) as ex: + return ex + + +class DNSCache: + """DNS cache for the dashboard.""" + + def __init__(self, ttl: int | None = 120) -> None: + """Initialize the DNSCache.""" + self._cache: dict[str, tuple[float, list[str] | Exception]] = {} + self._ttl = ttl + + async def async_resolve( + self, hostname: str, now_monotonic: float + ) -> list[str] | Exception: + """Resolve a hostname to a list of IP address.""" + if expire_time_addresses := self._cache.get(hostname): + expire_time, addresses = expire_time_addresses + if expire_time > now_monotonic: + return addresses + + expires = now_monotonic + self._ttl + addresses = await _async_resolve_wrapper(hostname) + self._cache[hostname] = (expires, addresses) + return addresses diff --git a/esphome/dashboard/entries.py b/esphome/dashboard/entries.py new file mode 100644 index 000000000000..cd318ba8a7f4 --- /dev/null +++ b/esphome/dashboard/entries.py @@ -0,0 +1,397 @@ +from __future__ import annotations + +import asyncio +import logging +import os +from collections import defaultdict +from typing import TYPE_CHECKING, Any + +from esphome import const, util +from esphome.storage_json import StorageJSON, ext_storage_path + +from .const import ( + DASHBOARD_COMMAND, + EVENT_ENTRY_ADDED, + EVENT_ENTRY_REMOVED, + EVENT_ENTRY_STATE_CHANGED, + EVENT_ENTRY_UPDATED, +) +from .enum import StrEnum +from .util.subprocess import async_run_system_command + +if TYPE_CHECKING: + from .core import ESPHomeDashboard + +_LOGGER = logging.getLogger(__name__) + + +DashboardCacheKeyType = tuple[int, int, float, int] + +# Currently EntryState is a simple +# online/offline/unknown enum, but in the future +# it may be expanded to include more states + + +class EntryState(StrEnum): + ONLINE = "online" + OFFLINE = "offline" + UNKNOWN = "unknown" + + +_BOOL_TO_ENTRY_STATE = { + True: EntryState.ONLINE, + False: EntryState.OFFLINE, + None: EntryState.UNKNOWN, +} +_ENTRY_STATE_TO_BOOL = { + EntryState.ONLINE: True, + EntryState.OFFLINE: False, + EntryState.UNKNOWN: None, +} + + +def bool_to_entry_state(value: bool) -> EntryState: + """Convert a bool to an entry state.""" + return _BOOL_TO_ENTRY_STATE[value] + + +def entry_state_to_bool(value: EntryState) -> bool | None: + """Convert an entry state to a bool.""" + return _ENTRY_STATE_TO_BOOL[value] + + +class DashboardEntries: + """Represents all dashboard entries.""" + + __slots__ = ( + "_dashboard", + "_loop", + "_config_dir", + "_entries", + "_entry_states", + "_loaded_entries", + "_update_lock", + "_name_to_entry", + ) + + def __init__(self, dashboard: ESPHomeDashboard) -> None: + """Initialize the DashboardEntries.""" + self._dashboard = dashboard + self._loop = asyncio.get_running_loop() + self._config_dir = dashboard.settings.config_dir + # Entries are stored as + # { + # "path/to/file.yaml": DashboardEntry, + # ... + # } + self._entries: dict[str, DashboardEntry] = {} + self._loaded_entries = False + self._update_lock = asyncio.Lock() + self._name_to_entry: dict[str, set[DashboardEntry]] = defaultdict(set) + + def get(self, path: str) -> DashboardEntry | None: + """Get an entry by path.""" + return self._entries.get(path) + + def get_by_name(self, name: str) -> set[DashboardEntry] | None: + """Get an entry by name.""" + return self._name_to_entry.get(name) + + async def _async_all(self) -> list[DashboardEntry]: + """Return all entries.""" + return list(self._entries.values()) + + def all(self) -> list[DashboardEntry]: + """Return all entries.""" + return asyncio.run_coroutine_threadsafe(self._async_all, self._loop).result() + + def async_all(self) -> list[DashboardEntry]: + """Return all entries.""" + return list(self._entries.values()) + + def set_state(self, entry: DashboardEntry, state: EntryState) -> None: + """Set the state for an entry.""" + asyncio.run_coroutine_threadsafe( + self._async_set_state(entry, state), self._loop + ).result() + + async def _async_set_state(self, entry: DashboardEntry, state: EntryState) -> None: + """Set the state for an entry.""" + self.async_set_state(entry, state) + + def async_set_state(self, entry: DashboardEntry, state: EntryState) -> None: + """Set the state for an entry.""" + if entry.state == state: + return + entry.state = state + self._dashboard.bus.async_fire( + EVENT_ENTRY_STATE_CHANGED, {"entry": entry, "state": state} + ) + + async def async_request_update_entries(self) -> None: + """Request an update of the dashboard entries from disk. + + If an update is already in progress, this will do nothing. + """ + if self._update_lock.locked(): + _LOGGER.debug("Dashboard entries are already being updated") + return + await self.async_update_entries() + + async def async_update_entries(self) -> None: + """Update the dashboard entries from disk.""" + async with self._update_lock: + await self._async_update_entries() + + def _load_entries( + self, entries: dict[DashboardEntry, DashboardCacheKeyType] + ) -> None: + """Load all entries from disk.""" + for entry, cache_key in entries.items(): + _LOGGER.debug( + "Loading dashboard entry %s because cache key changed: %s", + entry.path, + cache_key, + ) + entry.load_from_disk(cache_key) + + async def _async_update_entries(self) -> list[DashboardEntry]: + """Sync the dashboard entries from disk.""" + _LOGGER.debug("Updating dashboard entries") + # At some point it would be nice to use watchdog to avoid polling + + path_to_cache_key = await self._loop.run_in_executor( + None, self._get_path_to_cache_key + ) + entries = self._entries + name_to_entry = self._name_to_entry + added: dict[DashboardEntry, DashboardCacheKeyType] = {} + updated: dict[DashboardEntry, DashboardCacheKeyType] = {} + removed: set[DashboardEntry] = { + entry + for filename, entry in entries.items() + if filename not in path_to_cache_key + } + original_names: dict[DashboardEntry, str] = {} + + for path, cache_key in path_to_cache_key.items(): + if not (entry := entries.get(path)): + entry = DashboardEntry(path, cache_key) + added[entry] = cache_key + continue + + if entry.cache_key != cache_key: + updated[entry] = cache_key + original_names[entry] = entry.name + + if added or updated: + await self._loop.run_in_executor( + None, self._load_entries, {**added, **updated} + ) + + bus = self._dashboard.bus + for entry in added: + entries[entry.path] = entry + name_to_entry[entry.name].add(entry) + bus.async_fire(EVENT_ENTRY_ADDED, {"entry": entry}) + + for entry in removed: + del entries[entry.path] + name_to_entry[entry.name].discard(entry) + bus.async_fire(EVENT_ENTRY_REMOVED, {"entry": entry}) + + for entry in updated: + if (original_name := original_names[entry]) != (current_name := entry.name): + name_to_entry[original_name].discard(entry) + name_to_entry[current_name].add(entry) + bus.async_fire(EVENT_ENTRY_UPDATED, {"entry": entry}) + + def _get_path_to_cache_key(self) -> dict[str, DashboardCacheKeyType]: + """Return a dict of path to cache key.""" + path_to_cache_key: dict[str, DashboardCacheKeyType] = {} + # + # The cache key is (inode, device, mtime, size) + # which allows us to avoid locking since it ensures + # every iteration of this call will always return the newest + # items from disk at the cost of a stat() call on each + # file which is much faster than reading the file + # for the cache hit case which is the common case. + # + for file in util.list_yaml_files([self._config_dir]): + try: + # Prefer the json storage path if it exists + stat = os.stat(ext_storage_path(os.path.basename(file))) + except OSError: + try: + # Fallback to the yaml file if the storage + # file does not exist or could not be generated + stat = os.stat(file) + except OSError: + # File was deleted, ignore + continue + path_to_cache_key[file] = ( + stat.st_ino, + stat.st_dev, + stat.st_mtime, + stat.st_size, + ) + return path_to_cache_key + + def async_schedule_storage_json_update(self, filename: str) -> None: + """Schedule a task to update the storage JSON file.""" + self._dashboard.async_create_background_task( + async_run_system_command( + [*DASHBOARD_COMMAND, "compile", "--only-generate", filename] + ) + ) + + +class DashboardEntry: + """Represents a single dashboard entry. + + This class is thread-safe and read-only. + """ + + __slots__ = ( + "path", + "filename", + "_storage_path", + "cache_key", + "storage", + "state", + "_to_dict", + ) + + def __init__(self, path: str, cache_key: DashboardCacheKeyType) -> None: + """Initialize the DashboardEntry.""" + self.path = path + self.filename: str = os.path.basename(path) + self._storage_path = ext_storage_path(self.filename) + self.cache_key = cache_key + self.storage: StorageJSON | None = None + self.state = EntryState.UNKNOWN + self._to_dict: dict[str, Any] | None = None + + def __repr__(self) -> str: + """Return the representation of this entry.""" + return ( + f"DashboardEntry(path={self.path} " + f"address={self.address} " + f"web_port={self.web_port} " + f"name={self.name} " + f"no_mdns={self.no_mdns} " + f"state={self.state} " + ")" + ) + + def to_dict(self) -> dict[str, Any]: + """Return a dict representation of this entry. + + The dict includes the loaded configuration but not + the current state of the entry. + """ + if self._to_dict is None: + self._to_dict = { + "name": self.name, + "friendly_name": self.friendly_name, + "configuration": self.filename, + "loaded_integrations": sorted(self.loaded_integrations), + "deployed_version": self.update_old, + "current_version": self.update_new, + "path": self.path, + "comment": self.comment, + "address": self.address, + "web_port": self.web_port, + "target_platform": self.target_platform, + } + return self._to_dict + + def load_from_disk(self, cache_key: DashboardCacheKeyType | None = None) -> None: + """Load this entry from disk.""" + self.storage = StorageJSON.load(self._storage_path) + self._to_dict = None + # + # Currently StorageJSON.load() will return None if the file does not exist + # + # StorageJSON currently does not provide an updated cache key so we use the + # one that is passed in. + # + # The cache key was read from the disk moments ago and may be stale but + # it does not matter since we are polling anyways, and the next call to + # async_update_entries() will load it again in the extremely rare case that + # it changed between the two calls. + # + if cache_key: + self.cache_key = cache_key + + @property + def address(self) -> str | None: + """Return the address of this entry.""" + if self.storage is None: + return None + return self.storage.address + + @property + def no_mdns(self) -> bool | None: + """Return the no_mdns of this entry.""" + if self.storage is None: + return None + return self.storage.no_mdns + + @property + def web_port(self) -> int | None: + """Return the web port of this entry.""" + if self.storage is None: + return None + return self.storage.web_port + + @property + def name(self) -> str: + """Return the name of this entry.""" + if self.storage is None: + return self.filename.replace(".yml", "").replace(".yaml", "") + return self.storage.name + + @property + def friendly_name(self) -> str: + """Return the friendly name of this entry.""" + if self.storage is None: + return self.name + return self.storage.friendly_name + + @property + def comment(self) -> str | None: + """Return the comment of this entry.""" + if self.storage is None: + return None + return self.storage.comment + + @property + def target_platform(self) -> str | None: + """Return the target platform of this entry.""" + if self.storage is None: + return None + return self.storage.target_platform + + @property + def update_available(self) -> bool: + """Return if an update is available for this entry.""" + if self.storage is None: + return True + return self.update_old != self.update_new + + @property + def update_old(self) -> str: + if self.storage is None: + return "" + return self.storage.esphome_version or "" + + @property + def update_new(self) -> str: + return const.__version__ + + @property + def loaded_integrations(self) -> set[str]: + if self.storage is None: + return [] + return self.storage.loaded_integrations diff --git a/esphome/dashboard/enum.py b/esphome/dashboard/enum.py new file mode 100644 index 000000000000..0fe30cf92ab4 --- /dev/null +++ b/esphome/dashboard/enum.py @@ -0,0 +1,20 @@ +"""Enum backports from standard lib.""" + +from __future__ import annotations + +from enum import Enum +from typing import Any + + +class StrEnum(str, Enum): + """Partial backport of Python 3.11's StrEnum for our basic use cases.""" + + def __new__(cls, value: str, *args: Any, **kwargs: Any) -> StrEnum: + """Create a new StrEnum instance.""" + if not isinstance(value, str): + raise TypeError(f"{value!r} is not a string") + return super().__new__(cls, value, *args, **kwargs) + + def __str__(self) -> str: + """Return self.value.""" + return str(self.value) diff --git a/esphome/dashboard/settings.py b/esphome/dashboard/settings.py new file mode 100644 index 000000000000..1f05abab4c27 --- /dev/null +++ b/esphome/dashboard/settings.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import hmac +import os +from pathlib import Path +from typing import Any + +from esphome.core import CORE +from esphome.helpers import get_bool_env + +from .util.password import password_hash + + +class DashboardSettings: + """Settings for the dashboard.""" + + __slots__ = ( + "config_dir", + "password_hash", + "username", + "using_password", + "on_ha_addon", + "cookie_secret", + "absolute_config_dir", + "verbose", + ) + + def __init__(self) -> None: + """Initialize the dashboard settings.""" + self.config_dir: str = "" + self.password_hash: str = "" + self.username: str = "" + self.using_password: bool = False + self.on_ha_addon: bool = False + self.cookie_secret: str | None = None + self.absolute_config_dir: Path | None = None + self.verbose: bool = False + + def parse_args(self, args: Any) -> None: + """Parse the arguments.""" + self.on_ha_addon: bool = args.ha_addon + password = args.password or os.getenv("PASSWORD") or "" + if not self.on_ha_addon: + self.username = args.username or os.getenv("USERNAME") or "" + self.using_password = bool(password) + if self.using_password: + self.password_hash = password_hash(password) + self.config_dir = args.configuration + self.absolute_config_dir = Path(self.config_dir).resolve() + self.verbose = args.verbose + CORE.config_path = os.path.join(self.config_dir, ".") + + @property + def relative_url(self) -> str: + return os.getenv("ESPHOME_DASHBOARD_RELATIVE_URL") or "/" + + @property + def status_use_ping(self): + return get_bool_env("ESPHOME_DASHBOARD_USE_PING") + + @property + def status_use_mqtt(self) -> bool: + return get_bool_env("ESPHOME_DASHBOARD_USE_MQTT") + + @property + def using_ha_addon_auth(self) -> bool: + if not self.on_ha_addon: + return False + return not get_bool_env("DISABLE_HA_AUTHENTICATION") + + @property + def using_auth(self) -> bool: + return self.using_password or self.using_ha_addon_auth + + @property + def streamer_mode(self) -> bool: + return get_bool_env("ESPHOME_STREAMER_MODE") + + def check_password(self, username: str, password: str) -> bool: + if not self.using_auth: + return True + if username != self.username: + return False + + # Compare password in constant running time (to prevent timing attacks) + return hmac.compare_digest(self.password_hash, password_hash(password)) + + def rel_path(self, *args: Any) -> str: + """Return a path relative to the ESPHome config folder.""" + joined_path = os.path.join(self.config_dir, *args) + # Raises ValueError if not relative to ESPHome config folder + Path(joined_path).resolve().relative_to(self.absolute_config_dir) + return joined_path diff --git a/esphome/dashboard/status/__init__.py b/esphome/dashboard/status/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/dashboard/status/mdns.py b/esphome/dashboard/status/mdns.py new file mode 100644 index 000000000000..bd212bc563bd --- /dev/null +++ b/esphome/dashboard/status/mdns.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +import asyncio + +from esphome.zeroconf import ( + ESPHOME_SERVICE_TYPE, + AsyncEsphomeZeroconf, + DashboardBrowser, + DashboardImportDiscovery, + DashboardStatus, +) + +from ..const import SENTINEL +from ..core import DASHBOARD +from ..entries import DashboardEntry, bool_to_entry_state + + +class MDNSStatus: + """Class that updates the mdns status.""" + + def __init__(self) -> None: + """Initialize the MDNSStatus class.""" + super().__init__() + self.aiozc: AsyncEsphomeZeroconf | None = None + # This is the current mdns state for each host (True, False, None) + self.host_mdns_state: dict[str, bool | None] = {} + self._loop = asyncio.get_running_loop() + + async def async_resolve_host(self, host_name: str) -> str | None: + """Resolve a host name to an address in a thread-safe manner.""" + if aiozc := self.aiozc: + return await aiozc.async_resolve_host(host_name) + return None + + async def async_refresh_hosts(self): + """Refresh the hosts to track.""" + dashboard = DASHBOARD + host_mdns_state = self.host_mdns_state + entries = dashboard.entries + poll_names: dict[str, set[DashboardEntry]] = {} + for entry in entries.async_all(): + if entry.no_mdns: + continue + # If we just adopted/imported this host, we likely + # already have a state for it, so we should make sure + # to set it so the dashboard shows it as online + if entry.loaded_integrations and "api" not in entry.loaded_integrations: + # No api available so we have to poll since + # the device won't respond to a request to ._esphomelib._tcp.local. + poll_names.setdefault(entry.name, set()).add(entry) + elif (online := host_mdns_state.get(entry.name, SENTINEL)) != SENTINEL: + entries.async_set_state(entry, bool_to_entry_state(online)) + + if poll_names and self.aiozc: + results = await asyncio.gather( + *(self.aiozc.async_resolve_host(name) for name in poll_names) + ) + for name, address in zip(poll_names, results): + result = bool(address) + host_mdns_state[name] = result + for entry in poll_names[name]: + entries.async_set_state(entry, bool_to_entry_state(result)) + + async def async_run(self) -> None: + dashboard = DASHBOARD + entries = dashboard.entries + aiozc = AsyncEsphomeZeroconf() + self.aiozc = aiozc + host_mdns_state = self.host_mdns_state + + def on_update(dat: dict[str, bool | None]) -> None: + """Update the entry state.""" + for name, result in dat.items(): + host_mdns_state[name] = result + if matching_entries := entries.get_by_name(name): + for entry in matching_entries: + if not entry.no_mdns: + entries.async_set_state(entry, bool_to_entry_state(result)) + + stat = DashboardStatus(on_update) + imports = DashboardImportDiscovery() + dashboard.import_result = imports.import_state + + browser = DashboardBrowser( + aiozc.zeroconf, + ESPHOME_SERVICE_TYPE, + [stat.browser_callback, imports.browser_callback], + ) + + ping_request = dashboard.ping_request + while not dashboard.stop_event.is_set(): + await self.async_refresh_hosts() + await ping_request.wait() + ping_request.clear() + + await browser.async_cancel() + await aiozc.async_close() + self.aiozc = None diff --git a/esphome/dashboard/status/mqtt.py b/esphome/dashboard/status/mqtt.py new file mode 100644 index 000000000000..8c35dd253569 --- /dev/null +++ b/esphome/dashboard/status/mqtt.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import binascii +import json +import os +import threading + +from esphome import mqtt + +from ..core import DASHBOARD +from ..entries import EntryState + + +class MqttStatusThread(threading.Thread): + """Status thread to get the status of the devices via MQTT.""" + + def run(self) -> None: + """Run the status thread.""" + dashboard = DASHBOARD + entries = dashboard.entries + current_entries = entries.all() + + config = mqtt.config_from_env() + topic = "esphome/discover/#" + + def on_message(client, userdata, msg): + nonlocal current_entries + + payload = msg.payload.decode(errors="backslashreplace") + if len(payload) > 0: + data = json.loads(payload) + if "name" not in data: + return + for entry in current_entries: + if entry.name == data["name"]: + entries.set_state(entry, EntryState.ONLINE) + return + + def on_connect(client, userdata, flags, return_code): + client.publish("esphome/discover", None, retain=False) + + mqttid = str(binascii.hexlify(os.urandom(6)).decode()) + + client = mqtt.prepare( + config, + [topic], + on_message, + on_connect, + None, + None, + f"esphome-dashboard-{mqttid}", + ) + client.loop_start() + + while not dashboard.stop_event.wait(2): + current_entries = entries.all() + # will be set to true on on_message + for entry in current_entries: + if entry.no_mdns: + entries.set_state(entry, EntryState.OFFLINE) + + client.publish("esphome/discover", None, retain=False) + dashboard.mqtt_ping_request.wait() + dashboard.mqtt_ping_request.clear() + + client.disconnect() + client.loop_stop() diff --git a/esphome/dashboard/status/ping.py b/esphome/dashboard/status/ping.py new file mode 100644 index 000000000000..6630f03c9dfe --- /dev/null +++ b/esphome/dashboard/status/ping.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import asyncio +import logging +import time +from typing import cast + +from icmplib import Host, SocketPermissionError, async_ping + +from ..const import MAX_EXECUTOR_WORKERS +from ..core import DASHBOARD +from ..entries import DashboardEntry, EntryState, bool_to_entry_state +from ..util.itertools import chunked + +_LOGGER = logging.getLogger(__name__) + +GROUP_SIZE = int(MAX_EXECUTOR_WORKERS / 2) + + +class PingStatus: + def __init__(self) -> None: + """Initialize the PingStatus class.""" + super().__init__() + self._loop = asyncio.get_running_loop() + + async def async_run(self) -> None: + """Run the ping status.""" + dashboard = DASHBOARD + entries = dashboard.entries + privileged = await _can_use_icmp_lib_with_privilege() + if privileged is None: + _LOGGER.warning("Cannot use icmplib because privileges are insufficient") + return + + while not dashboard.stop_event.is_set(): + # Only ping if the dashboard is open + await dashboard.ping_request.wait() + dashboard.ping_request.clear() + current_entries = dashboard.entries.async_all() + to_ping: list[DashboardEntry] = [ + entry for entry in current_entries if entry.address is not None + ] + + # Resolve DNS for all entries + entries_with_addresses: dict[DashboardEntry, list[str]] = {} + for ping_group in chunked(to_ping, GROUP_SIZE): + ping_group = cast(list[DashboardEntry], ping_group) + now_monotonic = time.monotonic() + dns_results = await asyncio.gather( + *( + dashboard.dns_cache.async_resolve(entry.address, now_monotonic) + for entry in ping_group + ), + return_exceptions=True, + ) + + for entry, result in zip(ping_group, dns_results): + if isinstance(result, Exception): + entries.async_set_state(entry, EntryState.UNKNOWN) + continue + if isinstance(result, BaseException): + raise result + entries_with_addresses[entry] = result + + # Ping all entries with valid addresses + for ping_group in chunked(entries_with_addresses.items(), GROUP_SIZE): + entry_addresses = cast(tuple[DashboardEntry, list[str]], ping_group) + + results = await asyncio.gather( + *( + async_ping(addresses[0], privileged=privileged) + for _, addresses in entry_addresses + ), + return_exceptions=True, + ) + + for entry_addresses, result in zip(entry_addresses, results): + if isinstance(result, Exception): + ping_result = False + elif isinstance(result, BaseException): + raise result + else: + host: Host = result + ping_result = host.is_alive + entry, _ = entry_addresses + entries.async_set_state(entry, bool_to_entry_state(ping_result)) + + +async def _can_use_icmp_lib_with_privilege() -> None | bool: + """Verify we can create a raw socket.""" + try: + await async_ping("127.0.0.1", count=0, timeout=0, privileged=True) + except SocketPermissionError: + try: + await async_ping("127.0.0.1", count=0, timeout=0, privileged=False) + except SocketPermissionError: + _LOGGER.debug( + "Cannot use icmplib because privileges are insufficient to create the" + " socket" + ) + return None + + _LOGGER.debug("Using icmplib in privileged=False mode") + return False + + _LOGGER.debug("Using icmplib in privileged=True mode") + return True diff --git a/esphome/dashboard/util/__init__.py b/esphome/dashboard/util/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/dashboard/util/file.py b/esphome/dashboard/util/file.py new file mode 100644 index 000000000000..661d5f34cf35 --- /dev/null +++ b/esphome/dashboard/util/file.py @@ -0,0 +1,63 @@ +import logging +import os +import tempfile +from pathlib import Path + +_LOGGER = logging.getLogger(__name__) + + +def write_utf8_file( + filename: Path, + utf8_str: str, + private: bool = False, +) -> None: + """Write a file and rename it into place. + + Writes all or nothing. + """ + write_file(filename, utf8_str.encode("utf-8"), private) + + +# from https://github.com/home-assistant/core/blob/dev/homeassistant/util/file.py +def write_file( + filename: Path, + utf8_data: bytes, + private: bool = False, +) -> None: + """Write a file and rename it into place. + + Writes all or nothing. + """ + + tmp_filename = "" + missing_fchmod = False + try: + # Modern versions of Python tempfile create this file with mode 0o600 + with tempfile.NamedTemporaryFile( + mode="wb", dir=os.path.dirname(filename), delete=False + ) as fdesc: + fdesc.write(utf8_data) + tmp_filename = fdesc.name + if not private: + try: + os.fchmod(fdesc.fileno(), 0o644) + except AttributeError: + # os.fchmod is not available on Windows + missing_fchmod = True + + os.replace(tmp_filename, filename) + if missing_fchmod: + os.chmod(filename, 0o644) + finally: + if os.path.exists(tmp_filename): + try: + os.remove(tmp_filename) + except OSError as err: + # If we are cleaning up then something else went wrong, so + # we should suppress likely follow-on errors in the cleanup + _LOGGER.error( + "File replacement cleanup failed for %s while saving %s: %s", + tmp_filename, + filename, + err, + ) diff --git a/esphome/dashboard/util/itertools.py b/esphome/dashboard/util/itertools.py new file mode 100644 index 000000000000..54e95ef802d7 --- /dev/null +++ b/esphome/dashboard/util/itertools.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from collections.abc import Iterable +from functools import partial +from itertools import islice +from typing import Any + + +def take(take_num: int, iterable: Iterable) -> list[Any]: + """Return first n items of the iterable as a list. + + From itertools recipes + """ + return list(islice(iterable, take_num)) + + +def chunked(iterable: Iterable, chunked_num: int) -> Iterable[Any]: + """Break *iterable* into lists of length *n*. + + From more-itertools + """ + return iter(partial(take, chunked_num, iter(iterable)), []) diff --git a/esphome/dashboard/util/password.py b/esphome/dashboard/util/password.py new file mode 100644 index 000000000000..e7ea28c25d5a --- /dev/null +++ b/esphome/dashboard/util/password.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import hashlib + + +def password_hash(password: str) -> bytes: + """Create a hash of a password to transform it to a fixed-length digest. + + Note this is not meant for secure storage, but for securely comparing passwords. + """ + return hashlib.sha256(password.encode()).digest() diff --git a/esphome/dashboard/util/subprocess.py b/esphome/dashboard/util/subprocess.py new file mode 100644 index 000000000000..583dd116e35f --- /dev/null +++ b/esphome/dashboard/util/subprocess.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Iterable + + +async def async_system_command_status(command: Iterable[str]) -> bool: + """Run a system command checking only the status.""" + process = await asyncio.create_subprocess_exec( + *command, + stdin=asyncio.subprocess.DEVNULL, + stdout=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.DEVNULL, + close_fds=False, + ) + await process.wait() + return process.returncode == 0 + + +async def async_run_system_command(command: Iterable[str]) -> tuple[bool, bytes, bytes]: + """Run a system command and return a tuple of returncode, stdout, stderr.""" + process = await asyncio.create_subprocess_exec( + *command, + stdin=asyncio.subprocess.DEVNULL, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + close_fds=False, + ) + stdout, stderr = await process.communicate() + await process.wait() + return process.returncode, stdout, stderr diff --git a/esphome/dashboard/util.py b/esphome/dashboard/util/text.py similarity index 63% rename from esphome/dashboard/util.py rename to esphome/dashboard/util/text.py index a2ad530b74ec..08d2df6abfaa 100644 --- a/esphome/dashboard/util.py +++ b/esphome/dashboard/util/text.py @@ -1,17 +1,10 @@ -import hashlib +from __future__ import annotations + import unicodedata from esphome.const import ALLOWED_NAME_CHARS -def password_hash(password: str) -> bytes: - """Create a hash of a password to transform it to a fixed-length digest. - - Note this is not meant for secure storage, but for securely comparing passwords. - """ - return hashlib.sha256(password.encode()).digest() - - def strip_accents(value): return "".join( c diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py new file mode 100644 index 000000000000..9ee231278110 --- /dev/null +++ b/esphome/dashboard/web_server.py @@ -0,0 +1,1176 @@ +from __future__ import annotations + +import asyncio +import base64 +import datetime +import functools +import gzip +import hashlib +import json +import logging +import os +import secrets +import shutil +import subprocess +import threading +import time +from collections.abc import Iterable +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable, TypeVar + +import tornado +import tornado.concurrent +import tornado.gen +import tornado.httpserver +import tornado.httputil +import tornado.ioloop +import tornado.iostream +import tornado.netutil +import tornado.process +import tornado.queues +import tornado.web +import tornado.websocket +import yaml +from tornado.log import access_log +from yaml.nodes import Node + +from esphome import const, platformio_api, yaml_util +from esphome.helpers import get_bool_env, mkdir_p +from esphome.storage_json import StorageJSON, ext_storage_path, trash_storage_path +from esphome.util import get_serial_ports, shlex_quote +from esphome.yaml_util import FastestAvailableSafeLoader + +from .const import DASHBOARD_COMMAND +from .core import DASHBOARD +from .entries import EntryState, entry_state_to_bool +from .util.file import write_file +from .util.subprocess import async_run_system_command +from .util.text import friendly_name_slugify + +if TYPE_CHECKING: + from requests import Response + + +_LOGGER = logging.getLogger(__name__) + +ENV_DEV = "ESPHOME_DASHBOARD_DEV" + +COOKIE_AUTHENTICATED_YES = b"yes" + +AUTH_COOKIE_NAME = "authenticated" + + +settings = DASHBOARD.settings + + +def template_args() -> dict[str, Any]: + version = const.__version__ + if "b" in version: + docs_link = "https://beta.esphome.io/" + elif "dev" in version: + docs_link = "https://next.esphome.io/" + else: + docs_link = "https://www.esphome.io/" + + return { + "version": version, + "docs_link": docs_link, + "get_static_file_url": get_static_file_url, + "relative_url": settings.relative_url, + "streamer_mode": settings.streamer_mode, + "config_dir": settings.config_dir, + } + + +T = TypeVar("T", bound=Callable[..., Any]) + + +def authenticated(func: T) -> T: + @functools.wraps(func) + def decorator(self, *args: Any, **kwargs: Any): + if not is_authenticated(self): + self.redirect("./login") + return None + return func(self, *args, **kwargs) + + return decorator + + +def is_authenticated(handler: BaseHandler) -> bool: + """Check if the request is authenticated.""" + if settings.on_ha_addon: + # Handle ingress - disable auth on ingress port + # X-HA-Ingress is automatically stripped on the non-ingress server in nginx + header = handler.request.headers.get("X-HA-Ingress", "NO") + if str(header) == "YES": + return True + + if settings.using_auth: + return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES + + return True + + +def bind_config(func): + def decorator(self, *args, **kwargs): + configuration = self.get_argument("configuration") + kwargs = kwargs.copy() + kwargs["configuration"] = configuration + return func(self, *args, **kwargs) + + return decorator + + +# pylint: disable=abstract-method +class BaseHandler(tornado.web.RequestHandler): + pass + + +def websocket_class(cls): + # pylint: disable=protected-access + if not hasattr(cls, "_message_handlers"): + cls._message_handlers = {} + + for _, method in cls.__dict__.items(): + if hasattr(method, "_message_handler"): + cls._message_handlers[method._message_handler] = method + + return cls + + +def websocket_method(name): + def wrap(fn): + # pylint: disable=protected-access + fn._message_handler = name + return fn + + return wrap + + +@websocket_class +class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler): + """Base class for ESPHome websocket commands.""" + + def __init__( + self, + application: tornado.web.Application, + request: tornado.httputil.HTTPServerRequest, + **kwargs: Any, + ) -> None: + """Initialize the websocket.""" + super().__init__(application, request, **kwargs) + self._proc = None + self._queue = None + self._is_closed = False + # Windows doesn't support non-blocking pipes, + # use Popen() with a reading thread instead + self._use_popen = os.name == "nt" + + def open(self, *args: str, **kwargs: str) -> None: + """Handle new WebSocket connection.""" + # Ensure messages from the subprocess are sent immediately + # to avoid a 200-500ms delay when nodelay is not set. + self.set_nodelay(True) + + @authenticated + async def on_message( # pylint: disable=invalid-overridden-method + self, message: str + ) -> None: + # Since tornado 4.5, on_message is allowed to be a coroutine + # Messages are always JSON, 500 when not + json_message = json.loads(message) + type_ = json_message["type"] + # pylint: disable=no-member + handlers = type(self)._message_handlers + if type_ not in handlers: + _LOGGER.warning("Requested unknown message type %s", type_) + return + + await handlers[type_](self, json_message) + + @websocket_method("spawn") + async def handle_spawn(self, json_message: dict[str, Any]) -> None: + if self._proc is not None: + # spawn can only be called once + return + command = await self.build_command(json_message) + _LOGGER.info("Running command '%s'", " ".join(shlex_quote(x) for x in command)) + + if self._use_popen: + self._queue = tornado.queues.Queue() + # pylint: disable=consider-using-with + self._proc = subprocess.Popen( + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + stdout_thread = threading.Thread(target=self._stdout_thread) + stdout_thread.daemon = True + stdout_thread.start() + else: + self._proc = tornado.process.Subprocess( + command, + stdout=tornado.process.Subprocess.STREAM, + stderr=subprocess.STDOUT, + stdin=tornado.process.Subprocess.STREAM, + close_fds=False, + ) + self._proc.set_exit_callback(self._proc_on_exit) + + tornado.ioloop.IOLoop.current().spawn_callback(self._redirect_stdout) + + @property + def is_process_active(self) -> bool: + return self._proc is not None and self._proc.returncode is None + + @websocket_method("stdin") + async def handle_stdin(self, json_message: dict[str, Any]) -> None: + if not self.is_process_active: + return + text: str = json_message["data"] + data = text.encode("utf-8", "replace") + _LOGGER.debug("< stdin: %s", data) + self._proc.stdin.write(data) + + @tornado.gen.coroutine + def _redirect_stdout(self) -> None: + reg = b"[\n\r]" + + while True: + try: + if self._use_popen: + data: bytes = yield self._queue.get() + if data is None: + self._proc_on_exit(self._proc.poll()) + break + else: + data: bytes = yield self._proc.stdout.read_until_regex(reg) + except tornado.iostream.StreamClosedError: + break + + text = data.decode("utf-8", "replace") + _LOGGER.debug("> stdout: %s", text) + self.write_message({"event": "line", "data": text}) + + def _stdout_thread(self) -> None: + if not self._use_popen: + return + while True: + data = self._proc.stdout.readline() + if data: + data = data.replace(b"\r", b"") + self._queue.put_nowait(data) + if self._proc.poll() is not None: + break + self._proc.wait(1.0) + self._queue.put_nowait(None) + + def _proc_on_exit(self, returncode: int) -> None: + if not self._is_closed: + # Check if the proc was not forcibly closed + _LOGGER.info("Process exited with return code %s", returncode) + self.write_message({"event": "exit", "code": returncode}) + + def on_close(self) -> None: + # Check if proc exists (if 'start' has been run) + if self.is_process_active: + _LOGGER.debug("Terminating process") + if self._use_popen: + self._proc.terminate() + else: + self._proc.proc.terminate() + # Shutdown proc on WS close + self._is_closed = True + + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + raise NotImplementedError + + +class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): + """Base class for commands that require a port.""" + + async def build_device_command( + self, args: list[str], json_message: dict[str, Any] + ) -> list[str]: + """Build the command to run.""" + dashboard = DASHBOARD + entries = dashboard.entries + configuration = json_message["configuration"] + config_file = settings.rel_path(configuration) + port = json_message["port"] + if ( + port == "OTA" # pylint: disable=too-many-boolean-expressions + and (entry := entries.get(config_file)) + and entry.loaded_integrations + and "api" in entry.loaded_integrations + ): + if (mdns := dashboard.mdns_status) and ( + address := await mdns.async_resolve_host(entry.name) + ): + # Use the IP address if available but only + # if the API is loaded and the device is online + # since MQTT logging will not work otherwise + port = address + elif ( + entry.address + and ( + address_list := await dashboard.dns_cache.async_resolve( + entry.address, time.monotonic() + ) + ) + and not isinstance(address_list, Exception) + ): + # If mdns is not available, try to use the DNS cache + port = address_list[0] + + return [ + *DASHBOARD_COMMAND, + *args, + config_file, + "--device", + port, + ] + + +class EsphomeLogsHandler(EsphomePortCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + """Build the command to run.""" + return await self.build_device_command(["logs"], json_message) + + +class EsphomeRenameHandler(EsphomeCommandWebSocket): + old_name: str + + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + config_file = settings.rel_path(json_message["configuration"]) + self.old_name = json_message["configuration"] + return [ + *DASHBOARD_COMMAND, + "rename", + config_file, + json_message["newName"], + ] + + def _proc_on_exit(self, returncode): + super()._proc_on_exit(returncode) + + if returncode != 0: + return + + # Remove the old ping result from the cache + entries = DASHBOARD.entries + if entry := entries.get(self.old_name): + entries.async_set_state(entry, EntryState.UNKNOWN) + + +class EsphomeUploadHandler(EsphomePortCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + """Build the command to run.""" + return await self.build_device_command(["upload"], json_message) + + +class EsphomeRunHandler(EsphomePortCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + """Build the command to run.""" + return await self.build_device_command(["run"], json_message) + + +class EsphomeCompileHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + config_file = settings.rel_path(json_message["configuration"]) + command = [*DASHBOARD_COMMAND, "compile"] + if json_message.get("only_generate", False): + command.append("--only-generate") + command.append(config_file) + return command + + +class EsphomeValidateHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + config_file = settings.rel_path(json_message["configuration"]) + command = [*DASHBOARD_COMMAND, "config", config_file] + if not settings.streamer_mode: + command.append("--show-secrets") + return command + + +class EsphomeCleanMqttHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + config_file = settings.rel_path(json_message["configuration"]) + return [*DASHBOARD_COMMAND, "clean-mqtt", config_file] + + +class EsphomeCleanHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + config_file = settings.rel_path(json_message["configuration"]) + return [*DASHBOARD_COMMAND, "clean", config_file] + + +class EsphomeVscodeHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + return [*DASHBOARD_COMMAND, "-q", "vscode", "dummy"] + + +class EsphomeAceEditorHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + return [*DASHBOARD_COMMAND, "-q", "vscode", "--ace", settings.config_dir] + + +class EsphomeUpdateAllHandler(EsphomeCommandWebSocket): + async def build_command(self, json_message: dict[str, Any]) -> list[str]: + return [*DASHBOARD_COMMAND, "update-all", settings.config_dir] + + +class SerialPortRequestHandler(BaseHandler): + @authenticated + async def get(self) -> None: + ports = await asyncio.get_running_loop().run_in_executor(None, get_serial_ports) + data = [] + for port in ports: + desc = port.description + if port.path == "/dev/ttyAMA0": + desc = "UART pins on GPIO header" + split_desc = desc.split(" - ") + if len(split_desc) == 2 and split_desc[0] == split_desc[1]: + # Some serial ports repeat their values + desc = split_desc[0] + data.append({"port": port.path, "desc": desc}) + data.append({"port": "OTA", "desc": "Over-The-Air"}) + data.sort(key=lambda x: x["port"], reverse=True) + self.set_header("content-type", "application/json") + self.write(json.dumps(data)) + + +class WizardRequestHandler(BaseHandler): + @authenticated + def post(self) -> None: + from esphome import wizard + + kwargs = { + k: v + for k, v in json.loads(self.request.body.decode()).items() + if k in ("name", "platform", "board", "ssid", "psk", "password") + } + if not kwargs["name"]: + self.set_status(422) + self.set_header("content-type", "application/json") + self.write(json.dumps({"error": "Name is required"})) + return + + kwargs["friendly_name"] = kwargs["name"] + kwargs["name"] = friendly_name_slugify(kwargs["friendly_name"]) + + kwargs["ota_password"] = secrets.token_hex(16) + noise_psk = secrets.token_bytes(32) + kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode() + filename = f"{kwargs['name']}.yaml" + destination = settings.rel_path(filename) + wizard.wizard_write(path=destination, **kwargs) + self.set_status(200) + self.set_header("content-type", "application/json") + self.write(json.dumps({"configuration": filename})) + self.finish() + + +class ImportRequestHandler(BaseHandler): + @authenticated + def post(self) -> None: + from esphome.components.dashboard_import import import_config + + dashboard = DASHBOARD + args = json.loads(self.request.body.decode()) + try: + name = args["name"] + friendly_name = args.get("friendly_name") + encryption = args.get("encryption", False) + + imported_device = next( + ( + res + for res in dashboard.import_result.values() + if res.device_name == name + ), + None, + ) + + if imported_device is not None: + network = imported_device.network + if friendly_name is None: + friendly_name = imported_device.friendly_name + else: + network = const.CONF_WIFI + + import_config( + settings.rel_path(f"{name}.yaml"), + name, + friendly_name, + args["project_name"], + args["package_import_url"], + network, + encryption, + ) + # Make sure the device gets marked online right away + dashboard.ping_request.set() + except FileExistsError: + self.set_status(500) + self.write("File already exists") + return + except ValueError as e: + _LOGGER.error(e) + self.set_status(422) + self.write("Invalid package url") + return + + self.set_status(200) + self.set_header("content-type", "application/json") + self.write(json.dumps({"configuration": f"{name}.yaml"})) + self.finish() + + +class DownloadListRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration: str | None = None) -> None: + storage_path = ext_storage_path(configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + + from esphome.components.esp32 import VARIANTS as ESP32_VARIANTS + + downloads = [] + platform: str = storage_json.target_platform.lower() + if platform == const.PLATFORM_RP2040: + from esphome.components.rp2040 import get_download_types as rp2040_types + + downloads = rp2040_types(storage_json) + elif platform == const.PLATFORM_ESP8266: + from esphome.components.esp8266 import get_download_types as esp8266_types + + downloads = esp8266_types(storage_json) + elif platform.upper() in ESP32_VARIANTS: + from esphome.components.esp32 import get_download_types as esp32_types + + downloads = esp32_types(storage_json) + elif platform in (const.PLATFORM_RTL87XX, const.PLATFORM_BK72XX): + from esphome.components.libretiny import ( + get_download_types as libretiny_types, + ) + + downloads = libretiny_types(storage_json) + else: + raise ValueError(f"Unknown platform {platform}") + + self.set_status(200) + self.set_header("content-type", "application/json") + self.write(json.dumps(downloads)) + self.finish() + return + + +class DownloadBinaryRequestHandler(BaseHandler): + def _load_file(self, path: str, compressed: bool) -> bytes: + """Load a file from disk and compress it if requested.""" + with open(path, "rb") as f: + data = f.read() + if compressed: + return gzip.compress(data, 9) + return data + + @authenticated + @bind_config + async def get(self, configuration: str | None = None) -> None: + """Download a binary file.""" + loop = asyncio.get_running_loop() + compressed = self.get_argument("compressed", "0") == "1" + + storage_path = ext_storage_path(configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + + # fallback to type=, but prioritize file= + file_name = self.get_argument("type", None) + file_name = self.get_argument("file", file_name) + if file_name is None: + self.send_error(400) + return + file_name = file_name.replace("..", "").lstrip("/") + # get requested download name, or build it based on filename + download_name = self.get_argument( + "download", + f"{storage_json.name}-{file_name}", + ) + path = os.path.dirname(storage_json.firmware_bin_path) + path = os.path.join(path, file_name) + + if not Path(path).is_file(): + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = await async_run_system_command(args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + found = False + for image in idedata.extra_flash_images: + if image.path.endswith(file_name): + path = image.path + download_name = file_name + found = True + break + + if not found: + self.send_error(404) + return + + download_name = download_name + ".gz" if compressed else download_name + + self.set_header("Content-Type", "application/octet-stream") + self.set_header( + "Content-Disposition", f'attachment; filename="{download_name}"' + ) + self.set_header("Cache-Control", "no-cache") + if not Path(path).is_file(): + self.send_error(404) + return + + data = await loop.run_in_executor(None, self._load_file, path, compressed) + self.write(data) + + self.finish() + + +class EsphomeVersionHandler(BaseHandler): + @authenticated + def get(self) -> None: + self.set_header("Content-Type", "application/json") + self.write(json.dumps({"version": const.__version__})) + self.finish() + + +class ListDevicesHandler(BaseHandler): + @authenticated + async def get(self) -> None: + dashboard = DASHBOARD + await dashboard.entries.async_request_update_entries() + entries = dashboard.entries.async_all() + self.set_header("content-type", "application/json") + configured = {entry.name for entry in entries} + + self.write( + json.dumps( + { + "configured": [entry.to_dict() for entry in entries], + "importable": [ + { + "name": res.device_name, + "friendly_name": res.friendly_name, + "package_import_url": res.package_import_url, + "project_name": res.project_name, + "project_version": res.project_version, + "network": res.network, + } + for res in dashboard.import_result.values() + if res.device_name not in configured + ], + } + ) + ) + + +class MainRequestHandler(BaseHandler): + @authenticated + def get(self) -> None: + begin = bool(self.get_argument("begin", False)) + if settings.using_password: + # Simply accessing the xsrf_token sets the cookie for us + self.xsrf_token # pylint: disable=pointless-statement + else: + self.clear_cookie("_xsrf") + + self.render( + "index.template.html", + begin=begin, + **template_args(), + login_enabled=settings.using_password, + ) + + +class PrometheusServiceDiscoveryHandler(BaseHandler): + @authenticated + async def get(self) -> None: + dashboard = DASHBOARD + await dashboard.entries.async_request_update_entries() + entries = dashboard.entries.async_all() + self.set_header("content-type", "application/json") + sd = [] + for entry in entries: + if entry.web_port is None: + continue + labels = { + "__meta_name": entry.name, + "__meta_esp_platform": entry.target_platform, + "__meta_esphome_version": entry.storage.esphome_version, + } + for integration in entry.storage.loaded_integrations: + labels[f"__meta_integration_{integration}"] = "true" + sd.append( + { + "targets": [ + f"{entry.address}:{entry.web_port}", + ], + "labels": labels, + } + ) + self.write(json.dumps(sd)) + + +class BoardsRequestHandler(BaseHandler): + @authenticated + def get(self, platform: str) -> None: + # filter all ESP32 variants by requested platform + if platform.startswith("esp32"): + from esphome.components.esp32.boards import BOARDS as ESP32_BOARDS + + boards = { + k: v + for k, v in ESP32_BOARDS.items() + if v[const.KEY_VARIANT] == platform.upper() + } + elif platform == const.PLATFORM_ESP8266: + from esphome.components.esp8266.boards import BOARDS as ESP8266_BOARDS + + boards = ESP8266_BOARDS + elif platform == const.PLATFORM_RP2040: + from esphome.components.rp2040.boards import BOARDS as RP2040_BOARDS + + boards = RP2040_BOARDS + elif platform == const.PLATFORM_BK72XX: + from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS + + boards = BK72XX_BOARDS + elif platform == const.PLATFORM_RTL87XX: + from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS + + boards = RTL87XX_BOARDS + else: + raise ValueError(f"Unknown platform {platform}") + + # map to a {board_name: board_title} dict + platform_boards = {key: val[const.KEY_NAME] for key, val in boards.items()} + # sort by board title + boards_items = sorted(platform_boards.items(), key=lambda item: item[1]) + output = [{"items": dict(boards_items)}] + + self.set_header("content-type", "application/json") + self.write(json.dumps(output)) + + +class PingRequestHandler(BaseHandler): + @authenticated + def get(self) -> None: + dashboard = DASHBOARD + dashboard.ping_request.set() + if settings.status_use_mqtt: + dashboard.mqtt_ping_request.set() + self.set_header("content-type", "application/json") + + self.write( + json.dumps( + { + entry.filename: entry_state_to_bool(entry.state) + for entry in dashboard.entries.async_all() + } + ) + ) + + +class InfoRequestHandler(BaseHandler): + @authenticated + @bind_config + async def get(self, configuration: str | None = None) -> None: + yaml_path = settings.rel_path(configuration) + dashboard = DASHBOARD + entry = dashboard.entries.get(yaml_path) + + if not entry: + self.set_status(404) + return + + self.set_header("content-type", "application/json") + self.write(entry.storage.to_json()) + + +class EditRequestHandler(BaseHandler): + @authenticated + @bind_config + async def get(self, configuration: str | None = None) -> None: + """Get the content of a file.""" + if not configuration.endswith((".yaml", ".yml")): + self.send_error(404) + return + + filename = settings.rel_path(configuration) + if Path(filename).resolve().parent != settings.absolute_config_dir: + self.send_error(404) + return + + loop = asyncio.get_running_loop() + content = await loop.run_in_executor( + None, self._read_file, filename, configuration + ) + if content is not None: + self.set_header("Content-Type", "application/yaml") + self.write(content) + + def _read_file(self, filename: str, configuration: str) -> bytes | None: + """Read a file and return the content as bytes.""" + try: + with open(file=filename, encoding="utf-8") as f: + return f.read() + except FileNotFoundError: + if configuration in const.SECRETS_FILES: + return "" + self.set_status(404) + return None + + def _write_file(self, filename: str, content: bytes) -> None: + """Write a file with the given content.""" + write_file(filename, content) + + @authenticated + @bind_config + async def post(self, configuration: str | None = None) -> None: + """Write the content of a file.""" + if not configuration.endswith((".yaml", ".yml")): + self.send_error(404) + return + + filename = settings.rel_path(configuration) + if Path(filename).resolve().parent != settings.absolute_config_dir: + self.send_error(404) + return + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, self._write_file, filename, self.request.body) + # Ensure the StorageJSON is updated as well + DASHBOARD.entries.async_schedule_storage_json_update(filename) + self.set_status(200) + + +class DeleteRequestHandler(BaseHandler): + @authenticated + @bind_config + def post(self, configuration: str | None = None) -> None: + config_file = settings.rel_path(configuration) + storage_path = ext_storage_path(configuration) + + trash_path = trash_storage_path() + mkdir_p(trash_path) + shutil.move(config_file, os.path.join(trash_path, configuration)) + + storage_json = StorageJSON.load(storage_path) + if storage_json is not None: + # Delete build folder (if exists) + name = storage_json.name + build_folder = os.path.join(settings.config_dir, name) + if build_folder is not None: + shutil.rmtree(build_folder, os.path.join(trash_path, name)) + + +class UndoDeleteRequestHandler(BaseHandler): + @authenticated + @bind_config + def post(self, configuration: str | None = None) -> None: + config_file = settings.rel_path(configuration) + trash_path = trash_storage_path() + shutil.move(os.path.join(trash_path, configuration), config_file) + + +class LoginHandler(BaseHandler): + def get(self) -> None: + if is_authenticated(self): + self.redirect("./") + else: + self.render_login_page() + + def render_login_page(self, error: str | None = None) -> None: + self.render( + "login.template.html", + error=error, + ha_addon=settings.using_ha_addon_auth, + has_username=bool(settings.username), + **template_args(), + ) + + def _make_supervisor_auth_request(self) -> Response: + """Make a request to the supervisor auth endpoint.""" + import requests + + headers = {"X-Supervisor-Token": os.getenv("SUPERVISOR_TOKEN")} + data = { + "username": self.get_argument("username", ""), + "password": self.get_argument("password", ""), + } + return requests.post( + "http://supervisor/auth", headers=headers, json=data, timeout=30 + ) + + async def post_ha_addon_login(self) -> None: + loop = asyncio.get_running_loop() + try: + req = await loop.run_in_executor(None, self._make_supervisor_auth_request) + except Exception as err: # pylint: disable=broad-except + _LOGGER.warning("Error during Hass.io auth request: %s", err) + self.set_status(500) + self.render_login_page(error="Internal server error") + return + + if req.status_code == 200: + self._set_authenticated() + self.redirect("/") + return + self.set_status(401) + self.render_login_page(error="Invalid username or password") + + def _set_authenticated(self) -> None: + """Set the authenticated cookie.""" + self.set_secure_cookie(AUTH_COOKIE_NAME, COOKIE_AUTHENTICATED_YES) + + def post_native_login(self) -> None: + username = self.get_argument("username", "") + password = self.get_argument("password", "") + if settings.check_password(username, password): + self._set_authenticated() + self.redirect("./") + return + error_str = ( + "Invalid username or password" if settings.username else "Invalid password" + ) + self.set_status(401) + self.render_login_page(error=error_str) + + async def post(self): + if settings.using_ha_addon_auth: + await self.post_ha_addon_login() + else: + self.post_native_login() + + +class LogoutHandler(BaseHandler): + @authenticated + def get(self) -> None: + self.clear_cookie(AUTH_COOKIE_NAME) + self.redirect("./login") + + +class SecretKeysRequestHandler(BaseHandler): + @authenticated + def get(self) -> None: + filename = None + + for secret_filename in const.SECRETS_FILES: + relative_filename = settings.rel_path(secret_filename) + if os.path.isfile(relative_filename): + filename = relative_filename + break + + if filename is None: + self.send_error(404) + return + + secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False)) + + self.set_header("content-type", "application/json") + self.write(json.dumps(secret_keys)) + + +class SafeLoaderIgnoreUnknown(FastestAvailableSafeLoader): + def ignore_unknown(self, node: Node) -> str: + return f"{node.tag} {node.value}" + + def construct_yaml_binary(self, node: Node) -> str: + return super().construct_yaml_binary(node).decode("ascii") + + +SafeLoaderIgnoreUnknown.add_constructor(None, SafeLoaderIgnoreUnknown.ignore_unknown) +SafeLoaderIgnoreUnknown.add_constructor( + "tag:yaml.org,2002:binary", SafeLoaderIgnoreUnknown.construct_yaml_binary +) + + +class JsonConfigRequestHandler(BaseHandler): + @authenticated + @bind_config + async def get(self, configuration: str | None = None) -> None: + filename = settings.rel_path(configuration) + if not os.path.isfile(filename): + self.send_error(404) + return + + args = ["esphome", "config", filename, "--show-secrets"] + + rc, stdout, _ = await async_run_system_command(args) + + if rc != 0: + self.send_error(422) + return + + data = yaml.load(stdout, Loader=SafeLoaderIgnoreUnknown) + self.set_header("content-type", "application/json") + self.write(json.dumps(data)) + self.finish() + + +def get_base_frontend_path() -> str: + if ENV_DEV not in os.environ: + import esphome_dashboard + + return esphome_dashboard.where() + + static_path = os.environ[ENV_DEV] + if not static_path.endswith("/"): + static_path += "/" + + # This path can be relative, so resolve against the root or else templates don't work + return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard")) + + +def get_static_path(*args: Iterable[str]) -> str: + return os.path.join(get_base_frontend_path(), "static", *args) + + +@functools.cache +def get_static_file_url(name: str) -> str: + base = f"./static/{name}" + + if ENV_DEV in os.environ: + return base + + # Module imports can't deduplicate if stuff added to url + if name == "js/esphome/index.js": + import esphome_dashboard + + return base.replace("index.js", esphome_dashboard.entrypoint()) + + path = get_static_path(name) + with open(path, "rb") as f_handle: + hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] + return f"{base}?hash={hash_}" + + +def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application: + def log_function(handler: tornado.web.RequestHandler) -> None: + if handler.get_status() < 400: + log_method = access_log.info + + if isinstance(handler, SerialPortRequestHandler) and not debug: + return + if isinstance(handler, PingRequestHandler) and not debug: + return + elif handler.get_status() < 500: + log_method = access_log.warning + else: + log_method = access_log.error + + request_time = 1000.0 * handler.request.request_time() + # pylint: disable=protected-access + log_method( + "%d %s %.2fms", + handler.get_status(), + handler._request_summary(), + request_time, + ) + + class StaticFileHandler(tornado.web.StaticFileHandler): + def get_cache_time( + self, path: str, modified: datetime.datetime | None, mime_type: str + ) -> int: + """Override to customize cache control behavior.""" + if debug: + return 0 + # Assets that are hashed have ?hash= in the URL, all javascript + # filenames hashed so we can cache them for a long time + if "hash" in self.request.arguments or "/javascript" in mime_type: + return self.CACHE_MAX_AGE + return super().get_cache_time(path, modified, mime_type) + + app_settings = { + "debug": debug, + "cookie_secret": settings.cookie_secret, + "log_function": log_function, + "websocket_ping_interval": 30.0, + "template_path": get_base_frontend_path(), + "xsrf_cookies": settings.using_password, + } + rel = settings.relative_url + return tornado.web.Application( + [ + (f"{rel}", MainRequestHandler), + (f"{rel}login", LoginHandler), + (f"{rel}logout", LogoutHandler), + (f"{rel}logs", EsphomeLogsHandler), + (f"{rel}upload", EsphomeUploadHandler), + (f"{rel}run", EsphomeRunHandler), + (f"{rel}compile", EsphomeCompileHandler), + (f"{rel}validate", EsphomeValidateHandler), + (f"{rel}clean-mqtt", EsphomeCleanMqttHandler), + (f"{rel}clean", EsphomeCleanHandler), + (f"{rel}vscode", EsphomeVscodeHandler), + (f"{rel}ace", EsphomeAceEditorHandler), + (f"{rel}update-all", EsphomeUpdateAllHandler), + (f"{rel}info", InfoRequestHandler), + (f"{rel}edit", EditRequestHandler), + (f"{rel}downloads", DownloadListRequestHandler), + (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}serial-ports", SerialPortRequestHandler), + (f"{rel}ping", PingRequestHandler), + (f"{rel}delete", DeleteRequestHandler), + (f"{rel}undo-delete", UndoDeleteRequestHandler), + (f"{rel}wizard", WizardRequestHandler), + (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), + (f"{rel}devices", ListDevicesHandler), + (f"{rel}import", ImportRequestHandler), + (f"{rel}secret_keys", SecretKeysRequestHandler), + (f"{rel}json-config", JsonConfigRequestHandler), + (f"{rel}rename", EsphomeRenameHandler), + (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), + (f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler), + (f"{rel}version", EsphomeVersionHandler), + ], + **app_settings, + ) + + +def start_web_server( + app: tornado.web.Application, + socket: str | None, + address: str | None, + port: int | None, + config_dir: str, +) -> None: + """Start the web server listener.""" + if socket is None: + _LOGGER.info( + "Starting dashboard web server on http://%s:%s and configuration dir %s...", + address, + port, + config_dir, + ) + app.listen(port, address) + return + + _LOGGER.info( + "Starting dashboard web server on unix socket %s and configuration dir %s...", + socket, + config_dir, + ) + server = tornado.httpserver.HTTPServer(app) + socket = tornado.netutil.bind_unix_socket(socket, mode=0o666) + server.add_socket(socket) diff --git a/esphome/espota2.py b/esphome/espota2.py index 98d6d3a0d9ac..580536153a17 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -1,45 +1,54 @@ +from __future__ import annotations + +import gzip import hashlib +import io import logging import random import socket import sys import time -import gzip from esphome.core import EsphomeError from esphome.helpers import is_ip_address, resolve_ip_address -RESPONSE_OK = 0 -RESPONSE_REQUEST_AUTH = 1 - -RESPONSE_HEADER_OK = 64 -RESPONSE_AUTH_OK = 65 -RESPONSE_UPDATE_PREPARE_OK = 66 -RESPONSE_BIN_MD5_OK = 67 -RESPONSE_RECEIVE_OK = 68 -RESPONSE_UPDATE_END_OK = 69 -RESPONSE_SUPPORTS_COMPRESSION = 70 - -RESPONSE_ERROR_MAGIC = 128 -RESPONSE_ERROR_UPDATE_PREPARE = 129 -RESPONSE_ERROR_AUTH_INVALID = 130 -RESPONSE_ERROR_WRITING_FLASH = 131 -RESPONSE_ERROR_UPDATE_END = 132 -RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133 -RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134 -RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135 -RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136 -RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137 -RESPONSE_ERROR_NO_UPDATE_PARTITION = 138 -RESPONSE_ERROR_MD5_MISMATCH = 139 -RESPONSE_ERROR_UNKNOWN = 255 +RESPONSE_OK = 0x00 +RESPONSE_REQUEST_AUTH = 0x01 + +RESPONSE_HEADER_OK = 0x40 +RESPONSE_AUTH_OK = 0x41 +RESPONSE_UPDATE_PREPARE_OK = 0x42 +RESPONSE_BIN_MD5_OK = 0x43 +RESPONSE_RECEIVE_OK = 0x44 +RESPONSE_UPDATE_END_OK = 0x45 +RESPONSE_SUPPORTS_COMPRESSION = 0x46 +RESPONSE_CHUNK_OK = 0x47 + +RESPONSE_ERROR_MAGIC = 0x80 +RESPONSE_ERROR_UPDATE_PREPARE = 0x81 +RESPONSE_ERROR_AUTH_INVALID = 0x82 +RESPONSE_ERROR_WRITING_FLASH = 0x83 +RESPONSE_ERROR_UPDATE_END = 0x84 +RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85 +RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86 +RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87 +RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88 +RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89 +RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A +RESPONSE_ERROR_MD5_MISMATCH = 0x8B +RESPONSE_ERROR_UNKNOWN = 0xFF OTA_VERSION_1_0 = 1 +OTA_VERSION_2_0 = 2 MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] FEATURE_SUPPORTS_COMPRESSION = 0x01 + +UPLOAD_BLOCK_SIZE = 8192 +UPLOAD_BUFFER_SIZE = UPLOAD_BLOCK_SIZE * 8 + _LOGGER = logging.getLogger(__name__) @@ -184,7 +193,9 @@ def send_check(sock, data, msg): raise OTAError(f"Error sending {msg}: {err}") from err -def perform_ota(sock, password, file_handle, filename): +def perform_ota( + sock: socket.socket, password: str, file_handle: io.IOBase, filename: str +) -> None: file_contents = file_handle.read() file_size = len(file_contents) _LOGGER.info("Uploading %s (%s bytes)", filename, file_size) @@ -194,8 +205,12 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, MAGIC_BYTES, "magic bytes") _, version = receive_exactly(sock, 2, "version", RESPONSE_OK) - if version != OTA_VERSION_1_0: - raise OTAError(f"Unsupported OTA version {version}") + _LOGGER.debug("Device support OTA version: %s", version) + supported_versions = (OTA_VERSION_1_0, OTA_VERSION_2_0) + if version not in supported_versions: + raise OTAError( + f"Device uses unsupported OTA version {version}, this ESPHome supports {supported_versions}" + ) # Features send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features") @@ -254,20 +269,24 @@ def perform_ota(sock, password, file_handle, filename): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 0) # Limit send buffer (usually around 100kB) in order to have progress bar # show the actual progress - sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) + + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, UPLOAD_BUFFER_SIZE) # Set higher timeout during upload - sock.settimeout(20.0) + sock.settimeout(30.0) + start_time = time.perf_counter() offset = 0 progress = ProgressBar() while True: - chunk = upload_contents[offset : offset + 1024] + chunk = upload_contents[offset : offset + UPLOAD_BLOCK_SIZE] if not chunk: break offset += len(chunk) try: sock.sendall(chunk) + if version >= OTA_VERSION_2_0: + receive_exactly(sock, 1, "chunk OK", RESPONSE_CHUNK_OK) except OSError as err: sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err @@ -277,8 +296,9 @@ def perform_ota(sock, password, file_handle, filename): # Enable nodelay for last checks sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + duration = time.perf_counter() - start_time - _LOGGER.info("Waiting for result...") + _LOGGER.info("Upload took %.2f seconds, waiting for result...", duration) receive_exactly(sock, 1, "receive OK", RESPONSE_RECEIVE_OK) receive_exactly(sock, 1, "Update end", RESPONSE_UPDATE_END_OK) diff --git a/esphome/external_files.py b/esphome/external_files.py new file mode 100644 index 000000000000..a1422d02b127 --- /dev/null +++ b/esphome/external_files.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +import logging +from pathlib import Path +import os +from datetime import datetime +import requests +import esphome.config_validation as cv +from esphome.core import CORE, TimePeriodSeconds + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@landonr"] + +NETWORK_TIMEOUT = 30 + +IF_MODIFIED_SINCE = "If-Modified-Since" +CACHE_CONTROL = "Cache-Control" +CACHE_CONTROL_MAX_AGE = "max-age=" +CONTENT_DISPOSITION = "content-disposition" +TEMP_DIR = "temp" + + +def has_remote_file_changed(url, local_file_path): + if os.path.exists(local_file_path): + _LOGGER.debug("has_remote_file_changed: File exists at %s", local_file_path) + try: + local_modification_time = os.path.getmtime(local_file_path) + local_modification_time_str = datetime.utcfromtimestamp( + local_modification_time + ).strftime("%a, %d %b %Y %H:%M:%S GMT") + + headers = { + IF_MODIFIED_SINCE: local_modification_time_str, + CACHE_CONTROL: CACHE_CONTROL_MAX_AGE + "3600", + } + response = requests.head( + url, headers=headers, timeout=NETWORK_TIMEOUT, allow_redirects=True + ) + + _LOGGER.debug( + "has_remote_file_changed: File %s, Local modified %s, response code %d", + local_file_path, + local_modification_time_str, + response.status_code, + ) + + if response.status_code == 304: + _LOGGER.debug( + "has_remote_file_changed: File not modified since %s", + local_modification_time_str, + ) + return False + _LOGGER.debug("has_remote_file_changed: File modified") + return True + except requests.exceptions.RequestException as e: + raise cv.Invalid( + f"Could not check if {url} has changed, please check if file exists " + f"({e})" + ) + + _LOGGER.debug("has_remote_file_changed: File doesn't exists at %s", local_file_path) + return True + + +def is_file_recent(file_path: str, refresh: TimePeriodSeconds) -> bool: + if os.path.exists(file_path): + creation_time = os.path.getctime(file_path) + current_time = datetime.now().timestamp() + return current_time - creation_time <= refresh.total_seconds + return False + + +def compute_local_file_dir(domain: str) -> Path: + base_directory = Path(CORE.data_dir) / domain + base_directory.mkdir(parents=True, exist_ok=True) + + return base_directory diff --git a/esphome/git.py b/esphome/git.py index dcc3e4d0c8ee..e41777f42559 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -35,7 +35,7 @@ def run_git_command(cmd, cwd=None) -> str: def _compute_destination_path(key: str, domain: str) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / domain + base_dir = Path(CORE.data_dir) / domain h = hashlib.new("sha256") h.update(key.encode()) return base_dir / h.hexdigest()[:8] @@ -59,17 +59,14 @@ def clone_or_update( ) repo_dir = _compute_destination_path(key, domain) - fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) cmd = ["git", "clone", "--depth=1"] - if ref is not None and not fetch_pr_branch: - cmd += ["--branch", ref] cmd += ["--", url, str(repo_dir)] run_git_command(cmd) - if fetch_pr_branch: + if ref is not None: # We need to fetch the PR branch first, otherwise git will complain # about missing objects _LOGGER.info("Fetching %s", ref) diff --git a/esphome/helpers.py b/esphome/helpers.py index 4012b2067f27..4c8cb4e2ccc0 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -3,6 +3,7 @@ import logging import os +import platform from pathlib import Path from typing import Union import tempfile @@ -11,6 +12,10 @@ _LOGGER = logging.getLogger(__name__) +IS_MACOS = platform.system() == "Darwin" +IS_WINDOWS = platform.system() == "Windows" +IS_LINUX = platform.system() == "Linux" + def ensure_unique_string(preferred_string, current_strings): test_string = preferred_string @@ -357,6 +362,9 @@ def snake_case(value): return value.replace(" ", "_").lower() +_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9-_]") + + def sanitize(value): """Same behaviour as `helpers.cpp` method `str_sanitize`.""" - return re.sub("[^-_0-9a-zA-Z]", r"", value) + return _DISALLOWED_CHARS.sub("_", value) diff --git a/esphome/loader.py b/esphome/loader.py index cd21e5a50904..e0457eb42554 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -23,7 +23,9 @@ class FileResource: resource: str def path(self) -> ContextManager[Path]: - return importlib.resources.path(self.package, self.resource) + return importlib.resources.as_file( + importlib.resources.files(self.package) / self.resource + ) class ComponentManifest: @@ -57,6 +59,10 @@ def config_schema(self) -> Optional[Any]: def multi_conf(self) -> bool: return getattr(self.module, "MULTI_CONF", False) + @property + def multi_conf_no_default(self) -> bool: + return getattr(self.module, "MULTI_CONF_NO_DEFAULT", False) + @property def to_code(self) -> Optional[Callable[[Any], None]]: return getattr(self.module, "to_code", None) @@ -97,10 +103,15 @@ def resources(self) -> list[FileResource]: loaded .py file (does not look through subdirectories) """ ret = [] - for resource in importlib.resources.contents(self.package): + + for resource in ( + r.name + for r in importlib.resources.files(self.package).iterdir() + if r.is_file() + ): if Path(resource).suffix not in SOURCE_FILE_EXTENSIONS: continue - if not importlib.resources.is_resource(self.package, resource): + if not importlib.resources.files(self.package).joinpath(resource).is_file(): # Not a resource = this is a directory (yeah this is confusing) continue ret.append(FileResource(self.package, resource)) diff --git a/esphome/log.py b/esphome/log.py index b5d72e774ce9..23dc453d32d5 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -78,6 +78,7 @@ def setup_log( CORE.verbose = True elif quiet: log_level = logging.CRITICAL + CORE.quiet = True else: log_level = logging.INFO logging.basicConfig(level=log_level) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 166301005da6..667a20bcf87b 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_BROKER, + CONF_CERTIFICATE_AUTHORITY, CONF_DISCOVERY_PREFIX, CONF_ESPHOME, CONF_LOG_TOPIC, @@ -99,7 +100,9 @@ def on_disconnect(client, userdata, result_code): elif username: client.username_pw_set(username, password) - if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS): + if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS) or config[CONF_MQTT].get( + CONF_CERTIFICATE_AUTHORITY + ): if sys.version_info >= (2, 7, 13): tls_version = ssl.PROTOCOL_TLS # pylint: disable=no-member else: diff --git a/esphome/pins.py b/esphome/pins.py index 2ac4cd4b54d0..d02ad357a001 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,5 +1,7 @@ import operator from functools import reduce +import esphome.config_validation as cv +from esphome.core import CORE from esphome.const import ( CONF_INPUT, @@ -9,17 +11,123 @@ CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, + CONF_IGNORE_STRAPPING_WARNING, + CONF_ALLOW_OTHER_USES, + CONF_INVERTED, ) -from esphome.util import SimpleRegistry -from esphome.core import CORE -PIN_SCHEMA_REGISTRY = SimpleRegistry() +class PinRegistry(dict): + def __init__(self): + super().__init__() + self.pins_used = {} + def reset(self): + self.pins_used = {} -def _set_mode(value, default_mode): - import esphome.config_validation as cv + def get_count(self, key, id, number): + """ + Get the number of places a given pin is used. + :param key: The key of the registered pin schema. + :param id: The ID of the defining component + :param number: The pin number + :return: The number of places the pin is used. + """ + pin_key = (key, id, number) + return len(self.pins_used[pin_key]) if pin_key in self.pins_used else 0 + + def register(self, name, schema, final_validate=None): + """ + Register a pin schema + :param name: + :param schema: + :param final_validate: + :return: + """ + + def decorator(fun): + self[name] = (fun, schema, final_validate) + return fun + + return decorator + + def validate(self, conf, key=None): + """ + Validate a pin against a registered schema + :param conf The pin config + :param key: an optional scalar key (e.g. platform) + :return: The transformed result + """ + from esphome.config import path_context + + key = self.get_key(conf) if key is None else key + # Element 1 is the pin validation function + # evaluate here so a validation failure skips the rest + result = self[key][1](conf) + if CONF_NUMBER in result: + # key maps to the pin schema + if key != CORE.target_platform: + pin_key = (key, conf[key], result[CONF_NUMBER]) + else: + pin_key = (key, key, result[CONF_NUMBER]) + if pin_key not in self.pins_used: + self.pins_used[pin_key] = [] + # client_id identifies the instance of the providing component + client_id = result.get(key) + self.pins_used[pin_key].append((path_context.get(), client_id, result)) + # return the validated pin config + return result + def get_key(self, conf): + """ + Is there a key in conf corresponding to a registered pin schema? + If not, fall back to the default platform schema. + :param conf The config for the component + :return: the schema key + """ + keys = list(filter(lambda k: k in conf, self)) + return keys[0] if keys else CORE.target_platform + + def get_to_code(self, key): + """ + Return the code generator function for a pin schema, stored as tuple element 0 + :param conf: The pin config + :param key An optional specific key + :return: The awaitable coroutine + """ + key = self.get_key(key) if isinstance(key, dict) else key + return self[key][0] + + def final_validate(self, fconf): + """ + Run the final validation for all pins, and check for reuse + :param fconf: The full config + """ + for (key, _, _), pin_list in self.pins_used.items(): + count = len(pin_list) # number of places same pin used. + final_val_fun = self[key][2] # final validation function + for pin_path, client_id, pin_config in pin_list: + with fconf.catch_error([cv.ROOT_CONFIG_PATH] + pin_path): + if final_val_fun is not None: + # Get the containing path of the config providing this pin. + parent_path = fconf.get_path_for_id(client_id)[:-1] + parent_config = fconf.get_config_for_path(parent_path) + final_val_fun(pin_config, parent_config) + allow_others = pin_config.get(CONF_ALLOW_OTHER_USES, False) + if count != 1 and not allow_others: + raise cv.Invalid( + f"Pin {pin_config[CONF_NUMBER]} is used in multiple places" + ) + if count == 1 and allow_others: + raise cv.Invalid( + f"Pin {pin_config[CONF_NUMBER]} incorrectly sets {CONF_ALLOW_OTHER_USES}: true" + ) + + +PIN_SCHEMA_REGISTRY = PinRegistry() + + +def _set_mode(value, default_mode): if CONF_MODE not in value: return {**value, CONF_MODE: default_mode} mode = value[CONF_MODE] @@ -65,20 +173,26 @@ def validator(value): if not isinstance(value, dict): return validator({CONF_NUMBER: value}) value = _set_mode(value, default_mode) - if not internal: - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key != CORE.target_platform and key in value: - return entry[1](value) - return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value) + if internal: + return PIN_SCHEMA_REGISTRY.validate(value, CORE.target_platform) + return PIN_SCHEMA_REGISTRY.validate(value) return validator def _internal_number_creator(mode): def validator(value): - value_d = {CONF_NUMBER: value} + if isinstance(value, dict): + if CONF_MODE in value or CONF_INVERTED in value: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + value_d = value + else: + value_d = {CONF_NUMBER: value} value_d = _set_mode(value_d, mode) - return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value_d)[CONF_NUMBER] + return PIN_SCHEMA_REGISTRY.validate(value_d, CORE.target_platform)[CONF_NUMBER] return validator @@ -146,3 +260,73 @@ def gpio_flags_expr(mode): CONF_PULLUP: True, } ) + + +def check_strapping_pin(conf, strapping_pin_list, logger): + num = conf[CONF_NUMBER] + if num in strapping_pin_list and not conf.get(CONF_IGNORE_STRAPPING_WARNING): + 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", + ) + # mitigate undisciplined use of strapping: + if num not in strapping_pin_list and conf.get(CONF_IGNORE_STRAPPING_WARNING): + raise cv.Invalid(f"GPIO{num} is not a strapping pin") + + +GPIO_STANDARD_MODES = ( + CONF_INPUT, + CONF_OUTPUT, + CONF_OPEN_DRAIN, + CONF_PULLUP, + CONF_PULLDOWN, +) + + +def gpio_validate_modes(value): + if not value[CONF_INPUT] and not value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be input or output") + return value + + +def gpio_base_schema( + pin_type, + number_validator, + modes=GPIO_STANDARD_MODES, + mode_validator=gpio_validate_modes, + invertable=True, +): + """ + Generate a base gpio pin schema + :param pin_type: The type for the pin variable + :param number_validator: A validator for the pin number + :param modes: The available modes, default is all standard modes + :param mode_validator: A validator function for the pin mode + :param invertable: If the pin supports hardware inversion + :return: A schema for the pin + """ + mode_default = len(modes) == 1 + mode_dict = dict( + map(lambda m: (cv.Optional(m, default=mode_default), cv.boolean), modes) + ) + + def _number_validator(value): + if isinstance(value, str) and value.upper().startswith("GPIOX"): + raise cv.Invalid( + f"Found placeholder '{value}' when expecting a GPIO pin number.\n" + "You must replace this with an actual pin number." + ) + return number_validator(value) + + schema = cv.Schema( + { + cv.GenerateID(): cv.declare_id(pin_type), + cv.Required(CONF_NUMBER): _number_validator, + cv.Optional(CONF_ALLOW_OTHER_USES): cv.boolean, + cv.Optional(CONF_MODE, default={}): cv.All(mode_dict, mode_validator), + } + ) + if invertable: + return schema.extend({cv.Optional(CONF_INVERTED, default=False): cv.boolean}) + return schema diff --git a/esphome/schema_extractors.py b/esphome/schema_extractors.py index 2280a84849f8..5491bc88c499 100644 --- a/esphome/schema_extractors.py +++ b/esphome/schema_extractors.py @@ -8,7 +8,6 @@ However there is a property to further disable decorator impact.""" - # This is set to true by script/build_language_schema.py # only, so data is collected (again functionality is not modified) EnableSchemaExtraction = False diff --git a/esphome/storage_json.py b/esphome/storage_json.py index acf525203d1d..0a41a4f738c5 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -1,87 +1,80 @@ +from __future__ import annotations import binascii import codecs -from datetime import datetime import json import logging import os -from typing import Optional +from datetime import datetime from esphome import const +from esphome.const import CONF_DISABLED, CONF_MDNS from esphome.core import CORE from esphome.helpers import write_file_if_changed - - -from esphome.const import ( - CONF_MDNS, - CONF_DISABLED, -) - from esphome.types import CoreType _LOGGER = logging.getLogger(__name__) def storage_path() -> str: - return CORE.relative_internal_path(f"{CORE.config_filename}.json") + return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json") -def ext_storage_path(base_path: str, config_filename: str) -> str: - return os.path.join(base_path, ".esphome", f"{config_filename}.json") +def ext_storage_path(config_filename: str) -> str: + return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json") -def esphome_storage_path(base_path: str) -> str: - return os.path.join(base_path, ".esphome", "esphome.json") +def esphome_storage_path() -> str: + return os.path.join(CORE.data_dir, "esphome.json") -def trash_storage_path(base_path: str) -> str: - return os.path.join(base_path, ".esphome", "trash") +def trash_storage_path() -> str: + return CORE.relative_config_path("trash") class StorageJSON: def __init__( self, - storage_version, - name, - friendly_name, - comment, - esphome_version, - src_version, - address, - web_port, - target_platform, - build_path, - firmware_bin_path, - loaded_integrations, - no_mdns, - ): + storage_version: int, + name: str, + friendly_name: str, + comment: str, + esphome_version: str, + src_version: int | None, + address: str, + web_port: int | None, + target_platform: str, + build_path: str, + firmware_bin_path: str, + loaded_integrations: set[str], + no_mdns: bool, + ) -> None: # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) - self.storage_version: int = storage_version + self.storage_version = storage_version # The name of the node - self.name: str = name + self.name = name # The friendly name of the node - self.friendly_name: str = friendly_name + self.friendly_name = friendly_name # The comment of the node - self.comment: str = comment + self.comment = comment # The esphome version this was compiled with - self.esphome_version: str = esphome_version + self.esphome_version = esphome_version # The version of the file in src/main.cpp - Used to migrate the file assert src_version is None or isinstance(src_version, int) - self.src_version: int = src_version + self.src_version = src_version # Address of the ESP, for example livingroom.local or a static IP - self.address: str = address + self.address = address # Web server port of the ESP, for example 80 assert web_port is None or isinstance(web_port, int) - self.web_port: int = web_port + self.web_port = web_port # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc. - self.target_platform: str = target_platform + self.target_platform = target_platform # The absolute path to the platformio project - self.build_path: str = build_path + self.build_path = build_path # The absolute path to the firmware binary - self.firmware_bin_path: str = firmware_bin_path - # A list of strings of names of loaded integrations - self.loaded_integrations: list[str] = loaded_integrations - self.loaded_integrations.sort() + self.firmware_bin_path = firmware_bin_path + # A set of strings of names of loaded integrations + self.loaded_integrations = loaded_integrations # Is mDNS disabled self.no_mdns = no_mdns @@ -98,7 +91,7 @@ def as_dict(self): "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, - "loaded_integrations": self.loaded_integrations, + "loaded_integrations": sorted(self.loaded_integrations), "no_mdns": self.no_mdns, } @@ -109,9 +102,7 @@ def save(self, path): write_file_if_changed(path, self.to_json()) @staticmethod - def from_esphome_core( - esph: CoreType, old: Optional["StorageJSON"] - ) -> "StorageJSON": + def from_esphome_core(esph: CoreType, old: StorageJSON | None) -> StorageJSON: hardware = esph.target_platform.upper() if esph.is_esp32: from esphome.components import esp32 @@ -129,7 +120,7 @@ def from_esphome_core( target_platform=hardware, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, - loaded_integrations=list(esph.loaded_integrations), + loaded_integrations=esph.loaded_integrations, no_mdns=( CONF_MDNS in esph.config and CONF_DISABLED in esph.config[CONF_MDNS] @@ -140,7 +131,7 @@ def from_esphome_core( @staticmethod def from_wizard( name: str, friendly_name: str, address: str, platform: str - ) -> "StorageJSON": + ) -> StorageJSON: return StorageJSON( storage_version=1, name=name, @@ -153,12 +144,12 @@ def from_wizard( target_platform=platform, build_path=None, firmware_bin_path=None, - loaded_integrations=[], + loaded_integrations=set(), no_mdns=False, ) @staticmethod - def _load_impl(path: str) -> Optional["StorageJSON"]: + def _load_impl(path: str) -> StorageJSON | None: with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) storage_version = storage["storage_version"] @@ -174,7 +165,7 @@ def _load_impl(path: str) -> Optional["StorageJSON"]: esp_platform = storage.get("esp_platform") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") - loaded_integrations = storage.get("loaded_integrations", []) + loaded_integrations = set(storage.get("loaded_integrations", [])) no_mdns = storage.get("no_mdns", False) return StorageJSON( storage_version, @@ -193,7 +184,7 @@ def _load_impl(path: str) -> Optional["StorageJSON"]: ) @staticmethod - def load(path: str) -> Optional["StorageJSON"]: + def load(path: str) -> StorageJSON | None: try: return StorageJSON._load_impl(path) except Exception: # pylint: disable=broad-except @@ -215,7 +206,7 @@ def __init__( # The last time ESPHome checked for an update as an isoformat encoded str self.last_update_check_str: str = last_update_check # Cache of the version gotten in the last version check - self.remote_version: Optional[str] = remote_version + self.remote_version: str | None = remote_version def as_dict(self) -> dict: return { @@ -226,7 +217,7 @@ def as_dict(self) -> dict: } @property - def last_update_check(self) -> Optional[datetime]: + def last_update_check(self) -> datetime | None: try: return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S") except Exception: # pylint: disable=broad-except @@ -243,7 +234,7 @@ def save(self, path: str) -> None: write_file_if_changed(path, self.to_json()) @staticmethod - def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]: + def _load_impl(path: str) -> EsphomeStorageJSON | None: with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) storage_version = storage["storage_version"] @@ -255,14 +246,14 @@ def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]: ) @staticmethod - def load(path: str) -> Optional["EsphomeStorageJSON"]: + def load(path: str) -> EsphomeStorageJSON | None: try: return EsphomeStorageJSON._load_impl(path) except Exception: # pylint: disable=broad-except return None @staticmethod - def get_default() -> "EsphomeStorageJSON": + def get_default() -> EsphomeStorageJSON: return EsphomeStorageJSON( storage_version=1, cookie_secret=binascii.hexlify(os.urandom(64)).decode(), diff --git a/esphome/types.py b/esphome/types.py index adb16fa91b9e..27ec61ceff30 100644 --- a/esphome/types.py +++ b/esphome/types.py @@ -1,4 +1,5 @@ """This helper module tracks commonly used types in the esphome python codebase.""" + from typing import Union from esphome.core import ID, Lambda, EsphomeCore diff --git a/esphome/util.py b/esphome/util.py index 748d81ba4156..d5a4c60570f8 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -81,6 +81,12 @@ def safe_print(message="", end="\n"): print("Cannot print line because of invalid locale!") +def safe_input(prompt=""): + if prompt: + safe_print(prompt, end="") + return input() + + def shlex_quote(s): if not s: return "''" @@ -190,7 +196,7 @@ def mock_exit(return_code): try: sys.argv = list(cmd) sys.exit = mock_exit - return func() or 0 + retval = func() or 0 except KeyboardInterrupt: # pylint: disable=try-except-raise raise except SystemExit as err: @@ -206,9 +212,10 @@ def mock_exit(return_code): sys.stdout = orig_stdout sys.stderr = orig_stderr - if capture_stdout: - # pylint: disable=lost-exception - return cap_stdout.getvalue() + if capture_stdout: + return cap_stdout.getvalue() + + return retval def run_external_process(*cmd, **kwargs): diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index e2171cabedc5..9af6cb717c90 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -64,7 +64,7 @@ def _compile_mapping(self, schema, invalid_msg=None): # Recursively compile schema _compiled_schema = {} - for skey, svalue in vol.iteritems(schema): + for skey, svalue in schema.items(): new_key = self._compile(skey) new_value = self._compile(svalue) _compiled_schema[skey] = (new_key, new_value) diff --git a/esphome/vscode.py b/esphome/vscode.py index cb2f51976f11..8198d2659a65 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -1,20 +1,22 @@ +from __future__ import annotations import json import os +from io import StringIO +from typing import Any -from typing import Optional - -from esphome.config import load_config, _format_vol_invalid, Config +from esphome.yaml_util import parse_yaml +from esphome.config import validate_config, _format_vol_invalid, Config from esphome.core import CORE, DocumentRange import esphome.config_validation as cv -def _get_invalid_range(res: Config, invalid: cv.Invalid) -> Optional[DocumentRange]: +def _get_invalid_range(res: Config, invalid: cv.Invalid) -> DocumentRange | None: return res.get_deepest_document_range_for_path( invalid.path, invalid.error_message == "extra keys not allowed" ) -def _dump_range(range: Optional[DocumentRange]) -> Optional[dict]: +def _dump_range(range: DocumentRange | None) -> dict | None: if range is None: return None return { @@ -56,6 +58,25 @@ def add_validation_error(self, range_, message): ) +def _read_file_content_from_json_on_stdin() -> str: + """Read the content of a json encoded file from stdin.""" + data = json.loads(input()) + assert data["type"] == "file_response" + return data["content"] + + +def _print_file_read_event(path: str) -> None: + """Print a file read event.""" + print( + json.dumps( + { + "type": "read_file", + "path": path, + } + ) + ) + + def read_config(args): while True: CORE.reset() @@ -68,9 +89,17 @@ def read_config(args): CORE.config_path = os.path.join(args.configuration, f) else: CORE.config_path = data["file"] + + file_name = CORE.config_path + _print_file_read_event(file_name) + raw_yaml = _read_file_content_from_json_on_stdin() + command_line_substitutions: dict[str, Any] = ( + dict(args.substitution) if args.substitution else {} + ) vs = VSCodeResult() try: - res = load_config(dict(args.substitution) if args.substitution else {}) + config = parse_yaml(file_name, StringIO(raw_yaml)) + res = validate_config(config, command_line_substitutions) except Exception as err: # pylint: disable=broad-except vs.add_yaml_error(str(err)) else: diff --git a/esphome/wizard.py b/esphome/wizard.py index 0015a86cdc96..4ec366bbb9e6 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -6,12 +6,12 @@ import voluptuous as vol import esphome.config_validation as cv +from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD +from esphome.core import CORE from esphome.helpers import get_bool_env, write_file -from esphome.log import color, Fore - +from esphome.log import Fore, color from esphome.storage_json import StorageJSON, ext_storage_path -from esphome.util import safe_print -from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD +from esphome.util import safe_input, safe_print CORE_BIG = r""" _____ ____ _____ ______ / ____/ __ \| __ \| ____| @@ -51,10 +51,12 @@ friendly_name: {friendly_name} """ -LOGGER_API_CONFIG = """ +LOGGER_CONFIG = """ # Enable logging logger: +""" +API_CONFIG = """ # Enable Home Assistant API api: """ @@ -136,7 +138,12 @@ def wizard_file(**kwargs): config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs) - config += LOGGER_API_CONFIG + config += LOGGER_CONFIG + + if kwargs["board"] == "rpipico": + return config + + config += API_CONFIG # Configure API if "password" in kwargs: @@ -193,10 +200,10 @@ def wizard_file(**kwargs): def wizard_write(path, **kwargs): - from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.esp32 import boards as esp32_boards + from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.rp2040 import boards as rp2040_boards - from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards name = kwargs["name"] @@ -225,7 +232,7 @@ def wizard_write(path, **kwargs): write_file(path, wizard_file(**kwargs)) storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware) - storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) + storage_path = ext_storage_path(os.path.basename(path)) storage.save(storage_path) return True @@ -252,8 +259,7 @@ def safe_print_step(step, big): def default_input(text, default): safe_print() safe_print(f"Press ENTER for default ({default})") - safe_print(text.format(default), end="") - return input() or default + return safe_input(text.format(default)) or default # From https://stackoverflow.com/a/518232/8924614 @@ -266,9 +272,9 @@ def strip_accents(value): def wizard(path): + from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp8266 import boards as esp8266_boards - from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards if not path.endswith(".yaml") and not path.endswith(".yml"): @@ -281,6 +287,9 @@ def wizard(path): f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." ) return 2 + + CORE.config_path = path + safe_print("Hi there!") sleep(1.5) safe_print("I'm the wizard of ESPHome :)") @@ -304,8 +313,7 @@ def wizard(path): ) safe_print() sleep(1) - safe_print(color(Fore.BOLD_WHITE, "(name): "), end="") - name = input() + name = safe_input(color(Fore.BOLD_WHITE, "(name): ")) while True: try: @@ -342,8 +350,9 @@ def wizard(path): while True: sleep(0.5) safe_print() - safe_print(color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): "), end="") - platform = input() + platform = safe_input( + color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): ") + ) try: platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper()) break @@ -397,8 +406,7 @@ def wizard(path): boards.append(board_id) while True: - safe_print(color(Fore.BOLD_WHITE, "(board): "), end="") - board = input() + board = safe_input(color(Fore.BOLD_WHITE, "(board): ")) try: board = vol.All(vol.Lower, vol.Any(*boards))(board) break @@ -424,8 +432,7 @@ def wizard(path): sleep(1.5) safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'Abraham Linksys')}\".") while True: - safe_print(color(Fore.BOLD_WHITE, "(ssid): "), end="") - ssid = input() + ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): ")) try: ssid = cv.ssid(ssid) break @@ -451,8 +458,7 @@ def wizard(path): safe_print() safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'PASSWORD42')}\"") sleep(0.5) - safe_print(color(Fore.BOLD_WHITE, "(PSK): "), end="") - psk = input() + psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): ")) safe_print( "Perfect! WiFi is now set up (you can create static IPs and so on later)." ) @@ -469,8 +475,7 @@ def wizard(path): safe_print() sleep(0.25) safe_print("Press ENTER for no password") - safe_print(color(Fore.BOLD_WHITE, "(password): "), end="") - password = input() + password = safe_input(color(Fore.BOLD_WHITE, "(password): ")) if not wizard_write( path=path, diff --git a/esphome/writer.py b/esphome/writer.py index ad506b6ae6c0..3ad0e60d3118 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import Union -from esphome.config import iter_components +from esphome.config import iter_components, iter_component_configs from esphome.const import ( HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, @@ -70,14 +70,14 @@ def get_flags(key): flags = set() - for _, component, conf in iter_components(CORE.config): + for _, component, conf in iter_component_configs(CORE.config): flags |= getattr(component, key)(conf) return flags def get_include_text(): include_text = '#include "esphome.h"\nusing namespace esphome;\n' - for _, component, conf in iter_components(CORE.config): + for _, component, conf in iter_component_configs(CORE.config): if not hasattr(component, "includes"): continue includes = component.includes @@ -203,7 +203,9 @@ def write_platformio_project(): write_platformio_ini(content) -DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ +DEFINES_H_FORMAT = ( + ESPHOME_H_FORMAT +) = """\ #pragma once #include "esphome/core/macros.h" {} @@ -230,7 +232,7 @@ def write_platformio_project(): def copy_src_tree(): source_files: list[loader.FileResource] = [] - for _, component, _ in iter_components(CORE.config): + for _, component in iter_components(CORE.config): source_files += component.resources source_files_map = { Path(x.package.replace(".", "/") + "/" + x.resource): x for x in source_files diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 8a03c431a772..06bfd8b217b8 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1,23 +1,34 @@ +from __future__ import annotations + import fnmatch import functools import inspect import logging import math import os - import uuid +from io import TextIOWrapper +from typing import Any + import yaml import yaml.constructor +from yaml import SafeLoader as PurePythonLoader + +try: + from yaml import CSafeLoader as FastestAvailableSafeLoader +except ImportError: + FastestAvailableSafeLoader = PurePythonLoader from esphome import core -from esphome.config_helpers import read_config_file, Extend +from esphome.config_helpers import Extend, Remove from esphome.core import ( + CORE, + DocumentRange, EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, - DocumentRange, ) from esphome.helpers import add_class_to_obj from esphome.util import OrderedDict, filter_yaml_files @@ -88,7 +99,7 @@ def wrapped(loader, node): return wrapped -class ESPHomeLoader(yaml.SafeLoader): +class ESPHomeLoaderMixin: """Loader class that keeps track of line numbers.""" @_add_data_ref @@ -240,7 +251,18 @@ def _rel_path(self, *args): @_add_data_ref def construct_secret(self, node): - secrets = _load_yaml_internal(self._rel_path(SECRET_YAML)) + try: + secrets = _load_yaml_internal(self._rel_path(SECRET_YAML)) + except EsphomeError as e: + if self.name == CORE.config_path: + raise e + try: + main_config_dir = os.path.dirname(CORE.config_path) + main_secret_yml = os.path.join(main_config_dir, SECRET_YAML) + secrets = _load_yaml_internal(main_secret_yml) + except EsphomeError as er: + raise EsphomeError(f"{e}\n{er}") from er + if node.value not in secrets: raise yaml.MarkedYAMLError( f"Secret '{node.value}' not defined", node.start_mark @@ -262,8 +284,8 @@ def extract_file_vars(node): return file, vars def substitute_vars(config, vars): - from esphome.const import CONF_SUBSTITUTIONS from esphome.components import substitutions + from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS org_subs = None result = config @@ -274,7 +296,15 @@ def substitute_vars(config, vars): elif CONF_SUBSTITUTIONS in result: org_subs = result.pop(CONF_SUBSTITUTIONS) + defaults = {} + if CONF_DEFAULTS in result: + defaults = result.pop(CONF_DEFAULTS) + result[CONF_SUBSTITUTIONS] = vars + for k, v in defaults.items(): + if k not in result[CONF_SUBSTITUTIONS]: + result[CONF_SUBSTITUTIONS][k] = v + # Ignore missing vars that refer to the top level substitutions substitutions.do_substitution_pass(result, None, ignore_missing=True) result.pop(CONF_SUBSTITUTIONS) @@ -291,8 +321,9 @@ def substitute_vars(config, vars): file, vars = node.value, None result = _load_yaml_internal(self._rel_path(file)) - if vars: - result = substitute_vars(result, vars) + if not vars: + vars = {} + result = substitute_vars(result, vars) return result @_add_data_ref @@ -342,50 +373,81 @@ def construct_force(self, node): def construct_extend(self, node): return Extend(str(node.value)) + @_add_data_ref + def construct_remove(self, node): + return Remove(str(node.value)) + + +class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader): + """Loader class that keeps track of line numbers.""" -ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap -) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map) -ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var) -ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret) -ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include) -ESPHomeLoader.add_constructor( - "!include_dir_list", ESPHomeLoader.construct_include_dir_list -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list -) -ESPHomeLoader.add_constructor( - "!include_dir_named", ESPHomeLoader.construct_include_dir_named -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named -) -ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) -ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend) + +class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader): + """Loader class that keeps track of line numbers.""" -def load_yaml(fname, clear_secrets=True): +for _loader in (ESPHomeLoader, ESPHomePurePythonLoader): + _loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int) + _loader.add_constructor("tag:yaml.org,2002:float", _loader.construct_yaml_float) + _loader.add_constructor("tag:yaml.org,2002:binary", _loader.construct_yaml_binary) + _loader.add_constructor("tag:yaml.org,2002:omap", _loader.construct_yaml_omap) + _loader.add_constructor("tag:yaml.org,2002:str", _loader.construct_yaml_str) + _loader.add_constructor("tag:yaml.org,2002:seq", _loader.construct_yaml_seq) + _loader.add_constructor("tag:yaml.org,2002:map", _loader.construct_yaml_map) + _loader.add_constructor("!env_var", _loader.construct_env_var) + _loader.add_constructor("!secret", _loader.construct_secret) + _loader.add_constructor("!include", _loader.construct_include) + _loader.add_constructor("!include_dir_list", _loader.construct_include_dir_list) + _loader.add_constructor( + "!include_dir_merge_list", _loader.construct_include_dir_merge_list + ) + _loader.add_constructor("!include_dir_named", _loader.construct_include_dir_named) + _loader.add_constructor( + "!include_dir_merge_named", _loader.construct_include_dir_merge_named + ) + _loader.add_constructor("!lambda", _loader.construct_lambda) + _loader.add_constructor("!force", _loader.construct_force) + _loader.add_constructor("!extend", _loader.construct_extend) + _loader.add_constructor("!remove", _loader.construct_remove) + + +def load_yaml(fname: str, clear_secrets: bool = True) -> Any: if clear_secrets: _SECRET_VALUES.clear() _SECRET_CACHE.clear() return _load_yaml_internal(fname) -def _load_yaml_internal(fname): - content = read_config_file(fname) - loader = ESPHomeLoader(content) +def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any: + """Parse a YAML file.""" + try: + return _load_yaml_internal_with_type(ESPHomeLoader, file_name, file_handle) + except EsphomeError: + # Loading failed, so we now load with the Python loader which has more + # readable exceptions + # Rewind the stream so we can try again + file_handle.seek(0, 0) + return _load_yaml_internal_with_type( + ESPHomePurePythonLoader, file_name, file_handle + ) + + +def _load_yaml_internal(fname: str) -> Any: + """Load a YAML file.""" + try: + with open(fname, encoding="utf-8") as f_handle: + return parse_yaml(fname, f_handle) + except (UnicodeDecodeError, OSError) as err: + raise EsphomeError(f"Error reading file {fname}: {err}") from err + + +def _load_yaml_internal_with_type( + loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader], + fname: str, + content: TextIOWrapper, +) -> Any: + """Load a YAML file.""" + loader = loader_type(content) loader.name = fname try: return loader.get_single_data() or OrderedDict() diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index b0dddfd15241..b67ea413233f 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,117 +1,48 @@ -import socket -import threading -import time -from typing import Optional +from __future__ import annotations + +import asyncio import logging from dataclasses import dataclass +from typing import Callable + +from zeroconf import IPVersion, ServiceInfo, ServiceStateChange, Zeroconf +from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf + +from esphome.storage_json import StorageJSON, ext_storage_path -from zeroconf import ( - DNSAddress, - DNSOutgoing, - DNSRecord, - DNSQuestion, - RecordUpdateListener, - Zeroconf, - ServiceBrowser, - ServiceStateChange, - current_time_millis, -) - -_CLASS_IN = 1 -_FLAGS_QR_QUERY = 0x0000 # query -_TYPE_A = 1 _LOGGER = logging.getLogger(__name__) -class HostResolver(RecordUpdateListener): - def __init__(self, name: str): - self.name = name - self.address: Optional[bytes] = None +_BACKGROUND_TASKS: set[asyncio.Task] = set() - def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: - if record is None: - return - if record.type == _TYPE_A: - assert isinstance(record, DNSAddress) - if record.name == self.name: - self.address = record.address - - def request(self, zc: Zeroconf, timeout: float) -> bool: - now = time.time() - delay = 0.2 - next_ = now + delay - last = now + timeout - - try: - zc.add_listener(self, None) - while self.address is None: - if last <= now: - # Timeout - return False - if next_ <= now: - out = DNSOutgoing(_FLAGS_QR_QUERY) - out.add_question(DNSQuestion(self.name, _TYPE_A, _CLASS_IN)) - zc.send(out) - next_ = now + delay - delay *= 2 - - time.sleep(min(next_, last) - now) - now = time.time() - finally: - zc.remove_listener(self) - - return True - - -class DashboardStatus(threading.Thread): - PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds - OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds - - def __init__(self, zc: Zeroconf, on_update) -> None: - threading.Thread.__init__(self) - self.zc = zc - self.query_hosts: set[str] = set() - self.key_to_host: dict[str, str] = {} - self.stop_event = threading.Event() - self.query_event = threading.Event() - self.on_update = on_update - def request_query(self, hosts: dict[str, str]) -> None: - self.query_hosts = set(hosts.values()) - self.key_to_host = hosts - self.query_event.set() +class HostResolver(ServiceInfo): + """Resolve a host name to an IP address.""" - def stop(self) -> None: - self.stop_event.set() - self.query_event.set() + @property + def _is_complete(self) -> bool: + """The ServiceInfo has all expected properties.""" + return bool(self._ipv4_addresses) - def host_status(self, key: str) -> bool: - entries = self.zc.cache.entries_with_name(key) - if not entries: - return False - now = current_time_millis() - return any( - (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries - ) +class DashboardStatus: + def __init__(self, on_update: Callable[[dict[str, bool | None], []]]) -> None: + """Initialize the dashboard status.""" + self.on_update = on_update - def run(self) -> None: - while not self.stop_event.is_set(): - self.on_update( - {key: self.host_status(host) for key, host in self.key_to_host.items()} - ) - now = current_time_millis() - for host in self.query_hosts: - entries = self.zc.cache.entries_with_name(host) - if not entries or all( - (entry.created + DashboardStatus.PING_AFTER) <= now - for entry in entries - ): - out = DNSOutgoing(_FLAGS_QR_QUERY) - out.add_question(DNSQuestion(host, _TYPE_A, _CLASS_IN)) - self.zc.send(out) - self.query_event.wait() - self.query_event.clear() + def browser_callback( + self, + zeroconf: Zeroconf, + service_type: str, + name: str, + state_change: ServiceStateChange, + ) -> None: + """Handle a service update.""" + short_name = name.partition(".")[0] + if state_change == ServiceStateChange.Removed: + self.on_update({short_name: False}) + elif state_change in (ServiceStateChange.Updated, ServiceStateChange.Added): + self.on_update({short_name: True}) ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local." @@ -120,11 +51,12 @@ def run(self) -> None: TXT_RECORD_PROJECT_VERSION = b"project_version" TXT_RECORD_NETWORK = b"network" TXT_RECORD_FRIENDLY_NAME = b"friendly_name" +TXT_RECORD_VERSION = b"version" @dataclass class DiscoveredImport: - friendly_name: Optional[str] + friendly_name: str | None device_name: str package_import_url: str project_name: str @@ -132,15 +64,15 @@ class DiscoveredImport: network: str +class DashboardBrowser(AsyncServiceBrowser): + """A class to browse for ESPHome nodes.""" + + class DashboardImportDiscovery: - def __init__(self, zc: Zeroconf) -> None: - self.zc = zc - self.service_browser = ServiceBrowser( - self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] - ) + def __init__(self) -> None: self.import_state: dict[str, DiscoveredImport] = {} - def _on_update( + def browser_callback( self, zeroconf: Zeroconf, service_type: str, @@ -153,8 +85,6 @@ def _on_update( name, state_change, ) - if service_type != ESPHOME_SERVICE_TYPE: - return if state_change == ServiceStateChange.Removed: self.import_state.pop(name, None) return @@ -163,7 +93,28 @@ def _on_update( # Ignore updates for devices that are not in the import state return - info = zeroconf.get_service_info(service_type, name) + info = AsyncServiceInfo( + service_type, + name, + ) + if info.load_from_cache(zeroconf): + self._process_service_info(name, info) + return + task = asyncio.create_task( + self._async_process_service_info(zeroconf, info, service_type, name) + ) + _BACKGROUND_TASKS.add(task) + task.add_done_callback(_BACKGROUND_TASKS.discard) + + async def _async_process_service_info( + self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str + ) -> None: + """Process a service info.""" + if await info.async_request(zeroconf, timeout=3000): + self._process_service_info(name, info) + + def _process_service_info(self, name: str, info: ServiceInfo) -> None: + """Process a service info.""" _LOGGER.debug("-> resolved info: %s", info) if info is None: return @@ -175,6 +126,10 @@ def _on_update( ] if any(key not in info.properties for key in required_keys): # Not a dashboard import device + version = info.properties.get(TXT_RECORD_VERSION) + if version is not None: + version = version.decode() + self.update_device_mdns(node_name, version) return import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() @@ -194,13 +149,51 @@ def _on_update( network=network, ) - def cancel(self) -> None: - self.service_browser.cancel() + def update_device_mdns(self, node_name: str, version: str): + storage_path = ext_storage_path(node_name + ".yaml") + storage_json = StorageJSON.load(storage_path) + + if storage_json is not None: + storage_version = storage_json.esphome_version + if version != storage_version: + storage_json.esphome_version = version + storage_json.save(storage_path) + _LOGGER.info( + "Updated %s with mdns version %s (was %s)", + node_name, + version, + storage_version, + ) + + +def _make_host_resolver(host: str) -> HostResolver: + """Create a new HostResolver for the given host name.""" + name = host.partition(".")[0] + info = HostResolver( + ESPHOME_SERVICE_TYPE, f"{name}.{ESPHOME_SERVICE_TYPE}", server=f"{name}.local." + ) + return info class EsphomeZeroconf(Zeroconf): - def resolve_host(self, host: str, timeout=3.0): - info = HostResolver(host) - if info.request(self, timeout): - return socket.inet_ntoa(info.address) + def resolve_host(self, host: str, timeout: float = 3.0) -> str | None: + """Resolve a host name to an IP address.""" + info = _make_host_resolver(host) + if ( + info.load_from_cache(self) + or (timeout and info.request(self, timeout * 1000)) + ) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)): + return str(addresses[0]) + return None + + +class AsyncEsphomeZeroconf(AsyncZeroconf): + async def async_resolve_host(self, host: str, timeout: float = 3.0) -> str | None: + """Resolve a host name to an IP address.""" + info = _make_host_resolver(host) + if ( + info.load_from_cache(self.zeroconf) + or (timeout and await info.async_request(self.zeroconf, timeout * 1000)) + ) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)): + return str(addresses[0]) return None diff --git a/platformio.ini b/platformio.ini index 50676e8813cf..d342b32b0280 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 - pavlodn/HaierProtocol@0.9.20 ; haier + pavlodn/HaierProtocol@0.9.25 ; haier ; 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 build_flags = @@ -57,7 +57,8 @@ lib_deps = ${common.lib_deps} SPI ; spi (Arduino built-in) Wire ; i2c (Arduino built-int) - esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base + heman/AsyncMqttClient-esphome@1.0.0 ; mqtt + esphome/ESPAsyncWebServer-esphome@3.2.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 @@ -79,21 +80,21 @@ build_flags = ; This are common settings for the ESP8266 using Arduino. [common:esp8266-arduino] extends = common:arduino -platform = platformio/espressif8266@3.2.0 +platform = platformio/espressif8266@4.2.1 platform_packages = - platformio/framework-arduinoespressif8266@~3.30002.0 + platformio/framework-arduinoespressif8266@~3.30102.0 framework = arduino lib_deps = ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) - ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt - esphome/ESPAsyncTCP-esphome@1.2.3 ; async_tcp + esphome/ESPAsyncTCP-esphome@2.0.0 ; async_tcp ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) - crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir + crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:arduino.build_flags} -Wno-nonnull-compare @@ -116,13 +117,14 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - esphome/AsyncTCP-esphome@1.2.2 ; async_tcp + esphome/AsyncTCP-esphome@2.1.3 ; async_tcp WiFiClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) esphome/ESP32-audioI2S@2.0.7 ; i2s_audio - crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir + crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:arduino.build_flags} -DUSE_ESP32 @@ -135,12 +137,13 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script extends = common:idf platform = platformio/espressif32@5.4.0 platform_packages = - platformio/framework-espidf@~3.40405.0 + platformio/framework-espidf@~3.40407.0 framework = espidf lib_deps = ${common:idf.lib_deps} espressif/esp32-camera@1.0.0 ; esp32_camera + droscy/esp_wireguard@0.4.0 ; wireguard build_flags = ${common:idf.build_flags} -Wno-nonnull-compare @@ -151,13 +154,12 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; These are common settings for the RP2040 using Arduino. [common:rp2040-arduino] extends = common:arduino -board_build.core = earlephilhower board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.3.0/rp2040-3.3.0.zip + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.7.2/rp2040-3.7.2.zip framework = arduino lib_deps = @@ -386,3 +388,4 @@ lib_deps = build_flags = ${common.build_flags} -DUSE_HOST + -std=c++17 diff --git a/requirements.txt b/requirements.txt index dccb418e8d42..698ae5644704 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,22 @@ -voluptuous==0.13.1 +async_timeout==4.0.3; python_version <= "3.10" +cryptography==42.0.2 +voluptuous==0.14.2 PyYAML==6.0.1 paho-mqtt==1.6.1 colorama==0.4.6 -tornado==6.3.2 -tzlocal==5.0.1 # from time +icmplib==3.0.4 +tornado==6.4 +tzlocal==5.2 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==6.1.10 # When updating platformio, also update Dockerfile -esptool==4.6.2 +platformio==6.1.15 # When updating platformio, also update Dockerfile +esptool==4.7.0 click==8.1.7 -esphome-dashboard==20230711.0 -aioesphomeapi==15.0.0 -zeroconf==0.80.0 +esphome-dashboard==20240412.0 +aioesphomeapi==24.3.0 +zeroconf==0.132.2 +python-magic==0.4.27 +ruamel.yaml==0.18.6 # dashboard_import # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 @@ -19,3 +24,6 @@ kconfiglib==13.7.1 # esp-idf >= 5.0 requires this pyparsing >= 3.0 + +# For autocompletion +argcomplete>=2.0.0 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 000000000000..eb749a861d29 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,4 @@ +# Useful stuff when working in a development environment +clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating +clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile +yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating diff --git a/requirements_optional.txt b/requirements_optional.txt index 8bbf0a680967..c984d41332b3 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,3 +1,2 @@ -pillow>4.0.0,<10.0.0 -cairosvg>=2.2.0 -cryptography>=2.0.0,<4 +pillow==10.2.0 +cairosvg==2.7.1 diff --git a/requirements_test.txt b/requirements_test.txt index 7ab6742b024e..ae833841ca26 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,15 +1,13 @@ -pylint==2.17.5 -flake8==6.0.0 # also change in .pre-commit-config.yaml when updating -black==23.7.0 # also change in .pre-commit-config.yaml when updating -pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating +pylint==3.1.0 +flake8==7.0.0 # also change in .pre-commit-config.yaml when updating +black==24.4.0 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.4.0 +pytest==8.2.0 pytest-cov==4.1.0 -pytest-mock==3.11.1 -pytest-asyncio==0.21.1 +pytest-mock==3.14.0 +pytest-asyncio==0.23.6 asyncmock==0.4.2 -hypothesis==5.49.0 - -clang-format==13.0.1 ; platform_machine != 'armv7l' +hypothesis==6.92.1 diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5a0c92350d77..a2bc3abf642e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -4,7 +4,7 @@ It's pretty crappy spaghetti code, but it works. you need to install protobuf-compiler: -running protc --version should return +running protoc --version should return libprotoc 3.6.1 then run this script with python3 and the files @@ -17,28 +17,22 @@ will be generated, they still need to be formatted """ -import re import os +import re +import sys +from abc import ABC, abstractmethod from pathlib import Path -from textwrap import dedent from subprocess import call +from textwrap import dedent # Generate with # protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto - import aioesphomeapi.api_options_pb2 as pb import google.protobuf.descriptor_pb2 as descriptor -file_header = "// This file was automatically generated with a tool.\n" -file_header += "// See scripts/api_protobuf/api_protobuf.py\n" - -cwd = Path(__file__).resolve().parent -root = cwd.parent.parent / "esphome" / "components" / "api" -prot = root / "api.protoc" -call(["protoc", "-o", str(prot), "-I", str(root), "api.proto"]) -content = prot.read_bytes() - -d = descriptor.FileDescriptorSet.FromString(content) +FILE_HEADER = """// This file was automatically generated with a tool. +// See scripts/api_protobuf/api_protobuf.py +""" def indent_list(text, padding=" "): @@ -64,7 +58,7 @@ def camel_to_snake(name): return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -class TypeInfo: +class TypeInfo(ABC): def __init__(self, field): self._field = field @@ -186,10 +180,12 @@ def encode_content(self): def dump_content(self): o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" - o += f'out.append("\\n");\n' + o += 'out.append("\\n");\n' return o - dump = None + @abstractmethod + def dump(self, name: str): + pass TYPE_INFO = {} @@ -212,7 +208,7 @@ class DoubleType(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -225,7 +221,7 @@ class FloatType(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -238,7 +234,7 @@ class Int64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -251,7 +247,7 @@ class UInt64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%llu", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -263,8 +259,8 @@ class Int32Type(TypeInfo): encode_func = "encode_int32" def dump(self, name): - o = f'sprintf(buffer, "%d", {name});\n' - o += f"out.append(buffer);" + o = f'sprintf(buffer, "%" PRId32, {name});\n' + o += "out.append(buffer);" return o @@ -277,7 +273,7 @@ class Fixed64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%llu", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -289,8 +285,8 @@ class Fixed32Type(TypeInfo): encode_func = "encode_fixed32" def dump(self, name): - o = f'sprintf(buffer, "%u", {name});\n' - o += f"out.append(buffer);" + o = f'sprintf(buffer, "%" PRIu32, {name});\n' + o += "out.append(buffer);" return o @@ -371,8 +367,8 @@ class UInt32Type(TypeInfo): encode_func = "encode_uint32" def dump(self, name): - o = f'sprintf(buffer, "%u", {name});\n' - o += f"out.append(buffer);" + o = f'sprintf(buffer, "%" PRIu32, {name});\n' + o += "out.append(buffer);" return o @@ -405,8 +401,8 @@ class SFixed32Type(TypeInfo): encode_func = "encode_sfixed32" def dump(self, name): - o = f'sprintf(buffer, "%d", {name});\n' - o += f"out.append(buffer);" + o = f'sprintf(buffer, "%" PRId32, {name});\n' + o += "out.append(buffer);" return o @@ -419,7 +415,7 @@ class SFixed64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -431,8 +427,8 @@ class SInt32Type(TypeInfo): encode_func = "encode_sint32" def dump(self, name): - o = f'sprintf(buffer, "%d", {name});\n' - o += f"out.append(buffer);" + o = f'sprintf(buffer, "%" PRId32, {name});\n' + o += "out.append(buffer);" return o @@ -445,7 +441,7 @@ class SInt64Type(TypeInfo): def dump(self, name): o = f'sprintf(buffer, "%lld", {name});\n' - o += f"out.append(buffer);" + o += "out.append(buffer);" return o @@ -527,7 +523,7 @@ def _ti_is_bool(self): def encode_content(self): o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" - o += f"}}" + o += "}" return o @property @@ -535,10 +531,13 @@ def dump_content(self): o = f'for (const auto {"" if self._ti_is_bool else "&"}it : this->{self.field_name}) {{\n' o += f' out.append(" {self.name}: ");\n' o += indent(self._ti.dump("it")) + "\n" - o += f' out.append("\\n");\n' - o += f"}}\n" + o += ' out.append("\\n");\n' + o += "}\n" return o + def dump(self, _: str): + pass + def build_enum_type(desc): name = desc.name @@ -547,17 +546,17 @@ def build_enum_type(desc): out += f" {v.name} = {v.number},\n" out += "};\n" - cpp = f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cpp = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cpp += f"template<> const char *proto_enum_to_string(enums::{name} value) {{\n" - cpp += f" switch (value) {{\n" + cpp += " switch (value) {\n" for v in desc.value: cpp += f" case enums::{v.name}:\n" cpp += f' return "{v.name}";\n' - cpp += f" default:\n" - cpp += f' return "UNKNOWN";\n' - cpp += f" }}\n" - cpp += f"}}\n" - cpp += f"#endif\n" + cpp += " default:\n" + cpp += ' return "UNKNOWN";\n' + cpp += " }\n" + cpp += "}\n" + cpp += "#endif\n" return out, cpp @@ -652,10 +651,10 @@ def build_message_type(desc): o += f" {dump[0]} " else: o += "\n" - o += f" __attribute__((unused)) char buffer[64];\n" + o += " __attribute__((unused)) char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' o += indent("\n".join(dump)) + "\n" - o += f' out.append("}}");\n' + o += ' out.append("}");\n' else: o2 = f'out.append("{desc.name} {{}}");' if len(o) + len(o2) + 3 < 120: @@ -664,9 +663,9 @@ def build_message_type(desc): o += "\n" o += f" {o2}\n" o += "}\n" - cpp += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cpp += o - cpp += f"#endif\n" + cpp += "#endif\n" prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" prot += "void dump_to(std::string &out) const override;\n" prot += "#endif\n" @@ -684,69 +683,12 @@ def build_message_type(desc): return out, cpp -file = d.file[0] -content = file_header -content += """\ -#pragma once - -#include "proto.h" - -namespace esphome { -namespace api { - -""" - -cpp = file_header -cpp += """\ -#include "api_pb2.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace api { - -""" - -content += "namespace enums {\n\n" - -for enum in file.enum_type: - s, c = build_enum_type(enum) - content += s - cpp += c - -content += "\n} // namespace enums\n\n" - -mt = file.message_type - -for m in mt: - s, c = build_message_type(m) - content += s - cpp += c - -content += """\ - -} // namespace api -} // namespace esphome -""" -cpp += """\ - -} // namespace api -} // namespace esphome -""" - -with open(root / "api_pb2.h", "w") as f: - f.write(content) - -with open(root / "api_pb2.cpp", "w") as f: - f.write(cpp) - SOURCE_BOTH = 0 SOURCE_SERVER = 1 SOURCE_CLIENT = 2 RECEIVE_CASES = {} -class_name = "APIServerConnectionBase" - ifdefs = {} @@ -766,7 +708,6 @@ def build_service_message_type(mt): ifdef = get_opt(mt, pb.ifdef) log = get_opt(mt, pb.log, True) - nodelay = get_opt(mt, pb.no_delay, False) hout = "" cout = "" @@ -779,14 +720,14 @@ def build_service_message_type(mt): # Generate send func = f"send_{snake}" hout += f"bool {func}(const {mt.name} &msg);\n" - cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" + cout += f"bool APIServerConnectionBase::{func}(const {mt.name} &msg) {{\n" if log: - cout += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + cout += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - cout += f"#endif\n" + cout += "#endif\n" # cout += f' this->set_nodelay({str(nodelay).lower()});\n' cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" - cout += f"}}\n" + cout += "}\n" if source in (SOURCE_BOTH, SOURCE_CLIENT): # Generate receive func = f"on_{snake}" @@ -795,169 +736,242 @@ def build_service_message_type(mt): if ifdef is not None: case += f"#ifdef {ifdef}\n" case += f"{mt.name} msg;\n" - case += f"msg.decode(msg_data, msg_size);\n" + case += "msg.decode(msg_data, msg_size);\n" if log: - case += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" + case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - case += f"#endif\n" + case += "#endif\n" case += f"this->{func}(msg);\n" if ifdef is not None: - case += f"#endif\n" + case += "#endif\n" case += "break;" RECEIVE_CASES[id_] = case if ifdef is not None: - hout += f"#endif\n" - cout += f"#endif\n" + hout += "#endif\n" + cout += "#endif\n" return hout, cout -hpp = file_header -hpp += """\ -#pragma once +def main(): + cwd = Path(__file__).resolve().parent + root = cwd.parent.parent / "esphome" / "components" / "api" + prot_file = root / "api.protoc" + call(["protoc", "-o", str(prot_file), "-I", str(root), "api.proto"]) + proto_content = prot_file.read_bytes() -#include "api_pb2.h" -#include "esphome/core/defines.h" + # pylint: disable-next=no-member + d = descriptor.FileDescriptorSet.FromString(proto_content) -namespace esphome { -namespace api { + file = d.file[0] + content = FILE_HEADER + content += """\ + #pragma once -""" + #include "proto.h" -cpp = file_header -cpp += """\ -#include "api_pb2_service.h" -#include "esphome/core/log.h" + namespace esphome { + namespace api { -namespace esphome { -namespace api { + """ -static const char *const TAG = "api.service"; + cpp = FILE_HEADER + cpp += """\ + #include "api_pb2.h" + #include "esphome/core/log.h" -""" + #include -hpp += f"class {class_name} : public ProtoService {{\n" -hpp += " public:\n" - -for mt in file.message_type: - obj = build_service_message_type(mt) - if obj is None: - continue - hout, cout = obj - hpp += indent(hout) + "\n" - cpp += cout - -cases = list(RECEIVE_CASES.items()) -cases.sort() -hpp += " protected:\n" -hpp += f" bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" -out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" -out += f" switch (msg_type) {{\n" -for i, case in cases: - c = f"case {i}: {{\n" - c += indent(case) + "\n" - c += f"}}" - out += indent(c, " ") + "\n" -out += " default:\n" -out += " return false;\n" -out += " }\n" -out += " return true;\n" -out += "}\n" -cpp += out -hpp += "};\n" - -serv = file.service[0] -class_name = "APIServerConnection" -hpp += "\n" -hpp += f"class {class_name} : public {class_name}Base {{\n" -hpp += " public:\n" -hpp_protected = "" -cpp += "\n" - -m = serv.method[0] -for m in serv.method: - func = m.name - inp = m.input_type[1:] - ret = m.output_type[1:] - is_void = ret == "void" - snake = camel_to_snake(inp) - on_func = f"on_{snake}" - needs_conn = get_opt(m, pb.needs_setup_connection, True) - needs_auth = get_opt(m, pb.needs_authentication, True) - - ifdef = ifdefs.get(inp, None) + namespace esphome { + namespace api { - if ifdef is not None: - hpp += f"#ifdef {ifdef}\n" - hpp_protected += f"#ifdef {ifdef}\n" - cpp += f"#ifdef {ifdef}\n" - - hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" - hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" - cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" - body = "" - if needs_conn: - body += "if (!this->is_connection_setup()) {\n" - body += " this->on_no_setup_connection();\n" - body += " return;\n" - body += "}\n" - if needs_auth: - body += "if (!this->is_authenticated()) {\n" - body += " this->on_unauthenticated_access();\n" - body += " return;\n" - body += "}\n" - - if is_void: - body += f"this->{func}(msg);\n" - else: - body += f"{ret} ret = this->{func}(msg);\n" - ret_snake = camel_to_snake(ret) - body += f"if (!this->send_{ret_snake}(ret)) {{\n" - body += f" this->on_fatal_error();\n" - body += "}\n" - cpp += indent(body) + "\n" + "}\n" + """ - if ifdef is not None: - hpp += f"#endif\n" - hpp_protected += f"#endif\n" - cpp += f"#endif\n" + content += "namespace enums {\n\n" -hpp += " protected:\n" -hpp += hpp_protected -hpp += "};\n" + for enum in file.enum_type: + s, c = build_enum_type(enum) + content += s + cpp += c -hpp += """\ + content += "\n} // namespace enums\n\n" -} // namespace api -} // namespace esphome -""" -cpp += """\ + mt = file.message_type -} // namespace api -} // namespace esphome -""" + for m in mt: + s, c = build_message_type(m) + content += s + cpp += c -with open(root / "api_pb2_service.h", "w") as f: - f.write(hpp) + content += """\ -with open(root / "api_pb2_service.cpp", "w") as f: - f.write(cpp) + } // namespace api + } // namespace esphome + """ + cpp += """\ -prot.unlink() + } // namespace api + } // namespace esphome + """ -try: - import clang_format + with open(root / "api_pb2.h", "w", encoding="utf-8") as f: + f.write(content) - def exec_clang_format(path): - clang_format_path = os.path.join( - os.path.dirname(clang_format.__file__), "data", "bin", "clang-format" - ) - call([clang_format_path, "-i", path]) - - exec_clang_format(root / "api_pb2_service.h") - exec_clang_format(root / "api_pb2_service.cpp") - exec_clang_format(root / "api_pb2.h") - exec_clang_format(root / "api_pb2.cpp") -except ImportError: - pass + with open(root / "api_pb2.cpp", "w", encoding="utf-8") as f: + f.write(cpp) + + hpp = FILE_HEADER + hpp += """\ + #pragma once + + #include "api_pb2.h" + #include "esphome/core/defines.h" + + namespace esphome { + namespace api { + + """ + + cpp = FILE_HEADER + cpp += """\ + #include "api_pb2_service.h" + #include "esphome/core/log.h" + + namespace esphome { + namespace api { + + static const char *const TAG = "api.service"; + + """ + + class_name = "APIServerConnectionBase" + + hpp += f"class {class_name} : public ProtoService {{\n" + hpp += " public:\n" + + for mt in file.message_type: + obj = build_service_message_type(mt) + if obj is None: + continue + hout, cout = obj + hpp += indent(hout) + "\n" + cpp += cout + + cases = list(RECEIVE_CASES.items()) + cases.sort() + hpp += " protected:\n" + hpp += " bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" + out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" + out += " switch (msg_type) {\n" + for i, case in cases: + c = f"case {i}: {{\n" + c += indent(case) + "\n" + c += "}" + out += indent(c, " ") + "\n" + out += " default:\n" + out += " return false;\n" + out += " }\n" + out += " return true;\n" + out += "}\n" + cpp += out + hpp += "};\n" + + serv = file.service[0] + class_name = "APIServerConnection" + hpp += "\n" + hpp += f"class {class_name} : public {class_name}Base {{\n" + hpp += " public:\n" + hpp_protected = "" + cpp += "\n" + + m = serv.method[0] + for m in serv.method: + func = m.name + inp = m.input_type[1:] + ret = m.output_type[1:] + is_void = ret == "void" + snake = camel_to_snake(inp) + on_func = f"on_{snake}" + needs_conn = get_opt(m, pb.needs_setup_connection, True) + needs_auth = get_opt(m, pb.needs_authentication, True) + + ifdef = ifdefs.get(inp, None) + + if ifdef is not None: + hpp += f"#ifdef {ifdef}\n" + hpp_protected += f"#ifdef {ifdef}\n" + cpp += f"#ifdef {ifdef}\n" + + hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" + hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" + cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" + body = "" + if needs_conn: + body += "if (!this->is_connection_setup()) {\n" + body += " this->on_no_setup_connection();\n" + body += " return;\n" + body += "}\n" + if needs_auth: + body += "if (!this->is_authenticated()) {\n" + body += " this->on_unauthenticated_access();\n" + body += " return;\n" + body += "}\n" + + if is_void: + body += f"this->{func}(msg);\n" + else: + body += f"{ret} ret = this->{func}(msg);\n" + ret_snake = camel_to_snake(ret) + body += f"if (!this->send_{ret_snake}(ret)) {{\n" + body += " this->on_fatal_error();\n" + body += "}\n" + cpp += indent(body) + "\n" + "}\n" + + if ifdef is not None: + hpp += "#endif\n" + hpp_protected += "#endif\n" + cpp += "#endif\n" + + hpp += " protected:\n" + hpp += hpp_protected + hpp += "};\n" + + hpp += """\ + + } // namespace api + } // namespace esphome + """ + cpp += """\ + + } // namespace api + } // namespace esphome + """ + + with open(root / "api_pb2_service.h", "w", encoding="utf-8") as f: + f.write(hpp) + + with open(root / "api_pb2_service.cpp", "w", encoding="utf-8") as f: + f.write(cpp) + + prot_file.unlink() + + try: + import clang_format + + def exec_clang_format(path): + clang_format_path = os.path.join( + os.path.dirname(clang_format.__file__), "data", "bin", "clang-format" + ) + call([clang_format_path, "-i", path]) + + exec_clang_format(root / "api_pb2_service.h") + exec_clang_format(root / "api_pb2_service.cpp") + exec_clang_format(root / "api_pb2.h") + exec_clang_format(root / "api_pb2.cpp") + except ImportError: + pass + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/script/build_language_schema.py b/script/build_language_schema.py index c6fcf5eb64d0..cb3dc1832de9 100644 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -61,6 +61,7 @@ def get_component_names(): + # pylint: disable-next=redefined-outer-name,reimported from esphome.loader import CORE_COMPONENTS_PATH component_names = ["esphome", "sensor", "esp32", "esp8266"] @@ -82,6 +83,13 @@ def load_components(): components[domain] = get_component(domain) +# pylint: disable=wrong-import-position +from esphome.const import CONF_TYPE, KEY_CORE +from esphome.core import CORE + +# pylint: enable=wrong-import-position + +CORE.data[KEY_CORE] = {} load_components() # Import esphome after loading components (so schema is tracked) @@ -91,7 +99,6 @@ def load_components(): from esphome import automation from esphome import pins from esphome.components import remote_base -from esphome.const import CONF_TYPE from esphome.loader import get_platform, CORE_COMPONENTS_PATH from esphome.helpers import write_file_if_changed from esphome.util import Registry @@ -111,7 +118,7 @@ def write_file(name, obj): def delete_extra_files(keep_names): for d in os.listdir(args.output_path): - if d.endswith(".json") and not d[:-5] in keep_names: + if d.endswith(".json") and d[:-5] not in keep_names: os.remove(os.path.join(args.output_path, d)) print(f"Deleted {d}") @@ -549,11 +556,11 @@ def shrink(): s = f"{domain}.{schema_name}" if ( not s.endswith("." + S_CONFIG_SCHEMA) - and s not in referenced_schemas.keys() + and s not in referenced_schemas and not is_platform_schema(s) ): print(f"Removing {s}") - output[domain][S_SCHEMAS].pop(schema_name) + domain_schemas[S_SCHEMAS].pop(schema_name) def build_schema(): @@ -561,7 +568,7 @@ def build_schema(): # check esphome was not loaded globally (IDE auto imports) if len(ejs.extended_schemas) == 0: - raise Exception( + raise LookupError( "no data collected. Did you globally import an ESPHome component?" ) @@ -700,7 +707,7 @@ def convert(schema, config_var, path): if schema_instance is schema: assert S_CONFIG_VARS not in config_var assert S_EXTENDS not in config_var - if not S_TYPE in config_var: + if S_TYPE not in config_var: config_var[S_TYPE] = S_SCHEMA # assert config_var[S_TYPE] == S_SCHEMA @@ -762,9 +769,9 @@ def convert(schema, config_var, path): elif schema == automation.validate_potentially_and_condition: config_var[S_TYPE] = "registry" config_var["registry"] = "condition" - elif schema == cv.int_ or schema == cv.int_range: + elif schema in (cv.int_, cv.int_range): config_var[S_TYPE] = "integer" - elif schema == cv.string or schema == cv.string_strict or schema == cv.valid_name: + elif schema in (cv.string, cv.string_strict, cv.valid_name): config_var[S_TYPE] = "string" elif isinstance(schema, vol.Schema): @@ -776,6 +783,7 @@ def convert(schema, config_var, path): config_var |= pin_validators[repr_schema] config_var[S_TYPE] = "pin" + # pylint: disable-next=too-many-nested-blocks elif repr_schema in ejs.hidden_schemas: schema_type = ejs.hidden_schemas[repr_schema] @@ -841,9 +849,11 @@ def convert(schema, config_var, path): config_var["id_type"] = { "class": str(data.base), - "parents": [str(x.base) for x in parents] - if isinstance(parents, list) - else None, + "parents": ( + [str(x.base) for x in parents] + if isinstance(parents, list) + else None + ), } elif schema_type == "use_id": if inspect.ismodule(data): @@ -866,10 +876,13 @@ def convert(schema, config_var, path): config_var["use_id_type"] = str(data.base) config_var[S_TYPE] = "use_id" else: - raise Exception("Unknown extracted schema type") + raise TypeError("Unknown extracted schema type") elif config_var.get("key") == "GeneratedID": - if path == "i2c/CONFIG_SCHEMA/extL/all/id": - config_var["id_type"] = {"class": "i2c::I2CBus", "parents": ["Component"]} + if path.startswith("i2c/CONFIG_SCHEMA/") and path.endswith("/id"): + config_var["id_type"] = { + "class": "i2c::I2CBus", + "parents": ["Component"], + } elif path == "uart/CONFIG_SCHEMA/val 1/extL/all/id": config_var["id_type"] = { "class": "uart::UARTComponent", @@ -878,7 +891,7 @@ def convert(schema, config_var, path): elif path == "pins/esp32/val 1/id": config_var["id_type"] = "pin" else: - raise Exception("Cannot determine id_type for " + path) + raise TypeError("Cannot determine id_type for " + path) elif repr_schema in ejs.registry_schemas: solve_registry.append((ejs.registry_schemas[repr_schema], config_var)) @@ -942,11 +955,7 @@ def convert_keys(converted, schema, path): result["key"] = "GeneratedID" elif isinstance(k, cv.Required): result["key"] = "Required" - elif ( - isinstance(k, cv.Optional) - or isinstance(k, cv.Inclusive) - or isinstance(k, cv.Exclusive) - ): + elif isinstance(k, (cv.Optional, cv.Inclusive, cv.Exclusive)): result["key"] = "Optional" else: converted["key"] = "String" diff --git a/script/bump-version.py b/script/bump-version.py index 1f034344f9ff..a55bb65cd68a 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -2,7 +2,6 @@ import argparse import re -import subprocess from dataclasses import dataclass import sys @@ -40,12 +39,12 @@ def parse(cls, value): def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: + with open(path, encoding="utf-8") as fh: content = fh.read() content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) if expected_count is not None: assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "wt") as fh: + with open(path, "w", encoding="utf-8") as fh: fh.write(content) diff --git a/script/ci-custom.py b/script/ci-custom.py index da4da50d7e15..704962fa9714 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 -from helpers import styled, print_error_for_file, git_ls_files, filter_changed import argparse import codecs import collections -import colorama import fnmatch import functools import os.path @@ -12,6 +10,9 @@ import sys import time +import colorama +from helpers import filter_changed, git_ls_files, print_error_for_file, styled + sys.path.append(os.path.dirname(__file__)) @@ -30,31 +31,6 @@ def find_all(a_str, sub): column += len(sub) -colorama.init() - -parser = argparse.ArgumentParser() -parser.add_argument( - "files", nargs="*", default=[], help="files to be processed (regex on path)" -) -parser.add_argument( - "-c", "--changed", action="store_true", help="Only run on changed files" -) -parser.add_argument( - "--print-slowest", action="store_true", help="Print the slowest checks" -) -args = parser.parse_args() - -EXECUTABLE_BIT = git_ls_files() -files = list(EXECUTABLE_BIT.keys()) -# Match against re -file_name_re = re.compile("|".join(args.files)) -files = [p for p in files if file_name_re.search(p)] - -if args.changed: - files = filter_changed(files) - -files.sort() - file_types = ( ".h", ".c", @@ -81,11 +57,36 @@ def find_all(a_str, sub): "", ) cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc") -ignore_types = (".ico", ".png", ".woff", ".woff2", "") +py_include = ("*.py",) +ignore_types = (".ico", ".png", ".woff", ".woff2", "", ".ttf", ".otf") LINT_FILE_CHECKS = [] LINT_CONTENT_CHECKS = [] LINT_POST_CHECKS = [] +EXECUTABLE_BIT = {} + +errors = collections.defaultdict(list) + + +def add_errors(fname, errs): + if not isinstance(errs, list): + errs = [errs] + for err in errs: + if err is None: + continue + try: + lineno, col, msg = err + except ValueError: + lineno = 1 + col = 1 + msg = err + if not isinstance(msg, str): + raise ValueError("Error is not instance of string!") + if not isinstance(lineno, int): + raise ValueError("Line number is not an int!") + if not isinstance(col, int): + raise ValueError("Column number is not an int!") + errors[fname].append((lineno, col, msg)) def run_check(lint_obj, fname, *args): @@ -155,7 +156,7 @@ def lint_re_check(regex, **kwargs): def decorator(func): @functools.wraps(func) def new_func(fname, content): - errors = [] + errs = [] for match in prog.finditer(content): if "NOLINT" in match.group(0): continue @@ -165,8 +166,8 @@ def new_func(fname, content): err = func(fname, match) if err is None: continue - errors.append((lineno, col + 1, err)) - return errors + errs.append((lineno, col + 1, err)) + return errs return decor(new_func) @@ -182,13 +183,13 @@ def new_func(fname, content): find_ = find if callable(find): find_ = find(fname, content) - errors = [] + errs = [] for line, col in find_all(content, find_): err = func(fname) - errors.append((line + 1, col + 1, err)) + errs.append((line + 1, col + 1, err)) if only_first: break - return errors + return errs return decor(new_func) @@ -235,8 +236,8 @@ def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] if ex != 100644: return ( - "File has invalid executable bit {}. If running from a windows machine please " - "see disabling executable bit in git.".format(ex) + f"File has invalid executable bit {ex}. If running from a windows machine please " + "see disabling executable bit in git." ) return None @@ -265,7 +266,8 @@ def lint_end_newline(fname, content): return None -CPP_RE_EOL = r"\s*?(?://.*?)?$" +CPP_RE_EOL = r".*?(?://.*?)?$" +PY_RE_EOL = r".*?(?:#.*?)?$" def highlight(s): @@ -273,7 +275,7 @@ def highlight(s): @lint_re_check( - r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, + r"^#define\s+([a-zA-Z0-9_]+)\s+(0b[10]+|0x[0-9a-fA-F]+|\d+)\s*?(?:\/\/.*?)?$", include=cpp_include, exclude=[ "esphome/core/log.h", @@ -285,8 +287,8 @@ def lint_no_defines(fname, match): s = highlight(f"static const uint8_t {match.group(1)} = {match.group(2)};") return ( "#define macros for integer constants are not allowed, please use " - "{} style instead (replace uint8_t with the appropriate " - "datatype). See also Google style guide.".format(s) + f"{s} style instead (replace uint8_t with the appropriate " + "datatype). See also Google style guide." ) @@ -296,11 +298,11 @@ def lint_no_long_delays(fname, match): if duration_ms < 50: return None return ( - "{} - long calls to delay() are not allowed in ESPHome because everything executes " - "in one thread. Calling delay() will block the main thread and slow down ESPHome.\n" + f"{highlight(match.group(0).strip())} - long calls to delay() are not allowed " + "in ESPHome because everything executes in one thread. Calling delay() will " + "block the main thread and slow down ESPHome.\n" "If there's no way to work around the delay() and it doesn't execute often, please add " "a '// NOLINT' comment to the line." - "".format(highlight(match.group(0).strip())) ) @@ -311,28 +313,28 @@ def lint_const_ordered(fname, content): Reason: Otherwise people add it to the end, and then that results in merge conflicts. """ lines = content.splitlines() - errors = [] + errs = [] for start in ["CONF_", "ICON_", "UNIT_"]: matching = [ (i + 1, line) for i, line in enumerate(lines) if line.startswith(start) ] ordered = list(sorted(matching, key=lambda x: x[1].replace("_", " "))) ordered = [(mi, ol) for (mi, _), (_, ol) in zip(matching, ordered)] - for (mi, ml), (oi, ol) in zip(matching, ordered): - if ml == ol: + for (mi, mline), (_, ol) in zip(matching, ordered): + if mline == ol: continue - target = next(i for i, l in ordered if l == ml) - target_text = next(l for i, l in matching if target == i) - errors.append( + target = next(i for i, line in ordered if line == mline) + target_text = next(line for i, line in matching if target == i) + errs.append( ( mi, 1, - f"Constant {highlight(ml)} is not ordered, please make sure all " + f"Constant {highlight(mline)} is not ordered, please make sure all " f"constants are ordered. See line {mi} (should go to line {target}, " f"{target_text})", ) ) - return errors + return errs @lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=["*.py"]) @@ -344,15 +346,14 @@ def lint_conf_matches(fname, match): if const_norm == value_norm: return None return ( - "Constant {} does not match value {}! Please make sure the constant's name matches its " - "value!" - "".format(highlight("CONF_" + const), highlight(value)) + f"Constant {highlight('CONF_' + const)} does not match value {highlight(value)}! " + "Please make sure the constant's name matches its value!" ) CONF_RE = r'^(CONF_[a-zA-Z0-9_]+)\s*=\s*[\'"].*?[\'"]\s*?$' -with codecs.open("esphome/const.py", "r", encoding="utf-8") as f_handle: - constants_content = f_handle.read() +with codecs.open("esphome/const.py", "r", encoding="utf-8") as const_f_handle: + constants_content = const_f_handle.read() CONSTANTS = [m.group(1) for m in re.finditer(CONF_RE, constants_content, re.MULTILINE)] CONSTANTS_USES = collections.defaultdict(list) @@ -365,8 +366,8 @@ def lint_conf_from_const_py(fname, match): CONSTANTS_USES[name].append(fname) return None return ( - "Constant {} has already been defined in const.py - please import the constant from " - "const.py directly.".format(highlight(name)) + f"Constant {highlight(name)} has already been defined in const.py - " + "please import the constant from const.py directly." ) @@ -458,7 +459,7 @@ def lint_no_removed_in_idf_conversions(fname, match): @lint_re_check( - r"[^\w\d]byte\s+[\w\d]+\s*=", + r"[^\w\d]byte +[\w\d]+\s*=", include=cpp_include, exclude={ "esphome/components/tuya/tuya.h", @@ -473,16 +474,15 @@ def lint_no_byte_datatype(fname, match): @lint_post_check def lint_constants_usage(): - errors = [] + errs = [] for constant, uses in CONSTANTS_USES.items(): - if len(uses) < 4: + if len(uses) < 3: continue - errors.append( - "Constant {} is defined in {} files. Please move all definitions of the " - "constant to const.py (Uses: {})" - "".format(highlight(constant), len(uses), ", ".join(uses)) + errs.append( + f"Constant {highlight(constant)} is defined in {len(uses)} files. Please move all definitions of the " + f"constant to const.py (Uses: {', '.join(uses)})" ) - return errors + return errs def relative_cpp_search_text(fname, content): @@ -553,7 +553,7 @@ def lint_namespace(fname, content): return ( "Invalid namespace found in C++ file. All integration C++ files should put all " "functions in a separate namespace that matches the integration's name. " - "Please make sure the file contains {}".format(highlight(search)) + f"Please make sure the file contains {highlight(search)}" ) @@ -576,11 +576,6 @@ def lint_pragma_once(fname, content): return None -@lint_re_check( - r"(whitelist|blacklist|slave)", - exclude=["script/ci-custom.py"], - flags=re.IGNORECASE | re.MULTILINE, -) def lint_inclusive_language(fname, match): # From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb return ( @@ -598,6 +593,21 @@ def lint_inclusive_language(fname, match): ) +lint_re_check( + r"(whitelist|blacklist|slave)" + PY_RE_EOL, + include=py_include, + exclude=["script/ci-custom.py"], + flags=re.IGNORECASE | re.MULTILINE, +)(lint_inclusive_language) + + +lint_re_check( + r"(whitelist|blacklist|slave)" + CPP_RE_EOL, + include=cpp_include, + flags=re.IGNORECASE | re.MULTILINE, +)(lint_inclusive_language) + + @lint_re_check(r"[\t\r\f\v ]+$") def lint_trailing_whitespace(fname, match): return "Trailing whitespace detected" @@ -611,12 +621,17 @@ def lint_trailing_whitespace(fname, match): "esphome/components/button/button.h", "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", + "esphome/components/datetime/date_entity.h", + "esphome/components/datetime/time_entity.h", + "esphome/components/datetime/datetime_entity.h", "esphome/components/display/display.h", + "esphome/components/event/event.h", "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", "esphome/components/lock/lock.h", "esphome/components/mqtt/mqtt_component.h", "esphome/components/number/number.h", + "esphome/components/text/text.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", "esphome/components/nextion/nextion_base.h", @@ -625,6 +640,7 @@ def lint_trailing_whitespace(fname, match): "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", + "esphome/components/valve/valve.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", @@ -638,66 +654,73 @@ def lint_log_in_header(fname): ) -errors = collections.defaultdict(list) +def main(): + colorama.init() + parser = argparse.ArgumentParser() + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" + ) + parser.add_argument( + "--print-slowest", action="store_true", help="Print the slowest checks" + ) + args = parser.parse_args() -def add_errors(fname, errs): - if not isinstance(errs, list): - errs = [errs] - for err in errs: - if err is None: + global EXECUTABLE_BIT + EXECUTABLE_BIT = git_ls_files() + files = list(EXECUTABLE_BIT.keys()) + # Match against re + file_name_re = re.compile("|".join(args.files)) + files = [p for p in files if file_name_re.search(p)] + + if args.changed: + files = filter_changed(files) + + files.sort() + + for fname in files: + _, ext = os.path.splitext(fname) + run_checks(LINT_FILE_CHECKS, fname, fname) + if ext in ignore_types: continue try: - lineno, col, msg = err - except ValueError: - lineno = 1 - col = 1 - msg = err - if not isinstance(msg, str): - raise ValueError("Error is not instance of string!") - if not isinstance(lineno, int): - raise ValueError("Line number is not an int!") - if not isinstance(col, int): - raise ValueError("Column number is not an int!") - errors[fname].append((lineno, col, msg)) + with codecs.open(fname, "r", encoding="utf-8") as f_handle: + content = f_handle.read() + except UnicodeDecodeError: + add_errors( + fname, + "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + ) + continue + run_checks(LINT_CONTENT_CHECKS, fname, fname, content) + run_checks(LINT_POST_CHECKS, "POST") -for fname in files: - _, ext = os.path.splitext(fname) - run_checks(LINT_FILE_CHECKS, fname, fname) - if ext in ignore_types: - continue - try: - with codecs.open(fname, "r", encoding="utf-8") as f_handle: - content = f_handle.read() - except UnicodeDecodeError: - add_errors( - fname, - "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + for f, errs in sorted(errors.items()): + bold = functools.partial(styled, colorama.Style.BRIGHT) + bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) + err_str = ( + f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" + for lineno, col, msg in errs ) - continue - run_checks(LINT_CONTENT_CHECKS, fname, fname, content) + print_error_for_file(f, "\n".join(err_str)) -run_checks(LINT_POST_CHECKS, "POST") + if args.print_slowest: + lint_times = [] + for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS: + durations = lint.get("durations", []) + lint_times.append((sum(durations), len(durations), lint["func"].__name__)) + lint_times.sort(key=lambda x: -x[0]) + for i in range(min(len(lint_times), 10)): + dur, invocations, name = lint_times[i] + print(f" - '{name}' took {dur:.2f}s total (ran on {invocations} files)") + print(f"Total time measured: {sum(x[0] for x in lint_times):.2f}s") -for f, errs in sorted(errors.items()): - bold = functools.partial(styled, colorama.Style.BRIGHT) - bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) - err_str = ( - f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" - for lineno, col, msg in errs - ) - print_error_for_file(f, "\n".join(err_str)) - -if args.print_slowest: - lint_times = [] - for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS: - durations = lint.get("durations", []) - lint_times.append((sum(durations), len(durations), lint["func"].__name__)) - lint_times.sort(key=lambda x: -x[0]) - for i in range(min(len(lint_times), 10)): - dur, invocations, name = lint_times[i] - print(f" - '{name}' took {dur:.2f}s total (ran on {invocations} files)") - print(f"Total time measured: {sum(x[0] for x in lint_times):.2f}s") - -sys.exit(len(errors)) + return len(errors) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/script/clang-format b/script/clang-format index 165fbd269f0e..b065d807958a 100755 --- a/script/clang-format +++ b/script/clang-format @@ -1,6 +1,12 @@ #!/usr/bin/env python3 -from helpers import print_error_for_file, get_output, git_ls_files, filter_changed +from helpers import ( + print_error_for_file, + get_output, + git_ls_files, + filter_changed, + get_binary, +) import argparse import click import colorama @@ -13,11 +19,12 @@ import sys import threading -def run_format(args, queue, lock, failed_files): + +def run_format(executable, args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ["clang-format-13"] + invocation = [executable] if args.inplace: invocation.append("-i") else: @@ -58,22 +65,6 @@ def main(): ) args = parser.parse_args() - try: - get_output("clang-format-13", "-version") - except: - print( - """ - Oops. It looks like clang-format is not installed. - - Please check you can run "clang-format-13 -version" in your terminal and install - clang-format (v13) if necessary. - - Note you can also upload your code as a pull request on GitHub and see the CI check - output to apply clang-format. - """ - ) - return 1 - files = [] for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) @@ -90,11 +81,12 @@ def main(): failed_files = [] try: + executable = get_binary("clang-format", 13) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread( - target=run_format, args=(args, task_queue, lock, failed_files) + target=run_format, args=(executable, args, task_queue, lock, failed_files) ) t.daemon = True t.start() @@ -109,13 +101,18 @@ def main(): # Wait for all threads to be done. task_queue.join() + except FileNotFoundError as ex: + return 1 except KeyboardInterrupt: print() print("Ctrl-C detected, goodbye.") + # Kill subprocesses (and ourselves!) + # No simple, clean alternative appears to be available. os.kill(0, 9) + return 2 # Will not execute. - sys.exit(len(failed_files)) + return len(failed_files) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/script/clang-tidy b/script/clang-tidy index b025221fa8ff..84b02306d57b 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -11,6 +11,7 @@ from helpers import ( load_idedata, root_path, basepath, + get_binary, ) import argparse import click @@ -26,6 +27,7 @@ import tempfile import threading + def clang_options(idedata): cmd = [] @@ -110,10 +112,12 @@ def clang_options(idedata): return cmd -def run_tidy(args, options, tmpdir, queue, lock, failed_files): +pids = set() + +def run_tidy(executable, args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ["clang-tidy-14"] + invocation = [executable] if tmpdir is not None: invocation.append("--export-fixes") @@ -193,22 +197,6 @@ def main(): ) args = parser.parse_args() - try: - get_output("clang-tidy-14", "-version") - except: - print( - """ - Oops. It looks like clang-tidy-14 is not installed. - - Please check you can run "clang-tidy-14 -version" in your terminal and install - clang-tidy (v14) if necessary. - - Note you can also upload your code as a pull request on GitHub and see the CI check - output to apply clang-tidy. - """ - ) - return 1 - idedata = load_idedata(args.environment) options = clang_options(idedata) @@ -242,12 +230,13 @@ def main(): failed_files = [] try: + executable = get_binary("clang-tidy", 14) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread( target=run_tidy, - args=(args, options, tmpdir, task_queue, lock, failed_files), + args=(executable, args, options, tmpdir, task_queue, lock, failed_files), ) t.daemon = True t.start() @@ -262,23 +251,33 @@ def main(): # Wait for all threads to be done. task_queue.join() + except FileNotFoundError as ex: + return 1 except KeyboardInterrupt: print() print("Ctrl-C detected, goodbye.") if tmpdir: shutil.rmtree(tmpdir) + # Kill subprocesses (and ourselves!) + # No simple, clean alternative appears to be available. os.kill(0, 9) + return 2 # Will not execute. if args.fix and failed_files: print("Applying fixes ...") try: - subprocess.call(["clang-apply-replacements-14", tmpdir]) + try: + subprocess.call(["clang-apply-replacements-14", tmpdir]) + except FileNotFoundError: + subprocess.call(["clang-apply-replacements", tmpdir]) + except FileNotFoundError: + print("Error please install clang-apply-replacements-14 or clang-apply-replacements.\n", file=sys.stderr) except: print("Error applying fixes.\n", file=sys.stderr) raise - sys.exit(len(failed_files)) + return len(failed_files) if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create index 120ab3307d5a..272d350519bf 100755 --- a/script/devcontainer-post-create +++ b/script/devcontainer-post-create @@ -3,6 +3,9 @@ set -e # set -x +apt update +apt-get install avahi-utils -y + mkdir -p config script/setup diff --git a/script/fulltest b/script/fulltest index a605beebfeba..6440401e9706 100755 --- a/script/fulltest +++ b/script/fulltest @@ -12,3 +12,4 @@ script/lint-cpp script/unit_test script/component_test script/test +script/test_build_components diff --git a/script/helpers.py b/script/helpers.py index c042362aeb75..52b0658fb67f 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1,10 +1,11 @@ -import colorama +import json import os.path import re import subprocess -import json from pathlib import Path +import colorama + root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) basepath = os.path.join(root_path, "esphome") temp_folder = os.path.join(root_path, ".temp") @@ -44,7 +45,7 @@ def build_all_include(): content = "\n".join(headers) p = Path(temp_header_file) p.parent.mkdir(exist_ok=True) - p.write_text(content) + p.write_text(content, encoding="utf-8") def walk_files(path): @@ -54,14 +55,14 @@ def walk_files(path): def get_output(*args): - proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + output, _ = proc.communicate() return output.decode("utf-8") def get_err(*args): - proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: + _, err = proc.communicate() return err.decode("utf-8") @@ -69,16 +70,16 @@ def splitlines_no_ends(string): return [s.strip() for s in string.splitlines()] -def changed_files(): +def changed_files(branch="dev"): check_remotes = ["upstream", "origin"] check_remotes.extend(splitlines_no_ends(get_output("git", "remote"))) for remote in check_remotes: - command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "HEAD"] + command = ["git", "merge-base", f"refs/remotes/{remote}/{branch}", "HEAD"] try: merge_base = splitlines_no_ends(get_output(*command))[0] break # pylint: disable=bare-except - except: + except: # noqa: E722 pass else: raise ValueError("Git not configured") @@ -103,7 +104,7 @@ def filter_changed(files): def filter_grep(files, value): matched = [] for file in files: - with open(file) as handle: + with open(file, encoding="utf-8") as handle: contents = handle.read() if value in contents: matched.append(file) @@ -114,8 +115,8 @@ def git_ls_files(patterns=None): command = ["git", "ls-files", "-s"] if patterns is not None: command.extend(patterns) - proc = subprocess.Popen(command, stdout=subprocess.PIPE) - output, err = proc.communicate() + with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: + output, _ = proc.communicate() lines = [x.split() for x in output.decode("utf-8").splitlines()] return {s[3].strip(): int(s[0]) for s in lines} @@ -152,3 +153,39 @@ def load_idedata(environment): temp_idedata.write_text(json.dumps(data, indent=2) + "\n") return data + + +def get_binary(name: str, version: str) -> str: + binary_file = f"{name}-{version}" + try: + result = subprocess.check_output([binary_file, "-version"]) + if result.returncode == 0: + return binary_file + except Exception: + pass + binary_file = name + try: + result = subprocess.run( + [binary_file, "-version"], text=True, capture_output=True + ) + if result.returncode == 0 and (f"version {version}") in result.stdout: + return binary_file + raise FileNotFoundError(f"{name} not found") + + except FileNotFoundError as ex: + print( + f""" + Oops. It looks like {name} is not installed. It should be available under venv/bin + and in PATH after running in turn: + script/setup + source venv/bin/activate. + + Please confirm you can run "{name} -version" or "{name}-{version} -version" + in your terminal and install + {name} (v{version}) if necessary. + + Note you can also upload your code as a pull request on GitHub and see the CI check + output to apply {name} + """ + ) + raise diff --git a/script/list-components.py b/script/list-components.py new file mode 100755 index 000000000000..5b5fa5811f85 --- /dev/null +++ b/script/list-components.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys +import argparse + +from helpers import git_ls_files, changed_files +from esphome.loader import get_component, get_platform +from esphome.core import CORE +from esphome.const import ( + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + PLATFORM_ESP32, + PLATFORM_ESP8266, +) + + +def filter_component_files(str): + return str.startswith("esphome/components/") | str.startswith("tests/components/") + + +def extract_component_names_array_from_files_array(files): + components = [] + for file in files: + file_parts = file.split("/") + if len(file_parts) >= 4: + component_name = file_parts[2] + if component_name not in components: + components.append(component_name) + return components + + +def add_item_to_components_graph(components_graph, parent, child): + if not parent.startswith("__") and parent != child: + if parent not in components_graph: + components_graph[parent] = [] + if child not in components_graph[parent]: + components_graph[parent].append(child) + + +def create_components_graph(): + # The root directory of the repo + root = Path(__file__).parent.parent + components_dir = root / "esphome" / "components" + # Fake some directory so that get_component works + CORE.config_path = str(root) + # Various configuration to capture different outcomes used by `AUTO_LOAD` function. + TARGET_CONFIGURATIONS = [ + {KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: "arduino", KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: "esp-idf", KEY_TARGET_PLATFORM: None}, + {KEY_TARGET_FRAMEWORK: None, KEY_TARGET_PLATFORM: PLATFORM_ESP32}, + ] + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + components_graph = {} + + for path in components_dir.iterdir(): + if not path.is_dir(): + continue + if not (path / "__init__.py").is_file(): + continue + name = path.name + comp = get_component(name) + if comp is None: + print( + f"Cannot find component {name}. Make sure current path is pip installed ESPHome" + ) + sys.exit(1) + + for dependency in comp.dependencies: + add_item_to_components_graph( + components_graph, dependency.split(".")[0], name + ) + + for target_config in TARGET_CONFIGURATIONS: + CORE.data[KEY_CORE] = target_config + for auto_load in comp.auto_load: + add_item_to_components_graph(components_graph, auto_load, name) + # restore config + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + for platform_path in path.iterdir(): + platform_name = platform_path.stem + platform = get_platform(platform_name, name) + if platform is None: + continue + + add_item_to_components_graph(components_graph, platform_name, name) + + for dependency in platform.dependencies: + add_item_to_components_graph( + components_graph, dependency.split(".")[0], name + ) + + for target_config in TARGET_CONFIGURATIONS: + CORE.data[KEY_CORE] = target_config + for auto_load in platform.auto_load: + add_item_to_components_graph(components_graph, auto_load, name) + # restore config + CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0] + + return components_graph + + +def find_children_of_component(components_graph, component_name, depth=0): + if component_name not in components_graph: + return [] + + children = [] + + for child in components_graph[component_name]: + children.append(child) + if depth < 10: + children.extend( + find_children_of_component(components_graph, child, depth + 1) + ) + # Remove duplicate values + return list(set(children)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" + ) + parser.add_argument( + "-b", "--branch", help="Branch to compare changed files against" + ) + args = parser.parse_args() + + if args.branch and not args.changed: + parser.error("--branch requires --changed") + + files = git_ls_files() + files = filter(filter_component_files, files) + + if args.changed: + if args.branch: + changed = changed_files(args.branch) + else: + changed = changed_files() + files = [f for f in files if f in changed] + + components = extract_component_names_array_from_files_array(files) + + if args.changed: + components_graph = create_components_graph() + + all_changed_components = components.copy() + for c in components: + all_changed_components.extend( + find_children_of_component(components_graph, c) + ) + # Remove duplicate values + all_changed_components = list(set(all_changed_components)) + + for c in sorted(all_changed_components): + print(c) + else: + for c in sorted(components): + print(c) + + +if __name__ == "__main__": + main() diff --git a/script/setup b/script/setup index ba3b5443522b..f286b4672a52 100755 --- a/script/setup +++ b/script/setup @@ -4,10 +4,13 @@ set -e cd "$(dirname "$0")/.." - +location="venv/bin/activate" if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" ]; then python3 -m venv venv - source venv/bin/activate + if [ -f venv/Scripts/activate ]; then + location="venv/Scripts/activate" + fi + source $location; fi # Avoid unsafe git error when running inside devcontainer @@ -15,10 +18,14 @@ if [ -n "$DEVCONTAINER" ];then git config --global --add safe.directory "$PWD" fi -pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt +pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -r requirements_dev.txt pip3 install setuptools wheel pip3 install --no-use-pep517 -e . pre-commit install script/platformio_install_deps.py platformio.ini --libraries --tools --platforms + +echo +echo +echo "Virtual environment created. Run 'source $location' to use it." diff --git a/script/sync-device_class.py b/script/sync-device_class.py index ae6f4be0c860..12e1bb6a9fd9 100755 --- a/script/sync-device_class.py +++ b/script/sync-device_class.py @@ -2,12 +2,17 @@ import re +# pylint: disable=import-error from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.button import ButtonDeviceClass from homeassistant.components.cover import CoverDeviceClass +from homeassistant.components.event import EventDeviceClass from homeassistant.components.number import NumberDeviceClass from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.switch import SwitchDeviceClass +from homeassistant.components.valve import ValveDeviceClass + +# pylint: enable=import-error BLOCKLIST = ( # requires special support on HA side @@ -18,17 +23,19 @@ "binary_sensor": BinarySensorDeviceClass, "button": ButtonDeviceClass, "cover": CoverDeviceClass, + "event": EventDeviceClass, "number": NumberDeviceClass, "sensor": SensorDeviceClass, "switch": SwitchDeviceClass, + "valve": ValveDeviceClass, } def sub(path, pattern, repl): - with open(path) as handle: + with open(path, encoding="utf-8") as handle: content = handle.read() content = re.sub(pattern, repl, content, flags=re.MULTILINE) - with open(path, "w") as handle: + with open(path, "w", encoding="utf-8") as handle: handle.write(content) diff --git a/script/test b/script/test index 36a58cd75ae5..e227c17f9f80 100755 --- a/script/test +++ b/script/test @@ -6,12 +6,6 @@ cd "$(dirname "$0")/.." set -x -esphome compile tests/test1.yaml -esphome compile tests/test2.yaml -esphome compile tests/test3.yaml -esphome compile tests/test3.1.yaml -esphome compile tests/test4.yaml -esphome compile tests/test5.yaml -esphome compile tests/test6.yaml -esphome compile tests/test7.yaml -esphome compile tests/test8.yaml +for f in ./tests/test*.yaml; do + esphome compile $f +done diff --git a/script/test_build_components b/script/test_build_components new file mode 100755 index 000000000000..4d9125657200 --- /dev/null +++ b/script/test_build_components @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +set -e + +# Parse parameter: +# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`. +# - `c` - Component folder name to test. Default `*`. +esphome_command="compile" +target_component="*" +while getopts e:c: flag +do + case $flag in + e) esphome_command=${OPTARG};; + c) target_component=${OPTARG};; + \?) echo "Usage: $0 [-e ] [-c ]" 1>&2; exit 1;; + esac +done + +cd "$(dirname "$0")/.." + +if ! [ -d "./tests/test_build_components/build" ]; then + mkdir ./tests/test_build_components/build +fi + +start_esphome() { + # create dynamic yaml file in `build` folder. + # `./tests/test_build_components/build/[target_component].[test_name].[target_platform].yaml` + component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform.yaml" + + cp $target_platform_file $component_test_file + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS sed is...different + sed -i '' "s!\$component_test_file!../../.$f!g" $component_test_file + else + sed -i "s!\$component_test_file!../../.$f!g" $component_test_file + fi + + # Start esphome process + echo "> [$target_component] [$test_name] [$target_platform]" + echo "esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file" + # TODO: Validate escape of Command line substitution value + esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file +} + +# Find all test yaml files. +# - `./tests/components/[target_component]/[test_name].[target_platform].yaml` +# - `./tests/components/[target_component]/[test_name].all.yaml` +for f in ./tests/components/$target_component/*.*.yaml; do + [ -f "$f" ] || continue + IFS='/' read -r -a folder_name <<< "$f" + target_component="${folder_name[3]}" + + IFS='.' read -r -a file_name <<< "${folder_name[4]}" + test_name="${file_name[0]}" + target_platform="${file_name[1]}" + file_name_parts=${#file_name[@]} + + if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then + # Test has *not* defined a specific target platform. Need to run tests for all possible target platforms. + + for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do + IFS='/' read -r -a folder_name <<< "$target_platform_file" + IFS='.' read -r -a file_name <<< "${folder_name[3]}" + target_platform="${file_name[1]}" + + start_esphome + done + + else + # Test has defined a specific target platform. + + # Validate we have a base test yaml for selected platform. + # The target_platform is sourced from the following location. + # 1. `./tests/test_build_components/build_components_base.[target_platform].yaml` + # 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml` + target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml" + if ! [ -f "$target_platform_file" ]; then + # Try find arduino test framework as platform. + target_platform_ard="$target_platform-ard" + target_platform_file="./tests/test_build_components/build_components_base.$target_platform_ard.yaml" + if ! [ -f "$target_platform_file" ]; then + echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml, ./tests/build_components_base.$target_platform_ard.yaml] for component test [$f] found." + exit 1 + fi + target_platform=$target_platform_ard + fi + + start_esphome + fi +done diff --git a/setup.cfg b/setup.cfg index 755cef47c00f..b3cfbba6a1f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,6 @@ classifier = Programming Language :: C++ Programming Language :: Python :: 3 Topic :: Home Automation -Topic :: Home Automation [flake8] max-line-length = 120 diff --git a/tests/README.md b/tests/README.md index 6d83fc68867d..5b312d00de59 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,3 +27,4 @@ Current test_.yaml file contents. | test6.yaml | RP2040 | wifi | N/A | test7.yaml | ESP32-C3 | wifi | N/A | test8.yaml | ESP32-S3 | wifi | None +| test10.yaml | ESP32 | wifi | None diff --git a/tests/component_tests/packages/test_packages.py b/tests/component_tests/packages/test_packages.py index 0e24d78f5c71..01cf55872c97 100644 --- a/tests/component_tests/packages/test_packages.py +++ b/tests/component_tests/packages/test_packages.py @@ -20,7 +20,7 @@ CONF_WIFI, ) from esphome.components.packages import do_packages_pass -from esphome.config_helpers import Extend +from esphome.config_helpers import Extend, Remove import esphome.config_validation as cv # Test strings @@ -349,3 +349,165 @@ def test_package_merge_by_missing_id(): actual = do_packages_pass(config) assert actual == expected + + +def test_package_list_remove_by_id(): + """ + Ensures that components with matching IDs are removed correctly. + + In this test, two sensors are defined in a package, and one of them is removed at the top level. + """ + config = { + CONF_PACKAGES: { + "package_sensors": { + CONF_SENSOR: [ + { + CONF_ID: TEST_SENSOR_ID_1, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_1, + }, + { + CONF_ID: TEST_SENSOR_ID_2, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_2, + }, + ] + }, + # "package2": { + # CONF_SENSOR: [ + # { + # CONF_ID: Remove(TEST_SENSOR_ID_1), + # } + # ], + # }, + }, + CONF_SENSOR: [ + { + CONF_ID: Remove(TEST_SENSOR_ID_1), + }, + ], + } + + expected = { + CONF_SENSOR: [ + { + CONF_ID: TEST_SENSOR_ID_2, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_2, + }, + ] + } + + actual = do_packages_pass(config) + assert actual == expected + + +def test_multiple_package_list_remove_by_id(): + """ + Ensures that components with matching IDs are removed correctly. + + In this test, two sensors are defined in a package, and one of them is removed in another package. + """ + config = { + CONF_PACKAGES: { + "package_sensors": { + CONF_SENSOR: [ + { + CONF_ID: TEST_SENSOR_ID_1, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_1, + }, + { + CONF_ID: TEST_SENSOR_ID_2, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_2, + }, + ] + }, + "package2": { + CONF_SENSOR: [ + { + CONF_ID: Remove(TEST_SENSOR_ID_1), + } + ], + }, + }, + } + + expected = { + CONF_SENSOR: [ + { + CONF_ID: TEST_SENSOR_ID_2, + CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, + CONF_NAME: TEST_SENSOR_NAME_2, + }, + ] + } + + actual = do_packages_pass(config) + assert actual == expected + + +def test_package_dict_remove_by_id(basic_wifi, basic_esphome): + """ + 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. + + In this test, CONF_SSID should be overwritten by that defined in the top-level config. + """ + config = { + CONF_ESPHOME: basic_esphome, + CONF_PACKAGES: {"network": {CONF_WIFI: basic_wifi}}, + CONF_WIFI: Remove(), + } + + expected = { + CONF_ESPHOME: basic_esphome, + } + + actual = do_packages_pass(config) + assert actual == expected + + +def test_package_remove_by_missing_id(): + """ + Ensures that components with missing IDs are not merged. + """ + + config = { + CONF_PACKAGES: { + "sensors": { + CONF_SENSOR: [ + {CONF_ID: TEST_SENSOR_ID_1, CONF_FILTERS: [{CONF_MULTIPLY: 42.0}]}, + ] + } + }, + "missing_key": Remove(), + CONF_SENSOR: [ + {CONF_ID: TEST_SENSOR_ID_1, CONF_FILTERS: [{CONF_MULTIPLY: 10.0}]}, + {CONF_ID: Remove(TEST_SENSOR_ID_2), CONF_FILTERS: [{CONF_OFFSET: 146.0}]}, + ], + } + + expected = { + "missing_key": Remove(), + CONF_SENSOR: [ + { + CONF_ID: TEST_SENSOR_ID_1, + CONF_FILTERS: [{CONF_MULTIPLY: 42.0}], + }, + { + CONF_ID: TEST_SENSOR_ID_1, + CONF_FILTERS: [{CONF_MULTIPLY: 10.0}], + }, + { + CONF_ID: Remove(TEST_SENSOR_ID_2), + CONF_FILTERS: [{CONF_OFFSET: 146.0}], + }, + ], + } + + actual = do_packages_pass(config) + assert actual == expected diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py new file mode 100644 index 000000000000..51fcb3d382f0 --- /dev/null +++ b/tests/component_tests/text/test_text.py @@ -0,0 +1,70 @@ +"""Tests for the binary sensor component.""" + + +def test_text_is_setup(generate_main): + """ + When the binary sensor is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + + # Then + assert "new template_::TemplateText();" in main_cpp + assert "App.register_text" in main_cpp + + +def test_text_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + + # Then + assert 'it_1->set_name("test 1 text");' in main_cpp + + +def test_text_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + + # Then + assert "it_2->set_internal(false);" in main_cpp + assert "it_3->set_internal(true);" in main_cpp + + +def test_text_config_value_mode_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + + # Then + assert "it_1->traits.set_mode(text::TEXT_MODE_TEXT);" in main_cpp + assert "it_3->traits.set_mode(text::TEXT_MODE_PASSWORD);" in main_cpp + + +def test_text_config_lamda_is_set(generate_main): + """ + Test if lambda is set for lambda mode + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text/test_text.yaml") + + # Then + assert "it_4->set_template([=]() -> optional {" in main_cpp + assert 'return std::string{"Hello"};' in main_cpp diff --git a/tests/component_tests/text/test_text.yaml b/tests/component_tests/text/test_text.yaml new file mode 100644 index 000000000000..d81c909f9d14 --- /dev/null +++ b/tests/component_tests/text/test_text.yaml @@ -0,0 +1,45 @@ +esphome: + name: test + +esp32: + board: esp32dev + +text: + - platform: template + name: "test 1 text" + id: "it_1" + optimistic: true + mode: text + + - platform: template + name: "test 2 text" + id: "it_2" + icon: "mdi:text" + optimistic: true + min_length: 5 + max_length: 255 + internal: false + initial_value: "Welcome ESPHOME" + restore_value: true + mode: text + + - platform: template + name: "test 3 key" + id: "it_3" + icon: "mdi:text" + mode: "password" + optimistic: true + internal: true + max_length: 255 + + - platform: template + name: "test 4 key" + id: "it_4" + mode: text + set_action: + - then: + - logger.log: + format: Template text set to %s + args: ["x.c_str()"] + lambda: | + return std::string{"Hello"}; diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py new file mode 100644 index 000000000000..1c4ef6633d73 --- /dev/null +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -0,0 +1,58 @@ +"""Tests for the text sensor component.""" + + +def test_text_sensor_is_setup(generate_main): + """ + When the text is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert "new template_::TemplateTextSensor();" in main_cpp + assert "App.register_text_sensor" in main_cpp + + +def test_text_sensor_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + 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 + + +def test_text_sensor_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert "ts_2->set_internal(true);" in main_cpp + assert "ts_3->set_internal(false);" in main_cpp + + +def test_text_sensor_device_class_set(generate_main): + """ + When the device_class of text_sensor is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") + + # Then + assert 'ts_2->set_device_class("timestamp");' in main_cpp + assert 'ts_3->set_device_class("date");' in main_cpp diff --git a/tests/component_tests/text_sensor/test_text_sensor.yaml b/tests/component_tests/text_sensor/test_text_sensor.yaml new file mode 100644 index 000000000000..b426cb102c3e --- /dev/null +++ b/tests/component_tests/text_sensor/test_text_sensor.yaml @@ -0,0 +1,26 @@ +--- +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +text_sensor: + - platform: template + id: ts_1 + name: "Template Text Sensor 1" + lambda: |- + return {"Hello World"}; + - platform: template + id: ts_2 + name: "Template Text Sensor 2" + lambda: |- + return {"2023-06-22T18:43:52+00:00"}; + device_class: timestamp + internal: true + - platform: template + id: ts_3 + name: "Template Text Sensor 3" + lambda: |- + return {"2023-06-22T18:43:52+00:00"}; + device_class: date + internal: false diff --git a/tests/components/a01nyub/test.esp32-c3-idf.yaml b/tests/components/a01nyub/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3132f77136f0 --- /dev/null +++ b/tests/components/a01nyub/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32-c3.yaml b/tests/components/a01nyub/test.esp32-c3.yaml new file mode 100644 index 000000000000..3132f77136f0 --- /dev/null +++ b/tests/components/a01nyub/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32-idf.yaml b/tests/components/a01nyub/test.esp32-idf.yaml new file mode 100644 index 000000000000..79fc9c5fbf06 --- /dev/null +++ b/tests/components/a01nyub/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp32.yaml b/tests/components/a01nyub/test.esp32.yaml new file mode 100644 index 000000000000..79fc9c5fbf06 --- /dev/null +++ b/tests/components/a01nyub/test.esp32.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.esp8266.yaml b/tests/components/a01nyub/test.esp8266.yaml new file mode 100644 index 000000000000..3132f77136f0 --- /dev/null +++ b/tests/components/a01nyub/test.esp8266.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a01nyub/test.rp2040.yaml b/tests/components/a01nyub/test.rp2040.yaml new file mode 100644 index 000000000000..3132f77136f0 --- /dev/null +++ b/tests/components/a01nyub/test.rp2040.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a01nyub + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a01nyub + id: a01nyub_sensor + name: a01nyub Distance + uart_id: uart_a01nyub diff --git a/tests/components/a02yyuw/test.esp32-c3-idf.yaml b/tests/components/a02yyuw/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..76e1ad8ee1e5 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32-c3.yaml b/tests/components/a02yyuw/test.esp32-c3.yaml new file mode 100644 index 000000000000..76e1ad8ee1e5 --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32-idf.yaml b/tests/components/a02yyuw/test.esp32-idf.yaml new file mode 100644 index 000000000000..98d6a266b3fb --- /dev/null +++ b/tests/components/a02yyuw/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp32.yaml b/tests/components/a02yyuw/test.esp32.yaml new file mode 100644 index 000000000000..98d6a266b3fb --- /dev/null +++ b/tests/components/a02yyuw/test.esp32.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.esp8266.yaml b/tests/components/a02yyuw/test.esp8266.yaml new file mode 100644 index 000000000000..76e1ad8ee1e5 --- /dev/null +++ b/tests/components/a02yyuw/test.esp8266.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a02yyuw/test.rp2040.yaml b/tests/components/a02yyuw/test.rp2040.yaml new file mode 100644 index 000000000000..76e1ad8ee1e5 --- /dev/null +++ b/tests/components/a02yyuw/test.rp2040.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_a02yyuw + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: a02yyuw + id: a02yyuw_sensor + name: a02yyuw Distance + uart_id: uart_a02yyuw diff --git a/tests/components/a4988/test.esp32-c3-idf.yaml b/tests/components/a4988/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..af4e4fa32b83 --- /dev/null +++ b/tests/components/a4988/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32-c3.yaml b/tests/components/a4988/test.esp32-c3.yaml new file mode 100644 index 000000000000..af4e4fa32b83 --- /dev/null +++ b/tests/components/a4988/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32-idf.yaml b/tests/components/a4988/test.esp32-idf.yaml new file mode 100644 index 000000000000..0ca5e3f50466 --- /dev/null +++ b/tests/components/a4988/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 22 + dir_pin: + number: 23 + sleep_pin: + number: 25 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp32.yaml b/tests/components/a4988/test.esp32.yaml new file mode 100644 index 000000000000..0ca5e3f50466 --- /dev/null +++ b/tests/components/a4988/test.esp32.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 22 + dir_pin: + number: 23 + sleep_pin: + number: 25 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.esp8266.yaml b/tests/components/a4988/test.esp8266.yaml new file mode 100644 index 000000000000..f4c1886fc559 --- /dev/null +++ b/tests/components/a4988/test.esp8266.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 1 + dir_pin: + number: 2 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/a4988/test.rp2040.yaml b/tests/components/a4988/test.rp2040.yaml new file mode 100644 index 000000000000..af4e4fa32b83 --- /dev/null +++ b/tests/components/a4988/test.rp2040.yaml @@ -0,0 +1,12 @@ +stepper: + - platform: a4988 + id: a4988_stepper + step_pin: + number: 2 + dir_pin: + number: 3 + sleep_pin: + number: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/absolute_humidity/common.yaml b/tests/components/absolute_humidity/common.yaml new file mode 100644 index 000000000000..87a99f52061f --- /dev/null +++ b/tests/components/absolute_humidity/common.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: absolute_humidity + name: Absolute Humidity + temperature: template_temperature + humidity: template_humidity + - platform: template + id: template_humidity + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } diff --git a/tests/components/absolute_humidity/test.esp32-c3-idf.yaml b/tests/components/absolute_humidity/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.esp32-c3.yaml b/tests/components/absolute_humidity/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.esp32-idf.yaml b/tests/components/absolute_humidity/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.esp32.yaml b/tests/components/absolute_humidity/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.esp8266.yaml b/tests/components/absolute_humidity/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/absolute_humidity/test.rp2040.yaml b/tests/components/absolute_humidity/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/absolute_humidity/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ac_dimmer/test.esp32-c3.yaml b/tests/components/ac_dimmer/test.esp32-c3.yaml new file mode 100644 index 000000000000..f411d376be6e --- /dev/null +++ b/tests/components/ac_dimmer/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 6 diff --git a/tests/components/ac_dimmer/test.esp32.yaml b/tests/components/ac_dimmer/test.esp32.yaml new file mode 100644 index 000000000000..cc172016665c --- /dev/null +++ b/tests/components/ac_dimmer/test.esp32.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 12 + zero_cross_pin: + number: 13 diff --git a/tests/components/ac_dimmer/test.esp8266.yaml b/tests/components/ac_dimmer/test.esp8266.yaml new file mode 100644 index 000000000000..af18d11c5f56 --- /dev/null +++ b/tests/components/ac_dimmer/test.esp8266.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 4 diff --git a/tests/components/ac_dimmer/test.rp2040.yaml b/tests/components/ac_dimmer/test.rp2040.yaml new file mode 100644 index 000000000000..f411d376be6e --- /dev/null +++ b/tests/components/ac_dimmer/test.rp2040.yaml @@ -0,0 +1,7 @@ +output: + - platform: ac_dimmer + id: ac_dimmer_1 + gate_pin: + number: 5 + zero_cross_pin: + number: 6 diff --git a/tests/components/adc/test.esp32-c3.yaml b/tests/components/adc/test.esp32-c3.yaml new file mode 100644 index 000000000000..18e5ab3561a0 --- /dev/null +++ b/tests/components/adc/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db diff --git a/tests/components/adc/test.esp32-idf.yaml b/tests/components/adc/test.esp32-idf.yaml new file mode 100644 index 000000000000..923fd0d706b0 --- /dev/null +++ b/tests/components/adc/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: adc + pin: A0 + name: Living Room Brightness + update_interval: "1:01" + attenuation: 2.5db + unit_of_measurement: "°C" + icon: "mdi:water-percent" + accuracy_decimals: 5 + setup_priority: -100 + force_update: true diff --git a/tests/components/adc/test.esp32-s2.yaml b/tests/components/adc/test.esp32-s2.yaml new file mode 100644 index 000000000000..0119ad5e4d14 --- /dev/null +++ b/tests/components/adc/test.esp32-s2.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db diff --git a/tests/components/adc/test.esp32-s3.yaml b/tests/components/adc/test.esp32-s3.yaml new file mode 100644 index 000000000000..0119ad5e4d14 --- /dev/null +++ b/tests/components/adc/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db diff --git a/tests/components/adc/test.esp32.yaml b/tests/components/adc/test.esp32.yaml new file mode 100644 index 000000000000..923fd0d706b0 --- /dev/null +++ b/tests/components/adc/test.esp32.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: adc + pin: A0 + name: Living Room Brightness + update_interval: "1:01" + attenuation: 2.5db + unit_of_measurement: "°C" + icon: "mdi:water-percent" + accuracy_decimals: 5 + setup_priority: -100 + force_update: true diff --git a/tests/components/adc/test.esp8266.yaml b/tests/components/adc/test.esp8266.yaml new file mode 100644 index 000000000000..1ef79c7ca1fe --- /dev/null +++ b/tests/components/adc/test.esp8266.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: adc + id: my_sensor + pin: VCC diff --git a/tests/components/adc/test.rp2040.yaml b/tests/components/adc/test.rp2040.yaml new file mode 100644 index 000000000000..200b802a4d89 --- /dev/null +++ b/tests/components/adc/test.rp2040.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: adc + pin: VCC + name: VSYS diff --git a/tests/components/adc128s102/test.esp32-c3-idf.yaml b/tests/components/adc128s102/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8edf745e584d --- /dev/null +++ b/tests/components/adc128s102/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +adc128s102: + cs_pin: 8 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32-c3.yaml b/tests/components/adc128s102/test.esp32-c3.yaml new file mode 100644 index 000000000000..8edf745e584d --- /dev/null +++ b/tests/components/adc128s102/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +adc128s102: + cs_pin: 8 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32-idf.yaml b/tests/components/adc128s102/test.esp32-idf.yaml new file mode 100644 index 000000000000..005fbccc3454 --- /dev/null +++ b/tests/components/adc128s102/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +adc128s102: + cs_pin: 12 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp32.yaml b/tests/components/adc128s102/test.esp32.yaml new file mode 100644 index 000000000000..005fbccc3454 --- /dev/null +++ b/tests/components/adc128s102/test.esp32.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +adc128s102: + cs_pin: 12 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.esp8266.yaml b/tests/components/adc128s102/test.esp8266.yaml new file mode 100644 index 000000000000..09a51caec16b --- /dev/null +++ b/tests/components/adc128s102/test.esp8266.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +adc128s102: + cs_pin: 15 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/adc128s102/test.rp2040.yaml b/tests/components/adc128s102/test.rp2040.yaml new file mode 100644 index 000000000000..a7d54cbfe69e --- /dev/null +++ b/tests/components/adc128s102/test.rp2040.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_adc128s102 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +adc128s102: + cs_pin: 5 + id: adc128s102_adc + +sensor: + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 diff --git a/tests/components/addressable_light/test.esp32-c3-idf.yaml b/tests/components/addressable_light/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f587113faca5 --- /dev/null +++ b/tests/components/addressable_light/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32-c3.yaml b/tests/components/addressable_light/test.esp32-c3.yaml new file mode 100644 index 000000000000..f587113faca5 --- /dev/null +++ b/tests/components/addressable_light/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32-idf.yaml b/tests/components/addressable_light/test.esp32-idf.yaml new file mode 100644 index 000000000000..f587113faca5 --- /dev/null +++ b/tests/components/addressable_light/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/addressable_light/test.esp32.yaml b/tests/components/addressable_light/test.esp32.yaml new file mode 100644 index 000000000000..f7717be6106a --- /dev/null +++ b/tests/components/addressable_light/test.esp32.yaml @@ -0,0 +1,32 @@ +light: + - platform: fastled_clockless + id: led_matrix_32x8 + name: led_matrix_32x8 + chipset: WS2812B + pin: 2 + num_leds: 256 + rgb_order: GRB + default_transition_length: 0s + color_correct: [50%, 50%, 50%] + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms diff --git a/tests/components/ade7880/common.yaml b/tests/components/ade7880/common.yaml new file mode 100644 index 000000000000..0aa388a325bd --- /dev/null +++ b/tests/components/ade7880/common.yaml @@ -0,0 +1,56 @@ +i2c: + - id: i2c_ade7880 + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: ade7880 + i2c_id: i2c_ade7880 + irq0_pin: ${irq0_pin} + irq1_pin: ${irq1_pin} + reset_pin: ${reset_pin} + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 diff --git a/tests/components/ade7880/test.esp32-c3-idf.yaml b/tests/components/ade7880/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..87db3e942738 --- /dev/null +++ b/tests/components/ade7880/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO6 + irq1_pin: GPIO7 + reset_pin: GPIO10 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32-c3.yaml b/tests/components/ade7880/test.esp32-c3.yaml new file mode 100644 index 000000000000..87db3e942738 --- /dev/null +++ b/tests/components/ade7880/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO6 + irq1_pin: GPIO7 + reset_pin: GPIO10 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32-idf.yaml b/tests/components/ade7880/test.esp32-idf.yaml new file mode 100644 index 000000000000..685b49ff32a9 --- /dev/null +++ b/tests/components/ade7880/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp32.yaml b/tests/components/ade7880/test.esp32.yaml new file mode 100644 index 000000000000..685b49ff32a9 --- /dev/null +++ b/tests/components/ade7880/test.esp32.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.esp8266.yaml b/tests/components/ade7880/test.esp8266.yaml new file mode 100644 index 000000000000..685b49ff32a9 --- /dev/null +++ b/tests/components/ade7880/test.esp8266.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/ade7880/test.rp2040.yaml b/tests/components/ade7880/test.rp2040.yaml new file mode 100644 index 000000000000..685b49ff32a9 --- /dev/null +++ b/tests/components/ade7880/test.rp2040.yaml @@ -0,0 +1,8 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + irq0_pin: GPIO13 + irq1_pin: GPIO15 + reset_pin: GPIO16 + +<<: !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 new file mode 100644 index 000000000000..d7b365a7e1b8 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32-c3.yaml b/tests/components/ade7953_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..d7b365a7e1b8 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32-idf.yaml b/tests/components/ade7953_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..71602f20a363 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp32.yaml b/tests/components/ade7953_i2c/test.esp32.yaml new file mode 100644 index 000000000000..71602f20a363 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp32.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.esp8266.yaml b/tests/components/ade7953_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..6903cd195395 --- /dev/null +++ b/tests/components/ade7953_i2c/test.esp8266.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_i2c/test.rp2040.yaml b/tests/components/ade7953_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..d7b365a7e1b8 --- /dev/null +++ b/tests/components/ade7953_i2c/test.rp2040.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-c3-idf.yaml b/tests/components/ade7953_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a967f28d9c56 --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: ade7953_spi + cs_pin: 8 + irq_pin: 9 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-c3.yaml b/tests/components/ade7953_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..a967f28d9c56 --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-c3.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: ade7953_spi + cs_pin: 8 + irq_pin: 9 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32-idf.yaml b/tests/components/ade7953_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..e9ef7e41165e --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32-idf.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 13 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp32.yaml b/tests/components/ade7953_spi/test.esp32.yaml new file mode 100644 index 000000000000..e9ef7e41165e --- /dev/null +++ b/tests/components/ade7953_spi/test.esp32.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 13 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.esp8266.yaml b/tests/components/ade7953_spi/test.esp8266.yaml new file mode 100644 index 000000000000..b36b4445abea --- /dev/null +++ b/tests/components/ade7953_spi/test.esp8266.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: ade7953_spi + cs_pin: 15 + irq_pin: 5 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ade7953_spi/test.rp2040.yaml b/tests/components/ade7953_spi/test.rp2040.yaml new file mode 100644 index 000000000000..319abd4613ad --- /dev/null +++ b/tests/components/ade7953_spi/test.rp2040.yaml @@ -0,0 +1,36 @@ +spi: + - id: spi_ade7953 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: ade7953_spi + cs_pin: 5 + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s diff --git a/tests/components/ads1115/test.esp32-c3-idf.yaml b/tests/components/ads1115/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7ac5a09f3f87 --- /dev/null +++ b/tests/components/ads1115/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32-c3.yaml b/tests/components/ads1115/test.esp32-c3.yaml new file mode 100644 index 000000000000..7ac5a09f3f87 --- /dev/null +++ b/tests/components/ads1115/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32-idf.yaml b/tests/components/ads1115/test.esp32-idf.yaml new file mode 100644 index 000000000000..a869f2379bd8 --- /dev/null +++ b/tests/components/ads1115/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 16 + sda: 17 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp32.yaml b/tests/components/ads1115/test.esp32.yaml new file mode 100644 index 000000000000..a869f2379bd8 --- /dev/null +++ b/tests/components/ads1115/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 16 + sda: 17 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.esp8266.yaml b/tests/components/ads1115/test.esp8266.yaml new file mode 100644 index 000000000000..7ac5a09f3f87 --- /dev/null +++ b/tests/components/ads1115/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ads1115/test.rp2040.yaml b/tests/components/ads1115/test.rp2040.yaml new file mode 100644 index 000000000000..7ac5a09f3f87 --- /dev/null +++ b/tests/components/ads1115/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ads1115 + scl: 5 + sda: 4 + +ads1115: + address: 0x48 + +sensor: + - platform: ads1115 + multiplexer: A0_A1 + gain: 1.024 + id: ads1115_sensor diff --git a/tests/components/ags10/test.esp32-c3-idf.yaml b/tests/components/ags10/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e338fc78e064 --- /dev/null +++ b/tests/components/ags10/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32-c3.yaml b/tests/components/ags10/test.esp32-c3.yaml new file mode 100644 index 000000000000..e338fc78e064 --- /dev/null +++ b/tests/components/ags10/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32-idf.yaml b/tests/components/ags10/test.esp32-idf.yaml new file mode 100644 index 000000000000..b3b53c0d3152 --- /dev/null +++ b/tests/components/ags10/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 16 + sda: 17 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp32.yaml b/tests/components/ags10/test.esp32.yaml new file mode 100644 index 000000000000..b3b53c0d3152 --- /dev/null +++ b/tests/components/ags10/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 16 + sda: 17 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/ags10/test.esp8266.yaml b/tests/components/ags10/test.esp8266.yaml new file mode 100644 index 000000000000..e338fc78e064 --- /dev/null +++ b/tests/components/ags10/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ags10 + scl: 5 + sda: 4 + frequency: 10kHz + +sensor: + - platform: ags10 + id: ags10_1 + tvoc: + name: AGS10 TVOC + update_interval: 60s diff --git a/tests/components/aht10/test.esp32-c3-idf.yaml b/tests/components/aht10/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2e5f50547645 --- /dev/null +++ b/tests/components/aht10/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32-c3.yaml b/tests/components/aht10/test.esp32-c3.yaml new file mode 100644 index 000000000000..2e5f50547645 --- /dev/null +++ b/tests/components/aht10/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32-idf.yaml b/tests/components/aht10/test.esp32-idf.yaml new file mode 100644 index 000000000000..499e69e5d383 --- /dev/null +++ b/tests/components/aht10/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 16 + sda: 17 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp32.yaml b/tests/components/aht10/test.esp32.yaml new file mode 100644 index 000000000000..499e69e5d383 --- /dev/null +++ b/tests/components/aht10/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 16 + sda: 17 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.esp8266.yaml b/tests/components/aht10/test.esp8266.yaml new file mode 100644 index 000000000000..2e5f50547645 --- /dev/null +++ b/tests/components/aht10/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/aht10/test.rp2040.yaml b/tests/components/aht10/test.rp2040.yaml new file mode 100644 index 000000000000..2e5f50547645 --- /dev/null +++ b/tests/components/aht10/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_aht10 + scl: 5 + sda: 4 + +sensor: + - platform: aht10 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/airthings_wave_mini/common.yaml b/tests/components/airthings_wave_mini/common.yaml new file mode 100644 index 000000000000..87902e6c6638 --- /dev/null +++ b/tests/components/airthings_wave_mini/common.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthingsmini01 + +sensor: + - id: airthingswm + platform: airthings_wave_mini + ble_client_id: airthingsmini01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Mini Temperature + humidity: + name: Wave Mini Humidity + pressure: + name: Wave Mini Pressure + tvoc: + name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage diff --git a/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_mini/test.esp32-c3.yaml b/tests/components/airthings_wave_mini/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_mini/test.esp32-idf.yaml b/tests/components/airthings_wave_mini/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_mini/test.esp32.yaml b/tests/components/airthings_wave_mini/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_mini/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_plus/common.yaml b/tests/components/airthings_wave_plus/common.yaml new file mode 100644 index 000000000000..2124fcdaec53 --- /dev/null +++ b/tests/components/airthings_wave_plus/common.yaml @@ -0,0 +1,28 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +sensor: + - id: airthingswp + platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + battery_update_interval: 12h + temperature: + name: Wave Plus Temperature + radon: + name: Wave Plus Radon + radon_long_term: + name: Wave Plus Radon Long Term + pressure: + name: Wave Plus Pressure + humidity: + name: Wave Plus Humidity + co2: + name: Wave Plus CO2 + tvoc: + name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage diff --git a/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml b/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_plus/test.esp32-c3.yaml b/tests/components/airthings_wave_plus/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_plus/test.esp32-idf.yaml b/tests/components/airthings_wave_plus/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/airthings_wave_plus/test.esp32.yaml b/tests/components/airthings_wave_plus/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/airthings_wave_plus/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/common.yaml b/tests/components/alarm_control_panel/common.yaml new file mode 100644 index 000000000000..218274bad462 --- /dev/null +++ b/tests/components/alarm_control_panel/common.yaml @@ -0,0 +1,64 @@ +binary_sensor: + - platform: gpio + id: bin1 + pin: 1 + +alarm_control_panel: + - platform: template + id: alarmcontrolpanel1 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_state: + then: + - lambda: !lambda |- + ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); + - platform: template + id: alarmcontrolpanel2 + name: Alarm Panel + codes: + - "1234" + requires_code_to_arm: true + arming_home_time: 1s + arming_night_time: 1s + arming_away_time: 15s + pending_time: 15s + trigger_time: 30s + binary_sensors: + - input: bin1 + bypass_armed_home: true + bypass_armed_night: true + on_disarmed: + then: + - logger.log: "### DISARMED ###" + on_pending: + then: + - logger.log: "### PENDING ###" + on_arming: + then: + - logger.log: "### ARMING ###" + on_armed_home: + then: + - logger.log: "### ARMED HOME ###" + on_armed_night: + then: + - logger.log: "### ARMED NIGHT ###" + on_armed_away: + then: + - logger.log: "### ARMED AWAY ###" + on_triggered: + then: + - logger.log: "### TRIGGERED ###" + on_cleared: + then: + - logger.log: "### CLEARED ###" diff --git a/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml b/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.esp32-c3.yaml b/tests/components/alarm_control_panel/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.esp32-idf.yaml b/tests/components/alarm_control_panel/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.esp32.yaml b/tests/components/alarm_control_panel/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.esp8266.yaml b/tests/components/alarm_control_panel/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alarm_control_panel/test.rp2040.yaml b/tests/components/alarm_control_panel/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alarm_control_panel/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alpha3/common.yaml b/tests/components/alpha3/common.yaml new file mode 100644 index 000000000000..913f086ac4c8 --- /dev/null +++ b/tests/components/alpha3/common.yaml @@ -0,0 +1,17 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: alpha3_blec + +sensor: + - platform: alpha3 + ble_client_id: alpha3_blec + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" diff --git a/tests/components/alpha3/test.esp32-c3-idf.yaml b/tests/components/alpha3/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alpha3/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alpha3/test.esp32-c3.yaml b/tests/components/alpha3/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alpha3/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alpha3/test.esp32-idf.yaml b/tests/components/alpha3/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alpha3/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/alpha3/test.esp32.yaml b/tests/components/alpha3/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/alpha3/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/am2315c/test.esp32-c3-idf.yaml b/tests/components/am2315c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d09bffb7a43a --- /dev/null +++ b/tests/components/am2315c/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32-c3.yaml b/tests/components/am2315c/test.esp32-c3.yaml new file mode 100644 index 000000000000..d09bffb7a43a --- /dev/null +++ b/tests/components/am2315c/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32-idf.yaml b/tests/components/am2315c/test.esp32-idf.yaml new file mode 100644 index 000000000000..ed6b65f787c5 --- /dev/null +++ b/tests/components/am2315c/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 16 + sda: 17 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp32.yaml b/tests/components/am2315c/test.esp32.yaml new file mode 100644 index 000000000000..ed6b65f787c5 --- /dev/null +++ b/tests/components/am2315c/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 16 + sda: 17 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.esp8266.yaml b/tests/components/am2315c/test.esp8266.yaml new file mode 100644 index 000000000000..d09bffb7a43a --- /dev/null +++ b/tests/components/am2315c/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2315c/test.rp2040.yaml b/tests/components/am2315c/test.rp2040.yaml new file mode 100644 index 000000000000..d09bffb7a43a --- /dev/null +++ b/tests/components/am2315c/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_am2315c + scl: 5 + sda: 4 + +sensor: + - platform: am2315c + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-c3-idf.yaml b/tests/components/am2320/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..6acfe8d4fdf0 --- /dev/null +++ b/tests/components/am2320/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-c3.yaml b/tests/components/am2320/test.esp32-c3.yaml new file mode 100644 index 000000000000..6acfe8d4fdf0 --- /dev/null +++ b/tests/components/am2320/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32-idf.yaml b/tests/components/am2320/test.esp32-idf.yaml new file mode 100644 index 000000000000..99f4173b85c3 --- /dev/null +++ b/tests/components/am2320/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp32.yaml b/tests/components/am2320/test.esp32.yaml new file mode 100644 index 000000000000..99f4173b85c3 --- /dev/null +++ b/tests/components/am2320/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.esp8266.yaml b/tests/components/am2320/test.esp8266.yaml new file mode 100644 index 000000000000..6acfe8d4fdf0 --- /dev/null +++ b/tests/components/am2320/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am2320/test.rp2040.yaml b/tests/components/am2320/test.rp2040.yaml new file mode 100644 index 000000000000..6acfe8d4fdf0 --- /dev/null +++ b/tests/components/am2320/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +sensor: + - platform: am2320 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/am43/common.yaml b/tests/components/am43/common.yaml new file mode 100644 index 000000000000..60b7d81a550e --- /dev/null +++ b/tests/components/am43/common.yaml @@ -0,0 +1,19 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: am43_blec + +cover: + - platform: am43 + name: Test AM43 Cover + id: am43_test + ble_client_id: am43_blec + +sensor: + - platform: am43 + ble_client_id: am43_blec + battery_level: + name: Kitchen blinds battery + illuminance: + name: Kitchen blinds light diff --git a/tests/components/am43/test.esp32-c3-idf.yaml b/tests/components/am43/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/am43/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/am43/test.esp32-c3.yaml b/tests/components/am43/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/am43/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/am43/test.esp32-idf.yaml b/tests/components/am43/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/am43/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/am43/test.esp32.yaml b/tests/components/am43/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/am43/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/common.yaml b/tests/components/analog_threshold/common.yaml new file mode 100644 index 000000000000..b5c14dfe5658 --- /dev/null +++ b/tests/components/analog_threshold/common.yaml @@ -0,0 +1,28 @@ +sensor: + - platform: template + id: template_sensor + name: Template Sensor + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 15s + +binary_sensor: + - platform: analog_threshold + name: Analog Threshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Threshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: diff --git a/tests/components/analog_threshold/test.esp32-c3-idf.yaml b/tests/components/analog_threshold/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.esp32-c3.yaml b/tests/components/analog_threshold/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.esp32-idf.yaml b/tests/components/analog_threshold/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.esp32.yaml b/tests/components/analog_threshold/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.esp8266.yaml b/tests/components/analog_threshold/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/analog_threshold/test.rp2040.yaml b/tests/components/analog_threshold/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/analog_threshold/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/animation/test.esp32-c3-idf.yaml b/tests/components/animation/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9bcfbdb11841 --- /dev/null +++ b/tests/components/animation/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32-c3.yaml b/tests/components/animation/test.esp32-c3.yaml new file mode 100644 index 000000000000..9bcfbdb11841 --- /dev/null +++ b/tests/components/animation/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32-idf.yaml b/tests/components/animation/test.esp32-idf.yaml new file mode 100644 index 000000000000..5dc132eb2d55 --- /dev/null +++ b/tests/components/animation/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp32.yaml b/tests/components/animation/test.esp32.yaml new file mode 100644 index 000000000000..5dc132eb2d55 --- /dev/null +++ b/tests/components/animation/test.esp32.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.esp8266.yaml b/tests/components/animation/test.esp8266.yaml new file mode 100644 index 000000000000..ef0f483a79e2 --- /dev/null +++ b/tests/components/animation/test.esp8266.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/animation/test.rp2040.yaml b/tests/components/animation/test.rp2040.yaml new file mode 100644 index 000000000000..6ee29a334744 --- /dev/null +++ b/tests/components/animation/test.rp2040.yaml @@ -0,0 +1,23 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: ../../pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/components/anova/common.yaml b/tests/components/anova/common.yaml new file mode 100644 index 000000000000..c4162fe71e9e --- /dev/null +++ b/tests/components/anova/common.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: anova_blec + +climate: + - platform: anova + name: Anova cooker + ble_client_id: anova_blec + unit_of_measurement: c diff --git a/tests/components/anova/test.esp32-c3-idf.yaml b/tests/components/anova/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/anova/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/anova/test.esp32-c3.yaml b/tests/components/anova/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/anova/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/anova/test.esp32-idf.yaml b/tests/components/anova/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/anova/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/anova/test.esp32.yaml b/tests/components/anova/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/anova/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/apds9960/test.esp32-c3-idf.yaml b/tests/components/apds9960/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f6b6f7bac005 --- /dev/null +++ b/tests/components/apds9960/test.esp32-c3-idf.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32-c3.yaml b/tests/components/apds9960/test.esp32-c3.yaml new file mode 100644 index 000000000000..f6b6f7bac005 --- /dev/null +++ b/tests/components/apds9960/test.esp32-c3.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32-idf.yaml b/tests/components/apds9960/test.esp32-idf.yaml new file mode 100644 index 000000000000..7ff70a4d47b1 --- /dev/null +++ b/tests/components/apds9960/test.esp32-idf.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp32.yaml b/tests/components/apds9960/test.esp32.yaml new file mode 100644 index 000000000000..7ff70a4d47b1 --- /dev/null +++ b/tests/components/apds9960/test.esp32.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 16 + sda: 17 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.esp8266.yaml b/tests/components/apds9960/test.esp8266.yaml new file mode 100644 index 000000000000..f6b6f7bac005 --- /dev/null +++ b/tests/components/apds9960/test.esp8266.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/apds9960/test.rp2040.yaml b/tests/components/apds9960/test.rp2040.yaml new file mode 100644 index 000000000000..f6b6f7bac005 --- /dev/null +++ b/tests/components/apds9960/test.rp2040.yaml @@ -0,0 +1,48 @@ +i2c: + - id: i2c_bme280 + scl: 5 + sda: 4 + +apds9960: + address: 0x20 + update_interval: 60s + +binary_sensor: + - platform: apds9960 + id: apds9960_binary_sensor + direction: up + name: APDS9960 Up + device_class: motion + filters: + - invert + - delayed_on: 20ms + - delayed_off: 20ms + - lambda: "return false;" + on_state: + - logger.log: New state + - platform: apds9960 + direction: down + name: APDS9960 Down + - platform: apds9960 + direction: left + name: APDS9960 Left + - platform: apds9960 + direction: right + name: APDS9960 Right + +sensor: + - platform: apds9960 + type: proximity + name: APDS9960 Proximity + - platform: apds9960 + type: clear + name: APDS9960 Clear + - platform: apds9960 + type: red + name: APDS9960 Red + - platform: apds9960 + type: green + name: APDS9960 Green + - platform: apds9960 + type: blue + name: APDS9960 Blue diff --git a/tests/components/api/common.yaml b/tests/components/api/common.yaml new file mode 100644 index 000000000000..3c56811b95a7 --- /dev/null +++ b/tests/components/api/common.yaml @@ -0,0 +1,63 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.tag_scanned: pulse + +wifi: + ssid: MySSID + password: password1 + +api: + port: 8000 + password: pwd + reboot_timeout: 0min + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= + services: + - service: hello_world + variables: + name: string + then: + - logger.log: + format: Hello World %s! + args: + - name.c_str() + - service: empty_service + then: + - logger.log: Service Called + - service: all_types + variables: + bool_: bool + int_: int + float_: float + string_: string + then: + - logger.log: Something happened + - service: array_types + variables: + bool_arr: bool[] + int_arr: int[] + float_arr: float[] + string_arr: string[] + then: + - logger.log: + # yamllint disable rule:line-length + format: "Bool: %s (%u), Int: %d (%u), Float: %f (%u), String: %s (%u)" + # yamllint enable rule:line-length + args: + - YESNO(bool_arr[0]) + - bool_arr.size() + - int_arr[0] + - int_arr.size() + - float_arr[0] + - float_arr.size() + - string_arr[0].c_str() + - string_arr.size() diff --git a/tests/components/api/test.esp32-c3-idf.yaml b/tests/components/api/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/api/test.esp32-c3.yaml b/tests/components/api/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/api/test.esp32-idf.yaml b/tests/components/api/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/api/test.esp32.yaml b/tests/components/api/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/api/test.esp8266.yaml b/tests/components/api/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/api/test.rp2040.yaml b/tests/components/api/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/api/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/as3935_i2c/test.esp32-c3-idf.yaml b/tests/components/as3935_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c72556dbac4b --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32-c3.yaml b/tests/components/as3935_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..c72556dbac4b --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32-idf.yaml b/tests/components/as3935_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..fad703bee571 --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 16 + sda: 17 + +as3935_i2c: + irq_pin: 12 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp32.yaml b/tests/components/as3935_i2c/test.esp32.yaml new file mode 100644 index 000000000000..fad703bee571 --- /dev/null +++ b/tests/components/as3935_i2c/test.esp32.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 16 + sda: 17 + +as3935_i2c: + irq_pin: 12 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.esp8266.yaml b/tests/components/as3935_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..adba9e440f97 --- /dev/null +++ b/tests/components/as3935_i2c/test.esp8266.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 15 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_i2c/test.rp2040.yaml b/tests/components/as3935_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..c72556dbac4b --- /dev/null +++ b/tests/components/as3935_i2c/test.rp2040.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_as3935 + scl: 5 + sda: 4 + +as3935_i2c: + irq_pin: 6 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-c3-idf.yaml b/tests/components/as3935_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7a4a01aeeadd --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +as3935_spi: + cs_pin: 2 + irq_pin: 3 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-c3.yaml b/tests/components/as3935_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..7a4a01aeeadd --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +as3935_spi: + cs_pin: 2 + irq_pin: 3 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32-idf.yaml b/tests/components/as3935_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..813a39cb236c --- /dev/null +++ b/tests/components/as3935_spi/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +as3935_spi: + cs_pin: 12 + irq_pin: 13 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp32.yaml b/tests/components/as3935_spi/test.esp32.yaml new file mode 100644 index 000000000000..813a39cb236c --- /dev/null +++ b/tests/components/as3935_spi/test.esp32.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +as3935_spi: + cs_pin: 12 + irq_pin: 13 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.esp8266.yaml b/tests/components/as3935_spi/test.esp8266.yaml new file mode 100644 index 000000000000..38a40b083358 --- /dev/null +++ b/tests/components/as3935_spi/test.esp8266.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +as3935_spi: + cs_pin: 15 + irq_pin: 16 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as3935_spi/test.rp2040.yaml b/tests/components/as3935_spi/test.rp2040.yaml new file mode 100644 index 000000000000..528759d97d2e --- /dev/null +++ b/tests/components/as3935_spi/test.rp2040.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_as3935 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +as3935_spi: + cs_pin: 6 + irq_pin: 7 + +binary_sensor: + - platform: as3935 + name: Storm Alert + +sensor: + - platform: as3935 + lightning_energy: + name: Lightning Energy + distance: + name: Distance Storm diff --git a/tests/components/as5600/test.esp32-c3-idf.yaml b/tests/components/as5600/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e074fa5e0ca9 --- /dev/null +++ b/tests/components/as5600/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32-c3.yaml b/tests/components/as5600/test.esp32-c3.yaml new file mode 100644 index 000000000000..e074fa5e0ca9 --- /dev/null +++ b/tests/components/as5600/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32-idf.yaml b/tests/components/as5600/test.esp32-idf.yaml new file mode 100644 index 000000000000..312ee9ad04e9 --- /dev/null +++ b/tests/components/as5600/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +as5600: + dir_pin: 12 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp32.yaml b/tests/components/as5600/test.esp32.yaml new file mode 100644 index 000000000000..312ee9ad04e9 --- /dev/null +++ b/tests/components/as5600/test.esp32.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +as5600: + dir_pin: 12 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.esp8266.yaml b/tests/components/as5600/test.esp8266.yaml new file mode 100644 index 000000000000..a232d27305ba --- /dev/null +++ b/tests/components/as5600/test.esp8266.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 15 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as5600/test.rp2040.yaml b/tests/components/as5600/test.rp2040.yaml new file mode 100644 index 000000000000..e074fa5e0ca9 --- /dev/null +++ b/tests/components/as5600/test.rp2040.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +as5600: + dir_pin: 6 + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 + +sensor: + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status diff --git a/tests/components/as7341/test.esp32-c3-idf.yaml b/tests/components/as7341/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..19965d1715fa --- /dev/null +++ b/tests/components/as7341/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32-c3.yaml b/tests/components/as7341/test.esp32-c3.yaml new file mode 100644 index 000000000000..19965d1715fa --- /dev/null +++ b/tests/components/as7341/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32-idf.yaml b/tests/components/as7341/test.esp32-idf.yaml new file mode 100644 index 000000000000..d582a367ac9b --- /dev/null +++ b/tests/components/as7341/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp32.yaml b/tests/components/as7341/test.esp32.yaml new file mode 100644 index 000000000000..d582a367ac9b --- /dev/null +++ b/tests/components/as7341/test.esp32.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 16 + sda: 17 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.esp8266.yaml b/tests/components/as7341/test.esp8266.yaml new file mode 100644 index 000000000000..19965d1715fa --- /dev/null +++ b/tests/components/as7341/test.esp8266.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/as7341/test.rp2040.yaml b/tests/components/as7341/test.rp2040.yaml new file mode 100644 index 000000000000..19965d1715fa --- /dev/null +++ b/tests/components/as7341/test.rp2040.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_as5600 + scl: 5 + sda: 4 + +sensor: + - platform: as7341 + update_interval: 15s + gain: X8 + atime: 120 + astep: 99 + f1: + name: F1 + f2: + name: F2 + f3: + name: F3 + f4: + name: F4 + f5: + name: F5 + f6: + name: F6 + f7: + name: F7 + f8: + name: F8 + clear: + name: Clear + nir: + name: NIR diff --git a/tests/components/at581x/test.esp32-c3-idf.yaml b/tests/components/at581x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b49a283eca1e --- /dev/null +++ b/tests/components/at581x/test.esp32-c3-idf.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32-c3.yaml b/tests/components/at581x/test.esp32-c3.yaml new file mode 100644 index 000000000000..b49a283eca1e --- /dev/null +++ b/tests/components/at581x/test.esp32-c3.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32-idf.yaml b/tests/components/at581x/test.esp32-idf.yaml new file mode 100644 index 000000000000..ff84e61e1e1d --- /dev/null +++ b/tests/components/at581x/test.esp32-idf.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp32.yaml b/tests/components/at581x/test.esp32.yaml new file mode 100644 index 000000000000..ff84e61e1e1d --- /dev/null +++ b/tests/components/at581x/test.esp32.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.esp8266.yaml b/tests/components/at581x/test.esp8266.yaml new file mode 100644 index 000000000000..a7b006904542 --- /dev/null +++ b/tests/components/at581x/test.esp8266.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 14 + scl: 15 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO4 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/at581x/test.rp2040.yaml b/tests/components/at581x/test.rp2040.yaml new file mode 100644 index 000000000000..b49a283eca1e --- /dev/null +++ b/tests/components/at581x/test.rp2040.yaml @@ -0,0 +1,38 @@ +esphome: + on_boot: + then: + - at581x.settings: + id: "Waveradar" + hw_frontend_reset: false + frequency: 5800MHz + sensing_distance: 200 + poweron_selfcheck_time: 2s + protect_time: 1s + trigger_base: 500ms + trigger_keep: 10s + stage_gain: 3 + power_consumption: 70uA + - at581x.reset: + id: "Waveradar" + +at581x: + id: "Waveradar" + i2c_id: i2c_bus + +i2c: + sda: 8 + scl: 9 + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +binary_sensor: + - platform: gpio + pin: GPIO21 + name: "Radar motion" + +switch: + - platform: at581x + at581x_id: "Waveradar" + name: "Enable Radar" diff --git a/tests/components/atc_mithermometer/common.yaml b/tests/components/atc_mithermometer/common.yaml new file mode 100644 index 000000000000..0248090c23e3 --- /dev/null +++ b/tests/components/atc_mithermometer/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: atc_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: ATC Temperature + humidity: + name: ATC Humidity + battery_level: + name: ATC Battery-Level + battery_voltage: + name: ATC Battery-Voltage diff --git a/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml b/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/atc_mithermometer/test.esp32-c3.yaml b/tests/components/atc_mithermometer/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/atc_mithermometer/test.esp32-idf.yaml b/tests/components/atc_mithermometer/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/atc_mithermometer/test.esp32.yaml b/tests/components/atc_mithermometer/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/atc_mithermometer/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/atm90e26/test.esp32-c3-idf.yaml b/tests/components/atm90e26/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ce123bcf72ec --- /dev/null +++ b/tests/components/atm90e26/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e26 + cs_pin: 8 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32-c3.yaml b/tests/components/atm90e26/test.esp32-c3.yaml new file mode 100644 index 000000000000..ce123bcf72ec --- /dev/null +++ b/tests/components/atm90e26/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e26 + cs_pin: 8 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32-idf.yaml b/tests/components/atm90e26/test.esp32-idf.yaml new file mode 100644 index 000000000000..72fb3e5b24c8 --- /dev/null +++ b/tests/components/atm90e26/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e26 + cs_pin: 13 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp32.yaml b/tests/components/atm90e26/test.esp32.yaml new file mode 100644 index 000000000000..72fb3e5b24c8 --- /dev/null +++ b/tests/components/atm90e26/test.esp32.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e26 + cs_pin: 13 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.esp8266.yaml b/tests/components/atm90e26/test.esp8266.yaml new file mode 100644 index 000000000000..68d63cc27846 --- /dev/null +++ b/tests/components/atm90e26/test.esp8266.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: atm90e26 + cs_pin: 5 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e26/test.rp2040.yaml b/tests/components/atm90e26/test.rp2040.yaml new file mode 100644 index 000000000000..f43277dbb1a1 --- /dev/null +++ b/tests/components/atm90e26/test.rp2040.yaml @@ -0,0 +1,26 @@ +spi: + - id: spi_atm90e26 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: atm90e26 + cs_pin: 5 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 diff --git a/tests/components/atm90e32/test.esp32-c3-idf.yaml b/tests/components/atm90e32/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..263fb6d24e42 --- /dev/null +++ b/tests/components/atm90e32/test.esp32-c3-idf.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e32 + cs_pin: 8 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32-c3.yaml b/tests/components/atm90e32/test.esp32-c3.yaml new file mode 100644 index 000000000000..263fb6d24e42 --- /dev/null +++ b/tests/components/atm90e32/test.esp32-c3.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: atm90e32 + cs_pin: 8 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32-idf.yaml b/tests/components/atm90e32/test.esp32-idf.yaml new file mode 100644 index 000000000000..131270f8add2 --- /dev/null +++ b/tests/components/atm90e32/test.esp32-idf.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e32 + cs_pin: 13 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp32.yaml b/tests/components/atm90e32/test.esp32.yaml new file mode 100644 index 000000000000..131270f8add2 --- /dev/null +++ b/tests/components/atm90e32/test.esp32.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: atm90e32 + cs_pin: 13 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.esp8266.yaml b/tests/components/atm90e32/test.esp8266.yaml new file mode 100644 index 000000000000..e8e2abc1a98c --- /dev/null +++ b/tests/components/atm90e32/test.esp8266.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: atm90e32 + cs_pin: 5 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/atm90e32/test.rp2040.yaml b/tests/components/atm90e32/test.rp2040.yaml new file mode 100644 index 000000000000..525e0b801acb --- /dev/null +++ b/tests/components/atm90e32/test.rp2040.yaml @@ -0,0 +1,51 @@ +spi: + - id: spi_atm90e32 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: atm90e32 + cs_pin: 5 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_b: + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + current: + name: EMON CT3 Current + power: + name: EMON Active Power CT3 + reactive_power: + name: EMON Reactive Power CT3 + power_factor: + name: EMON Power Factor CT3 + gain_voltage: 7305 + gain_ct: 27961 + frequency: + name: EMON Line Frequency + chip_temperature: + name: EMON Chip Temp A + line_frequency: 60Hz + current_phases: 3 + gain_pga: 2X diff --git a/tests/components/b_parasite/common.yaml b/tests/components/b_parasite/common.yaml new file mode 100644 index 000000000000..262e891bb215 --- /dev/null +++ b/tests/components/b_parasite/common.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: b_parasite + mac_address: F0:CA:F0:CA:01:01 + humidity: + name: b-parasite Air Humidity + temperature: + name: b-parasite Air Temperature + moisture: + name: b-parasite Soil Moisture + battery_voltage: + name: b-parasite Battery Voltage + illuminance: + name: b-parasite Illuminance diff --git a/tests/components/b_parasite/test.esp32-c3-idf.yaml b/tests/components/b_parasite/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/b_parasite/test.esp32-c3.yaml b/tests/components/b_parasite/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/b_parasite/test.esp32-idf.yaml b/tests/components/b_parasite/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/b_parasite/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/b_parasite/test.esp32.yaml b/tests/components/b_parasite/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/b_parasite/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ballu/test.esp32.yaml b/tests/components/ballu/test.esp32.yaml new file mode 100644 index 000000000000..bb7b9b0435c1 --- /dev/null +++ b/tests/components/ballu/test.esp32.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: ballu + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/ballu/test.esp8266.yaml b/tests/components/ballu/test.esp8266.yaml new file mode 100644 index 000000000000..05aa446739d9 --- /dev/null +++ b/tests/components/ballu/test.esp8266.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: ballu + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/bang_bang/common.yaml b/tests/components/bang_bang/common.yaml new file mode 100644 index 000000000000..588202519171 --- /dev/null +++ b/tests/components/bang_bang/common.yaml @@ -0,0 +1,35 @@ +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: bang_bang + name: Bang Bang Climate + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + idle_action: + - switch.turn_on: template_switch1 + cool_action: + - switch.turn_on: template_switch2 + heat_action: + - switch.turn_on: template_switch1 + away_config: + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C diff --git a/tests/components/bang_bang/test.esp32-c3-idf.yaml b/tests/components/bang_bang/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bang_bang/test.esp32-c3.yaml b/tests/components/bang_bang/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bang_bang/test.esp32-idf.yaml b/tests/components/bang_bang/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bang_bang/test.esp32.yaml b/tests/components/bang_bang/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bang_bang/test.esp8266.yaml b/tests/components/bang_bang/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bang_bang/test.rp2040.yaml b/tests/components/bang_bang/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bang_bang/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bedjet/common.yaml b/tests/components/bedjet/common.yaml new file mode 100644 index 000000000000..c2be04a49afa --- /dev/null +++ b/tests/components/bedjet/common.yaml @@ -0,0 +1,33 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: bedjet_blec + +bedjet: + - id: bedjet_hub + ble_client_id: bedjet_blec + time_id: sntp_time + +climate: + - platform: bedjet + name: My Bedjet + bedjet_id: bedjet_hub + heat_mode: extended + +fan: + - platform: bedjet + name: My Bedjet fan + bedjet_id: bedjet_hub diff --git a/tests/components/bedjet/test.esp32-c3-idf.yaml b/tests/components/bedjet/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bedjet/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bedjet/test.esp32-c3.yaml b/tests/components/bedjet/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bedjet/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bedjet/test.esp32-idf.yaml b/tests/components/bedjet/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bedjet/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bedjet/test.esp32.yaml b/tests/components/bedjet/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/bedjet/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bh1750/test.esp32-c3-idf.yaml b/tests/components/bh1750/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e367de384595 --- /dev/null +++ b/tests/components/bh1750/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32-c3.yaml b/tests/components/bh1750/test.esp32-c3.yaml new file mode 100644 index 000000000000..e367de384595 --- /dev/null +++ b/tests/components/bh1750/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32-idf.yaml b/tests/components/bh1750/test.esp32-idf.yaml new file mode 100644 index 000000000000..b10ec231aedf --- /dev/null +++ b/tests/components/bh1750/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 16 + sda: 17 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp32.yaml b/tests/components/bh1750/test.esp32.yaml new file mode 100644 index 000000000000..b10ec231aedf --- /dev/null +++ b/tests/components/bh1750/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 16 + sda: 17 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.esp8266.yaml b/tests/components/bh1750/test.esp8266.yaml new file mode 100644 index 000000000000..e367de384595 --- /dev/null +++ b/tests/components/bh1750/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/bh1750/test.rp2040.yaml b/tests/components/bh1750/test.rp2040.yaml new file mode 100644 index 000000000000..e367de384595 --- /dev/null +++ b/tests/components/bh1750/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_bh1750 + scl: 5 + sda: 4 + +sensor: + - platform: bh1750 + name: Living Room Brightness + address: 0x23 + update_interval: 30s diff --git a/tests/components/binary_sensor_map/common.yaml b/tests/components/binary_sensor_map/common.yaml new file mode 100644 index 000000000000..8ffdd1f379fb --- /dev/null +++ b/tests/components/binary_sensor_map/common.yaml @@ -0,0 +1,61 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + - platform: template + id: bin2 + lambda: |- + if (millis() > 20000) { + return true; + } else { + return false; + } + - platform: template + id: bin3 + lambda: |- + if (millis() > 30000) { + return true; + } else { + return false; + } + +sensor: + - platform: binary_sensor_map + name: Binary Sensor Map + type: group + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 diff --git a/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml b/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.esp32-c3.yaml b/tests/components/binary_sensor_map/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.esp32-idf.yaml b/tests/components/binary_sensor_map/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.esp32.yaml b/tests/components/binary_sensor_map/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.esp8266.yaml b/tests/components/binary_sensor_map/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/binary_sensor_map/test.rp2040.yaml b/tests/components/binary_sensor_map/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/binary_sensor_map/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bl0939/test.esp32-c3-idf.yaml b/tests/components/bl0939/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4c92ccb7dd35 --- /dev/null +++ b/tests/components/bl0939/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32-c3.yaml b/tests/components/bl0939/test.esp32-c3.yaml new file mode 100644 index 000000000000..4c92ccb7dd35 --- /dev/null +++ b/tests/components/bl0939/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32-idf.yaml b/tests/components/bl0939/test.esp32-idf.yaml new file mode 100644 index 000000000000..df0e683b2f9b --- /dev/null +++ b/tests/components/bl0939/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp32.yaml b/tests/components/bl0939/test.esp32.yaml new file mode 100644 index 000000000000..df0e683b2f9b --- /dev/null +++ b/tests/components/bl0939/test.esp32.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.esp8266.yaml b/tests/components/bl0939/test.esp8266.yaml new file mode 100644 index 000000000000..4c92ccb7dd35 --- /dev/null +++ b/tests/components/bl0939/test.esp8266.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0939/test.rp2040.yaml b/tests/components/bl0939/test.rp2040.yaml new file mode 100644 index 000000000000..4c92ccb7dd35 --- /dev/null +++ b/tests/components/bl0939/test.rp2040.yaml @@ -0,0 +1,26 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0939 + voltage: + name: BL0939 Voltage + current_1: + name: BL0939 Current 1 + current_2: + name: BL0939 Current 2 + active_power_1: + name: BL0939 Active Power 1 + active_power_2: + name: BL0939 Active Power 2 + energy_1: + name: BL0939 Energy 1 + energy_2: + name: BL0939 Energy 2 + energy_total: + name: BL0939 Total energy diff --git a/tests/components/bl0940/test.esp32-c3-idf.yaml b/tests/components/bl0940/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a20f785b02d8 --- /dev/null +++ b/tests/components/bl0940/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32-c3.yaml b/tests/components/bl0940/test.esp32-c3.yaml new file mode 100644 index 000000000000..a20f785b02d8 --- /dev/null +++ b/tests/components/bl0940/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32-idf.yaml b/tests/components/bl0940/test.esp32-idf.yaml new file mode 100644 index 000000000000..c7d97ca3b9f8 --- /dev/null +++ b/tests/components/bl0940/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp32.yaml b/tests/components/bl0940/test.esp32.yaml new file mode 100644 index 000000000000..c7d97ca3b9f8 --- /dev/null +++ b/tests/components/bl0940/test.esp32.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.esp8266.yaml b/tests/components/bl0940/test.esp8266.yaml new file mode 100644 index 000000000000..a20f785b02d8 --- /dev/null +++ b/tests/components/bl0940/test.esp8266.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0940/test.rp2040.yaml b/tests/components/bl0940/test.rp2040.yaml new file mode 100644 index 000000000000..a20f785b02d8 --- /dev/null +++ b/tests/components/bl0940/test.rp2040.yaml @@ -0,0 +1,22 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0940 + voltage: + name: BL0940 Voltage + current: + name: BL0940 Current + power: + name: BL0940 Power + energy: + name: BL0940 Energy + internal_temperature: + name: BL0940 Internal temperature + external_temperature: + name: BL0940 External temperature diff --git a/tests/components/bl0942/test.esp32-c3-idf.yaml b/tests/components/bl0942/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8d16efed4f1e --- /dev/null +++ b/tests/components/bl0942/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32-c3.yaml b/tests/components/bl0942/test.esp32-c3.yaml new file mode 100644 index 000000000000..8d16efed4f1e --- /dev/null +++ b/tests/components/bl0942/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32-idf.yaml b/tests/components/bl0942/test.esp32-idf.yaml new file mode 100644 index 000000000000..45ac85aa2a13 --- /dev/null +++ b/tests/components/bl0942/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp32.yaml b/tests/components/bl0942/test.esp32.yaml new file mode 100644 index 000000000000..45ac85aa2a13 --- /dev/null +++ b/tests/components/bl0942/test.esp32.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.esp8266.yaml b/tests/components/bl0942/test.esp8266.yaml new file mode 100644 index 000000000000..8d16efed4f1e --- /dev/null +++ b/tests/components/bl0942/test.esp8266.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/bl0942/test.rp2040.yaml b/tests/components/bl0942/test.rp2040.yaml new file mode 100644 index 000000000000..8d16efed4f1e --- /dev/null +++ b/tests/components/bl0942/test.rp2040.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_bl0939 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: bl0942 + voltage: + name: BL0942 Voltage + current: + name: BL0942 Current + power: + name: BL0942 Power + energy: + name: BL0942 Energy + frequency: + name: BL0942 Frequency diff --git a/tests/components/ble_client/common.yaml b/tests/components/ble_client/common.yaml new file mode 100644 index 000000000000..b5272d01f0fa --- /dev/null +++ b/tests/components/ble_client/common.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: test_blec diff --git a/tests/components/ble_client/test.esp32-c3-idf.yaml b/tests/components/ble_client/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_client/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_client/test.esp32-c3.yaml b/tests/components/ble_client/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_client/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_client/test.esp32-idf.yaml b/tests/components/ble_client/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_client/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_client/test.esp32.yaml b/tests/components/ble_client/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_client/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_presence/common.yaml b/tests/components/ble_presence/common.yaml new file mode 100644 index 000000000000..6e5173eed848 --- /dev/null +++ b/tests/components/ble_presence/common.yaml @@ -0,0 +1,24 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: ble_presence + mac_address: AC:37:43:77:5F:4C + name: ESP32 BLE Tracker Google Home Mini + - platform: ble_presence + service_uuid: 11aa + name: BLE Test Service 16 Presence + - platform: ble_presence + service_uuid: "11223344" + name: BLE Test Service 32 Presence + - platform: ble_presence + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence + - platform: ble_presence + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + ibeacon_major: 100 + ibeacon_minor: 1 + name: BLE Test iBeacon Presence + - platform: ble_presence + irk: 1234567890abcdef1234567890abcdef + name: "ESP32 BLE Tracker with Identity Resolving Key" + diff --git a/tests/components/ble_presence/test.esp32-c3-idf.yaml b/tests/components/ble_presence/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_presence/test.esp32-c3.yaml b/tests/components/ble_presence/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_presence/test.esp32-idf.yaml b/tests/components/ble_presence/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_presence/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_presence/test.esp32.yaml b/tests/components/ble_presence/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_presence/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_rssi/common.yaml b/tests/components/ble_rssi/common.yaml new file mode 100644 index 000000000000..43bed1d0e782 --- /dev/null +++ b/tests/components/ble_rssi/common.yaml @@ -0,0 +1,21 @@ +esp32_ble_tracker: + +sensor: + - platform: ble_rssi + mac_address: AC:37:43:77:5F:4C + name: BLE Google Home Mini RSSI value + - platform: ble_rssi + service_uuid: 11aa + name: BLE Test Service 16 + - platform: ble_rssi + service_uuid: "11223344" + name: BLE Test Service 32 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 + - platform: ble_rssi + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID + - platform: ble_rssi + irk: 1234567890abcdef1234567890abcdef + name: "BLE Tracker with Identity Resolving Key" diff --git a/tests/components/ble_rssi/test.esp32-c3-idf.yaml b/tests/components/ble_rssi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_rssi/test.esp32-c3.yaml b/tests/components/ble_rssi/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_rssi/test.esp32-idf.yaml b/tests/components/ble_rssi/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_rssi/test.esp32.yaml b/tests/components/ble_rssi/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_rssi/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_scanner/common.yaml b/tests/components/ble_scanner/common.yaml new file mode 100644 index 000000000000..935a5a5a19e7 --- /dev/null +++ b/tests/components/ble_scanner/common.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +text_sensor: + - platform: ble_scanner + name: Scanner diff --git a/tests/components/ble_scanner/test.esp32-c3-idf.yaml b/tests/components/ble_scanner/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_scanner/test.esp32-c3.yaml b/tests/components/ble_scanner/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_scanner/test.esp32-idf.yaml b/tests/components/ble_scanner/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ble_scanner/test.esp32.yaml b/tests/components/ble_scanner/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ble_scanner/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/common.yaml b/tests/components/bme280_i2c/common.yaml new file mode 100644 index 000000000000..e74ce9bf6d44 --- /dev/null +++ b/tests/components/bme280_i2c/common.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_bme280 + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: bme280_i2c + i2c_id: i2c_bme280 + address: 0x76 + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_i2c/test.esp32-c3-idf.yaml b/tests/components/bme280_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.esp32-c3.yaml b/tests/components/bme280_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.esp32-idf.yaml b/tests/components/bme280_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..63c3bd6afd22 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.esp32.yaml b/tests/components/bme280_i2c/test.esp32.yaml new file mode 100644 index 000000000000..63c3bd6afd22 --- /dev/null +++ b/tests/components/bme280_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.esp8266.yaml b/tests/components/bme280_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bme280_i2c/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bme280_i2c/test.rp2040.yaml b/tests/components/bme280_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bme280_i2c/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/common.yaml b/tests/components/bme280_spi/common.yaml new file mode 100644 index 000000000000..303ecf9f73b8 --- /dev/null +++ b/tests/components/bme280_spi/common.yaml @@ -0,0 +1,20 @@ +spi: + - id: spi_bme280 + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +sensor: + - platform: bme280_spi + spi_id: spi_bme280 + cs_pin: ${cs_pin} + temperature: + id: bme280_temperature + name: BME280 Temperature + humidity: + id: bme280_humidity + name: BME280 Humidity + pressure: + id: bme280_pressure + name: BME280 Pressure + update_interval: 15s diff --git a/tests/components/bme280_spi/test.esp32-c3-idf.yaml b/tests/components/bme280_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2415ba5dc62f --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.esp32-c3.yaml b/tests/components/bme280_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..2415ba5dc62f --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.esp32-idf.yaml b/tests/components/bme280_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..54e027a61433 --- /dev/null +++ b/tests/components/bme280_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.esp32.yaml b/tests/components/bme280_spi/test.esp32.yaml new file mode 100644 index 000000000000..54e027a61433 --- /dev/null +++ b/tests/components/bme280_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.esp8266.yaml b/tests/components/bme280_spi/test.esp8266.yaml new file mode 100644 index 000000000000..dbd158d030a6 --- /dev/null +++ b/tests/components/bme280_spi/test.esp8266.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO14 + mosi_pin: GPIO13 + miso_pin: GPIO12 + cs_pin: GPIO15 + +<<: !include common.yaml diff --git a/tests/components/bme280_spi/test.rp2040.yaml b/tests/components/bme280_spi/test.rp2040.yaml new file mode 100644 index 000000000000..f6c3f1eeca9e --- /dev/null +++ b/tests/components/bme280_spi/test.rp2040.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO2 + mosi_pin: GPIO3 + miso_pin: GPIO4 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bme680/test.esp32-c3-idf.yaml b/tests/components/bme680/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f12be09d200f --- /dev/null +++ b/tests/components/bme680/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32-c3.yaml b/tests/components/bme680/test.esp32-c3.yaml new file mode 100644 index 000000000000..f12be09d200f --- /dev/null +++ b/tests/components/bme680/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32-idf.yaml b/tests/components/bme680/test.esp32-idf.yaml new file mode 100644 index 000000000000..04d0ed8fe4b2 --- /dev/null +++ b/tests/components/bme680/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp32.yaml b/tests/components/bme680/test.esp32.yaml new file mode 100644 index 000000000000..04d0ed8fe4b2 --- /dev/null +++ b/tests/components/bme680/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.esp8266.yaml b/tests/components/bme680/test.esp8266.yaml new file mode 100644 index 000000000000..f12be09d200f --- /dev/null +++ b/tests/components/bme680/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680/test.rp2040.yaml b/tests/components/bme680/test.rp2040.yaml new file mode 100644 index 000000000000..f12be09d200f --- /dev/null +++ b/tests/components/bme680/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +sensor: + - platform: bme680 + temperature: + name: BME680 Temperature + oversampling: 16x + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + address: 0x77 + heater: + temperature: 320 + duration: 150ms + update_interval: 15s diff --git a/tests/components/bme680_bsec/test.esp32.yaml b/tests/components/bme680_bsec/test.esp32.yaml new file mode 100644 index 000000000000..4f62f13abb49 --- /dev/null +++ b/tests/components/bme680_bsec/test.esp32.yaml @@ -0,0 +1,29 @@ +i2c: + - id: i2c_bme680 + scl: 16 + sda: 17 + +bme680_bsec: + address: 0x77 + +sensor: + - platform: bme680_bsec + temperature: + name: BME680 Temperature + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + iaq: + name: BME680 IAQ + co2_equivalent: + name: BME680 eCO2 + breath_voc_equivalent: + name: BME680 Breath eVOC + +text_sensor: + - platform: bme680_bsec + iaq_accuracy: + name: BME680 Accuracy diff --git a/tests/components/bme680_bsec/test.esp8266.yaml b/tests/components/bme680_bsec/test.esp8266.yaml new file mode 100644 index 000000000000..84b32d363554 --- /dev/null +++ b/tests/components/bme680_bsec/test.esp8266.yaml @@ -0,0 +1,29 @@ +i2c: + - id: i2c_bme680 + scl: 5 + sda: 4 + +bme680_bsec: + address: 0x77 + +sensor: + - platform: bme680_bsec + temperature: + name: BME680 Temperature + pressure: + name: BME680 Pressure + humidity: + name: BME680 Humidity + gas_resistance: + name: BME680 Gas Sensor + iaq: + name: BME680 IAQ + co2_equivalent: + name: BME680 eCO2 + breath_voc_equivalent: + name: BME680 Breath eVOC + +text_sensor: + - platform: bme680_bsec + iaq_accuracy: + name: BME680 Accuracy diff --git a/tests/components/bmi160/test.esp32-c3-idf.yaml b/tests/components/bmi160/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3fd644198024 --- /dev/null +++ b/tests/components/bmi160/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32-c3.yaml b/tests/components/bmi160/test.esp32-c3.yaml new file mode 100644 index 000000000000..3fd644198024 --- /dev/null +++ b/tests/components/bmi160/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32-idf.yaml b/tests/components/bmi160/test.esp32-idf.yaml new file mode 100644 index 000000000000..a8a90c8c8744 --- /dev/null +++ b/tests/components/bmi160/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 16 + sda: 17 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp32.yaml b/tests/components/bmi160/test.esp32.yaml new file mode 100644 index 000000000000..a8a90c8c8744 --- /dev/null +++ b/tests/components/bmi160/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 16 + sda: 17 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.esp8266.yaml b/tests/components/bmi160/test.esp8266.yaml new file mode 100644 index 000000000000..3fd644198024 --- /dev/null +++ b/tests/components/bmi160/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmi160/test.rp2040.yaml b/tests/components/bmi160/test.rp2040.yaml new file mode 100644 index 000000000000..3fd644198024 --- /dev/null +++ b/tests/components/bmi160/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_bmi160 + scl: 5 + sda: 4 + +sensor: + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature diff --git a/tests/components/bmp085/test.esp32-c3-idf.yaml b/tests/components/bmp085/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..76a9fd07ba3b --- /dev/null +++ b/tests/components/bmp085/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32-c3.yaml b/tests/components/bmp085/test.esp32-c3.yaml new file mode 100644 index 000000000000..76a9fd07ba3b --- /dev/null +++ b/tests/components/bmp085/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32-idf.yaml b/tests/components/bmp085/test.esp32-idf.yaml new file mode 100644 index 000000000000..8a4f714ddd71 --- /dev/null +++ b/tests/components/bmp085/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 16 + sda: 17 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp32.yaml b/tests/components/bmp085/test.esp32.yaml new file mode 100644 index 000000000000..8a4f714ddd71 --- /dev/null +++ b/tests/components/bmp085/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 16 + sda: 17 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.esp8266.yaml b/tests/components/bmp085/test.esp8266.yaml new file mode 100644 index 000000000000..76a9fd07ba3b --- /dev/null +++ b/tests/components/bmp085/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp085/test.rp2040.yaml b/tests/components/bmp085/test.rp2040.yaml new file mode 100644 index 000000000000..76a9fd07ba3b --- /dev/null +++ b/tests/components/bmp085/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp085 + scl: 5 + sda: 4 + +sensor: + - platform: bmp085 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + filters: + - lambda: >- + return x / powf(1.0 - (x / 44330.0), 5.255); + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3-idf.yaml b/tests/components/bmp280/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5f7f85d3e2fb --- /dev/null +++ b/tests/components/bmp280/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3.yaml b/tests/components/bmp280/test.esp32-c3.yaml new file mode 100644 index 000000000000..5f7f85d3e2fb --- /dev/null +++ b/tests/components/bmp280/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-idf.yaml b/tests/components/bmp280/test.esp32-idf.yaml new file mode 100644 index 000000000000..aeb1cb262bdc --- /dev/null +++ b/tests/components/bmp280/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 16 + sda: 17 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp32.yaml b/tests/components/bmp280/test.esp32.yaml new file mode 100644 index 000000000000..aeb1cb262bdc --- /dev/null +++ b/tests/components/bmp280/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 16 + sda: 17 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.esp8266.yaml b/tests/components/bmp280/test.esp8266.yaml new file mode 100644 index 000000000000..5f7f85d3e2fb --- /dev/null +++ b/tests/components/bmp280/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280/test.rp2040.yaml b/tests/components/bmp280/test.rp2040.yaml new file mode 100644 index 000000000000..5f7f85d3e2fb --- /dev/null +++ b/tests/components/bmp280/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp280 + scl: 5 + sda: 4 + +sensor: + - platform: bmp280 + address: 0x77 + temperature: + name: Outside Temperature + oversampling: 16x + pressure: + name: Outside Pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp3xx_i2c/common.yaml b/tests/components/bmp3xx_i2c/common.yaml new file mode 100644 index 000000000000..6641b7a1b834 --- /dev/null +++ b/tests/components/bmp3xx_i2c/common.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_bmp3xx + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: bmp3xx_i2c + i2c_id: i2c_bmp3xx + address: 0x77 + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml b/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.esp32-c3.yaml b/tests/components/bmp3xx_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.esp32-idf.yaml b/tests/components/bmp3xx_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..63c3bd6afd22 --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.esp32.yaml b/tests/components/bmp3xx_i2c/test.esp32.yaml new file mode 100644 index 000000000000..63c3bd6afd22 --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.esp8266.yaml b/tests/components/bmp3xx_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_i2c/test.rp2040.yaml b/tests/components/bmp3xx_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..ee2c29ca4ecd --- /dev/null +++ b/tests/components/bmp3xx_i2c/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/common.yaml b/tests/components/bmp3xx_spi/common.yaml new file mode 100644 index 000000000000..8d5f897661de --- /dev/null +++ b/tests/components/bmp3xx_spi/common.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_bmp3xx + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +sensor: + - platform: bmp3xx_spi + spi_id: spi_bmp3xx + cs_pin: ${cs_pin} + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + iir_filter: 2X diff --git a/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml b/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2415ba5dc62f --- /dev/null +++ b/tests/components/bmp3xx_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.esp32-c3.yaml b/tests/components/bmp3xx_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..2415ba5dc62f --- /dev/null +++ b/tests/components/bmp3xx_spi/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.esp32-idf.yaml b/tests/components/bmp3xx_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..54e027a61433 --- /dev/null +++ b/tests/components/bmp3xx_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.esp32.yaml b/tests/components/bmp3xx_spi/test.esp32.yaml new file mode 100644 index 000000000000..54e027a61433 --- /dev/null +++ b/tests/components/bmp3xx_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.esp8266.yaml b/tests/components/bmp3xx_spi/test.esp8266.yaml new file mode 100644 index 000000000000..dbd158d030a6 --- /dev/null +++ b/tests/components/bmp3xx_spi/test.esp8266.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO14 + mosi_pin: GPIO13 + miso_pin: GPIO12 + cs_pin: GPIO15 + +<<: !include common.yaml diff --git a/tests/components/bmp3xx_spi/test.rp2040.yaml b/tests/components/bmp3xx_spi/test.rp2040.yaml new file mode 100644 index 000000000000..f6c3f1eeca9e --- /dev/null +++ b/tests/components/bmp3xx_spi/test.rp2040.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO2 + mosi_pin: GPIO3 + miso_pin: GPIO4 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bmp581/test.esp32-c3-idf.yaml b/tests/components/bmp581/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..29d27afb9074 --- /dev/null +++ b/tests/components/bmp581/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32-c3.yaml b/tests/components/bmp581/test.esp32-c3.yaml new file mode 100644 index 000000000000..29d27afb9074 --- /dev/null +++ b/tests/components/bmp581/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32-idf.yaml b/tests/components/bmp581/test.esp32-idf.yaml new file mode 100644 index 000000000000..a464b8ce6a31 --- /dev/null +++ b/tests/components/bmp581/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 16 + sda: 17 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp32.yaml b/tests/components/bmp581/test.esp32.yaml new file mode 100644 index 000000000000..a464b8ce6a31 --- /dev/null +++ b/tests/components/bmp581/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 16 + sda: 17 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.esp8266.yaml b/tests/components/bmp581/test.esp8266.yaml new file mode 100644 index 000000000000..29d27afb9074 --- /dev/null +++ b/tests/components/bmp581/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bmp581/test.rp2040.yaml b/tests/components/bmp581/test.rp2040.yaml new file mode 100644 index 000000000000..29d27afb9074 --- /dev/null +++ b/tests/components/bmp581/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_bmp581 + scl: 5 + sda: 4 + +sensor: + - platform: bmp581 + temperature: + name: "BMP581 Temperature" + iir_filter: 2x + pressure: + name: "BMP581 Pressure" + oversampling: 128x diff --git a/tests/components/bp1658cj/test.esp32-c3-idf.yaml b/tests/components/bp1658cj/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..74d315537101 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32-c3.yaml b/tests/components/bp1658cj/test.esp32-c3.yaml new file mode 100644 index 000000000000..74d315537101 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32-idf.yaml b/tests/components/bp1658cj/test.esp32-idf.yaml new file mode 100644 index 000000000000..5f9e25d3bd72 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 16 + data_pin: 17 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp32.yaml b/tests/components/bp1658cj/test.esp32.yaml new file mode 100644 index 000000000000..5f9e25d3bd72 --- /dev/null +++ b/tests/components/bp1658cj/test.esp32.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 16 + data_pin: 17 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.esp8266.yaml b/tests/components/bp1658cj/test.esp8266.yaml new file mode 100644 index 000000000000..74d315537101 --- /dev/null +++ b/tests/components/bp1658cj/test.esp8266.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp1658cj/test.rp2040.yaml b/tests/components/bp1658cj/test.rp2040.yaml new file mode 100644 index 000000000000..74d315537101 --- /dev/null +++ b/tests/components/bp1658cj/test.rp2040.yaml @@ -0,0 +1,22 @@ +bp1658cj: + clock_pin: 5 + data_pin: 4 + max_power_color_channels: 4 + max_power_white_channels: 6 + +output: + - platform: bp1658cj + id: bp1658cj_red + channel: 1 + - platform: bp1658cj + id: bp1658cj_green + channel: 2 + - platform: bp1658cj + id: bp1658cj_blue + channel: 0 + - platform: bp1658cj + id: bp1658cj_coldwhite + channel: 3 + - platform: bp1658cj + id: bp1658cj_warmwhite + channel: 4 diff --git a/tests/components/bp5758d/test.esp32-c3-idf.yaml b/tests/components/bp5758d/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ec74e935cd4f --- /dev/null +++ b/tests/components/bp5758d/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32-c3.yaml b/tests/components/bp5758d/test.esp32-c3.yaml new file mode 100644 index 000000000000..ec74e935cd4f --- /dev/null +++ b/tests/components/bp5758d/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32-idf.yaml b/tests/components/bp5758d/test.esp32-idf.yaml new file mode 100644 index 000000000000..b7929a05187c --- /dev/null +++ b/tests/components/bp5758d/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 16 + data_pin: 17 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp32.yaml b/tests/components/bp5758d/test.esp32.yaml new file mode 100644 index 000000000000..b7929a05187c --- /dev/null +++ b/tests/components/bp5758d/test.esp32.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 16 + data_pin: 17 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.esp8266.yaml b/tests/components/bp5758d/test.esp8266.yaml new file mode 100644 index 000000000000..ec74e935cd4f --- /dev/null +++ b/tests/components/bp5758d/test.esp8266.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/bp5758d/test.rp2040.yaml b/tests/components/bp5758d/test.rp2040.yaml new file mode 100644 index 000000000000..ec74e935cd4f --- /dev/null +++ b/tests/components/bp5758d/test.rp2040.yaml @@ -0,0 +1,25 @@ +bp5758d: + clock_pin: 5 + data_pin: 4 + +output: + - platform: bp5758d + id: bp5758d_red + channel: 2 + current: 10 + - platform: bp5758d + id: bp5758d_green + channel: 3 + current: 10 + - platform: bp5758d + id: bp5758d_blue + channel: 1 + current: 10 + - platform: bp5758d + id: bp5758d_coldwhite + channel: 5 + current: 10 + - platform: bp5758d + id: bp5758d_warmwhite + channel: 4 + current: 10 diff --git a/tests/components/button/common.yaml b/tests/components/button/common.yaml new file mode 100644 index 000000000000..d5978601f43e --- /dev/null +++ b/tests/components/button/common.yaml @@ -0,0 +1,6 @@ +button: + - platform: template + name: Button + id: some_button + on_press: + - logger.log: Button pressed diff --git a/tests/components/button/test.esp32-c3-idf.yaml b/tests/components/button/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/button/test.esp32-c3.yaml b/tests/components/button/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/button/test.esp32-idf.yaml b/tests/components/button/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/button/test.esp32.yaml b/tests/components/button/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/button/test.esp8266.yaml b/tests/components/button/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/button/test.rp2040.yaml b/tests/components/button/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/button/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/canbus/common.yaml b/tests/components/canbus/common.yaml new file mode 100644 index 000000000000..fd146cc3a307 --- /dev/null +++ b/tests/components/canbus/common.yaml @@ -0,0 +1,46 @@ +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: Truth + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + } + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/components/canbus/test.esp32-c3-idf.yaml b/tests/components/canbus/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/canbus/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/canbus/test.esp32-c3.yaml b/tests/components/canbus/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/canbus/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/canbus/test.esp32-idf.yaml b/tests/components/canbus/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/canbus/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/canbus/test.esp32.yaml b/tests/components/canbus/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/canbus/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/cap1188/test.esp32-c3-idf.yaml b/tests/components/cap1188/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c6d3c959420c --- /dev/null +++ b/tests/components/cap1188/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32-c3.yaml b/tests/components/cap1188/test.esp32-c3.yaml new file mode 100644 index 000000000000..c6d3c959420c --- /dev/null +++ b/tests/components/cap1188/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32-idf.yaml b/tests/components/cap1188/test.esp32-idf.yaml new file mode 100644 index 000000000000..efd1d6021771 --- /dev/null +++ b/tests/components/cap1188/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 16 + sda: 17 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp32.yaml b/tests/components/cap1188/test.esp32.yaml new file mode 100644 index 000000000000..efd1d6021771 --- /dev/null +++ b/tests/components/cap1188/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 16 + sda: 17 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.esp8266.yaml b/tests/components/cap1188/test.esp8266.yaml new file mode 100644 index 000000000000..7573d451402e --- /dev/null +++ b/tests/components/cap1188/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 15 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/cap1188/test.rp2040.yaml b/tests/components/cap1188/test.rp2040.yaml new file mode 100644 index 000000000000..c6d3c959420c --- /dev/null +++ b/tests/components/cap1188/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_cap1188 + scl: 5 + sda: 4 + +cap1188: + id: cap1188_component + address: 0x29 + reset_pin: 6 + touch_threshold: 0x20 + allow_multiple_touches: true diff --git a/tests/components/captive_portal/common.yaml b/tests/components/captive_portal/common.yaml new file mode 100644 index 000000000000..25bc4a887a52 --- /dev/null +++ b/tests/components/captive_portal/common.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +captive_portal: diff --git a/tests/components/captive_portal/test.esp32-c3-idf.yaml b/tests/components/captive_portal/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/captive_portal/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/captive_portal/test.esp32-c3.yaml b/tests/components/captive_portal/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/captive_portal/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/captive_portal/test.esp32-idf.yaml b/tests/components/captive_portal/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/captive_portal/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/captive_portal/test.esp32.yaml b/tests/components/captive_portal/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/captive_portal/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/captive_portal/test.esp8266.yaml b/tests/components/captive_portal/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/captive_portal/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ccs811/test.esp32-c3-idf.yaml b/tests/components/ccs811/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..26ec7807e4b6 --- /dev/null +++ b/tests/components/ccs811/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32-c3.yaml b/tests/components/ccs811/test.esp32-c3.yaml new file mode 100644 index 000000000000..26ec7807e4b6 --- /dev/null +++ b/tests/components/ccs811/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32-idf.yaml b/tests/components/ccs811/test.esp32-idf.yaml new file mode 100644 index 000000000000..08b3a48cc78a --- /dev/null +++ b/tests/components/ccs811/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 16 + sda: 17 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp32.yaml b/tests/components/ccs811/test.esp32.yaml new file mode 100644 index 000000000000..08b3a48cc78a --- /dev/null +++ b/tests/components/ccs811/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 16 + sda: 17 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.esp8266.yaml b/tests/components/ccs811/test.esp8266.yaml new file mode 100644 index 000000000000..26ec7807e4b6 --- /dev/null +++ b/tests/components/ccs811/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/ccs811/test.rp2040.yaml b/tests/components/ccs811/test.rp2040.yaml new file mode 100644 index 000000000000..26ec7807e4b6 --- /dev/null +++ b/tests/components/ccs811/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ccs811 + scl: 5 + sda: 4 + +sensor: + - platform: ccs811 + eco2: + name: CCS811 eCO2 + tvoc: + name: CCS811 TVOC + baseline: 0x4242 + update_interval: 30s diff --git a/tests/components/cd74hc4067/test.esp32-c3-idf.yaml b/tests/components/cd74hc4067/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5aa653d26c3b --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32-c3.yaml b/tests/components/cd74hc4067/test.esp32-c3.yaml new file mode 100644 index 000000000000..5aa653d26c3b --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32-idf.yaml b/tests/components/cd74hc4067/test.esp32-idf.yaml new file mode 100644 index 000000000000..71a1238ccc4e --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp32.yaml b/tests/components/cd74hc4067/test.esp32.yaml new file mode 100644 index 000000000000..71a1238ccc4e --- /dev/null +++ b/tests/components/cd74hc4067/test.esp32.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.esp8266.yaml b/tests/components/cd74hc4067/test.esp8266.yaml new file mode 100644 index 000000000000..8bcce5bc1778 --- /dev/null +++ b/tests/components/cd74hc4067/test.esp8266.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 12 + pin_s1: 13 + pin_s2: 14 + pin_s3: 15 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: A0 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/cd74hc4067/test.rp2040.yaml b/tests/components/cd74hc4067/test.rp2040.yaml new file mode 100644 index 000000000000..75adcce79629 --- /dev/null +++ b/tests/components/cd74hc4067/test.rp2040.yaml @@ -0,0 +1,18 @@ +cd74hc4067: + pin_s0: 2 + pin_s1: 3 + pin_s2: 4 + pin_s3: 5 + +sensor: + - platform: adc + id: esp_adc_sensor + pin: 26 + - platform: cd74hc4067 + id: cd74hc4067_adc_0 + number: 0 + sensor: esp_adc_sensor + - platform: cd74hc4067 + id: cd74hc4067_adc_1 + number: 1 + sensor: esp_adc_sensor diff --git a/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml b/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e714bf0686cc --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32-c3.yaml b/tests/components/climate_ir_lg/test.esp32-c3.yaml new file mode 100644 index 000000000000..e714bf0686cc --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32-idf.yaml b/tests/components/climate_ir_lg/test.esp32-idf.yaml new file mode 100644 index 000000000000..e714bf0686cc --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp32.yaml b/tests/components/climate_ir_lg/test.esp32.yaml new file mode 100644 index 000000000000..e714bf0686cc --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/climate_ir_lg/test.esp8266.yaml b/tests/components/climate_ir_lg/test.esp8266.yaml new file mode 100644 index 000000000000..7482bf058064 --- /dev/null +++ b/tests/components/climate_ir_lg/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: climate_ir_lg + name: LG Climate diff --git a/tests/components/color/common.yaml b/tests/components/color/common.yaml new file mode 100644 index 000000000000..88524e6a5fdc --- /dev/null +++ b/tests/components/color/common.yaml @@ -0,0 +1,20 @@ +color: + - id: kbx_red + red: 100% + green_int: 123 + blue: 2% + - id: kbx_blue + red: 0% + green: 1% + blue: 100% + - id: kbx_green + hex: "3DEC55" + - id: kbx_green_1 + hex: 3DEC55 + - id: cps_red + hex: 800000 + - id: cps_green + hex: 008000 + - id: cps_blue + hex: 000080 + diff --git a/tests/components/color/test.esp32-c3-idf.yaml b/tests/components/color/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/color/test.esp32-c3.yaml b/tests/components/color/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/color/test.esp32-idf.yaml b/tests/components/color/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/color/test.esp32.yaml b/tests/components/color/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/color/test.esp8266.yaml b/tests/components/color/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/color/test.rp2040.yaml b/tests/components/color/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/color/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..8d3faa54ee0e --- /dev/null +++ b/tests/components/color_temperature/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32-c3.yaml b/tests/components/color_temperature/test.esp32-c3.yaml new file mode 100644 index 000000000000..8d3faa54ee0e --- /dev/null +++ b/tests/components/color_temperature/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32-idf.yaml b/tests/components/color_temperature/test.esp32-idf.yaml new file mode 100644 index 000000000000..608907d2fced --- /dev/null +++ b/tests/components/color_temperature/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp32.yaml b/tests/components/color_temperature/test.esp32.yaml new file mode 100644 index 000000000000..608907d2fced --- /dev/null +++ b/tests/components/color_temperature/test.esp32.yaml @@ -0,0 +1,15 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.esp8266.yaml b/tests/components/color_temperature/test.esp8266.yaml new file mode 100644 index 000000000000..ed0bfb6aa417 --- /dev/null +++ b/tests/components/color_temperature/test.esp8266.yaml @@ -0,0 +1,15 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/color_temperature/test.rp2040.yaml b/tests/components/color_temperature/test.rp2040.yaml new file mode 100644 index 000000000000..887ad1c8572a --- /dev/null +++ b/tests/components/color_temperature/test.rp2040.yaml @@ -0,0 +1,15 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + +light: + - platform: color_temperature + name: Lights + color_temperature: light_output_1 + brightness: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/combination/common.yaml b/tests/components/combination/common.yaml new file mode 100644 index 000000000000..62246190afda --- /dev/null +++ b/tests/components/combination/common.yaml @@ -0,0 +1,76 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: |- + if (millis() > 10000) { + return 0.6; + } else { + return 0.0; + } + - platform: template + id: template_temperature2 + lambda: |- + if (millis() > 20000) { + return 0.8; + } else { + return 0.0; + } + - platform: combination + type: kalman + name: Kalman-filtered temperature + process_std_dev: 0.00139 + sources: + - source: template_temperature1 + error: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;" + - source: template_temperature2 + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: template_temperature1 + - source: template_temperature2 diff --git a/tests/components/combination/test.esp32-c3-idf.yaml b/tests/components/combination/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/combination/test.esp32-c3.yaml b/tests/components/combination/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/combination/test.esp32-idf.yaml b/tests/components/combination/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/combination/test.esp32.yaml b/tests/components/combination/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/combination/test.esp8266.yaml b/tests/components/combination/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/combination/test.rp2040.yaml b/tests/components/combination/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/combination/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/coolix/test.esp32-c3-idf.yaml b/tests/components/coolix/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0f9518d2cf45 --- /dev/null +++ b/tests/components/coolix/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32-c3.yaml b/tests/components/coolix/test.esp32-c3.yaml new file mode 100644 index 000000000000..0f9518d2cf45 --- /dev/null +++ b/tests/components/coolix/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32-idf.yaml b/tests/components/coolix/test.esp32-idf.yaml new file mode 100644 index 000000000000..0f9518d2cf45 --- /dev/null +++ b/tests/components/coolix/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp32.yaml b/tests/components/coolix/test.esp32.yaml new file mode 100644 index 000000000000..0f9518d2cf45 --- /dev/null +++ b/tests/components/coolix/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/coolix/test.esp8266.yaml b/tests/components/coolix/test.esp8266.yaml new file mode 100644 index 000000000000..61de8c755804 --- /dev/null +++ b/tests/components/coolix/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: coolix + name: Coolix Climate diff --git a/tests/components/copy/test.esp32-c3-idf.yaml b/tests/components/copy/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..554638f462f7 --- /dev/null +++ b/tests/components/copy/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32-c3.yaml b/tests/components/copy/test.esp32-c3.yaml new file mode 100644 index 000000000000..554638f462f7 --- /dev/null +++ b/tests/components/copy/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32-idf.yaml b/tests/components/copy/test.esp32-idf.yaml new file mode 100644 index 000000000000..806dbfe9f381 --- /dev/null +++ b/tests/components/copy/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp32.yaml b/tests/components/copy/test.esp32.yaml new file mode 100644 index 000000000000..806dbfe9f381 --- /dev/null +++ b/tests/components/copy/test.esp32.yaml @@ -0,0 +1,23 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.esp8266.yaml b/tests/components/copy/test.esp8266.yaml new file mode 100644 index 000000000000..1521e5f279b6 --- /dev/null +++ b/tests/components/copy/test.esp8266.yaml @@ -0,0 +1,23 @@ +output: + - platform: esp8266_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/copy/test.rp2040.yaml b/tests/components/copy/test.rp2040.yaml new file mode 100644 index 000000000000..42e5eb800005 --- /dev/null +++ b/tests/components/copy/test.rp2040.yaml @@ -0,0 +1,23 @@ +output: + - platform: rp2040_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 + - platform: copy + source_id: fan_speed + name: Fan Speed Copy + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy diff --git a/tests/components/cs5460a/test.esp32-c3-idf.yaml b/tests/components/cs5460a/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4ce21783a3e4 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 8 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32-c3.yaml b/tests/components/cs5460a/test.esp32-c3.yaml new file mode 100644 index 000000000000..4ce21783a3e4 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 8 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32-idf.yaml b/tests/components/cs5460a/test.esp32-idf.yaml new file mode 100644 index 000000000000..e7eb1cbd7303 --- /dev/null +++ b/tests/components/cs5460a/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 12 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp32.yaml b/tests/components/cs5460a/test.esp32.yaml new file mode 100644 index 000000000000..e7eb1cbd7303 --- /dev/null +++ b/tests/components/cs5460a/test.esp32.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 12 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.esp8266.yaml b/tests/components/cs5460a/test.esp8266.yaml new file mode 100644 index 000000000000..c5a458d0ecd3 --- /dev/null +++ b/tests/components/cs5460a/test.esp8266.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 15 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cs5460a/test.rp2040.yaml b/tests/components/cs5460a/test.rp2040.yaml new file mode 100644 index 000000000000..f3daf7d72d29 --- /dev/null +++ b/tests/components/cs5460a/test.rp2040.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_cs5460a + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: cs5460a + id: cs5460a1 + cs_pin: 6 + current: + name: Socket current + voltage: + name: Mains voltage + power: + name: Socket power + on_value: + then: + cs5460a.restart: cs5460a1 + samples: 1600 + pga_gain: 10X + current_gain: 0.01 + voltage_gain: 0.000573 + current_hpf: true + voltage_hpf: true + phase_offset: 20 + pulse_energy: 0.01 kWh diff --git a/tests/components/cse7761/test.esp32-c3-idf.yaml b/tests/components/cse7761/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..581db24fd544 --- /dev/null +++ b/tests/components/cse7761/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32-c3.yaml b/tests/components/cse7761/test.esp32-c3.yaml new file mode 100644 index 000000000000..581db24fd544 --- /dev/null +++ b/tests/components/cse7761/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32-idf.yaml b/tests/components/cse7761/test.esp32-idf.yaml new file mode 100644 index 000000000000..4174e9a92e0a --- /dev/null +++ b/tests/components/cse7761/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp32.yaml b/tests/components/cse7761/test.esp32.yaml new file mode 100644 index 000000000000..4174e9a92e0a --- /dev/null +++ b/tests/components/cse7761/test.esp32.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.esp8266.yaml b/tests/components/cse7761/test.esp8266.yaml new file mode 100644 index 000000000000..581db24fd544 --- /dev/null +++ b/tests/components/cse7761/test.esp8266.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7761/test.rp2040.yaml b/tests/components/cse7761/test.rp2040.yaml new file mode 100644 index 000000000000..581db24fd544 --- /dev/null +++ b/tests/components/cse7761/test.rp2040.yaml @@ -0,0 +1,20 @@ +uart: + - id: uart_cse7761 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 38400 + +sensor: + - platform: cse7761 + voltage: + name: CSE7761 Voltage + current_1: + name: CSE7761 Current 1 + current_2: + name: CSE7761 Current 2 + active_power_1: + name: CSE7761 Active Power 1 + active_power_2: + name: CSE7761 Active Power 2 diff --git a/tests/components/cse7766/test.esp32-c3-idf.yaml b/tests/components/cse7766/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..432cc0a80ed6 --- /dev/null +++ b/tests/components/cse7766/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32-c3.yaml b/tests/components/cse7766/test.esp32-c3.yaml new file mode 100644 index 000000000000..432cc0a80ed6 --- /dev/null +++ b/tests/components/cse7766/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32-idf.yaml b/tests/components/cse7766/test.esp32-idf.yaml new file mode 100644 index 000000000000..f94cd0f7d8e7 --- /dev/null +++ b/tests/components/cse7766/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp32.yaml b/tests/components/cse7766/test.esp32.yaml new file mode 100644 index 000000000000..f94cd0f7d8e7 --- /dev/null +++ b/tests/components/cse7766/test.esp32.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.esp8266.yaml b/tests/components/cse7766/test.esp8266.yaml new file mode 100644 index 000000000000..432cc0a80ed6 --- /dev/null +++ b/tests/components/cse7766/test.esp8266.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cse7766/test.rp2040.yaml b/tests/components/cse7766/test.rp2040.yaml new file mode 100644 index 000000000000..432cc0a80ed6 --- /dev/null +++ b/tests/components/cse7766/test.rp2040.yaml @@ -0,0 +1,16 @@ +uart: + - id: uart_cse7766 + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +sensor: + - platform: cse7766 + voltage: + name: CSE7766 Voltage + current: + name: CSE7766 Current + power: + name: CSE776 Power diff --git a/tests/components/cst226/common.yaml b/tests/components/cst226/common.yaml new file mode 100644 index 000000000000..4cbf38ef500f --- /dev/null +++ b/tests/components/cst226/common.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_id_1 + clk_pin: GPIO7 + mosi_pin: GPIO6 + interface: any + +display: + - platform: ili9xxx + id: displ8 + model: ili9342 + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: + number: GPIO21 + +i2c: + scl: GPIO18 + sda: GPIO8 + +touchscreen: + - platform: cst226 + interrupt_pin: GPIO3 + reset_pin: GPIO20 + diff --git a/tests/components/cst226/test.esp32-c3.yaml b/tests/components/cst226/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/cst226/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/cst816/common.yaml b/tests/components/cst816/common.yaml new file mode 100644 index 000000000000..f8deea6e98bc --- /dev/null +++ b/tests/components/cst816/common.yaml @@ -0,0 +1,36 @@ +touchscreen: + - platform: cst816 + id: my_touchscreen + interrupt_pin: + number: 21 + reset_pin: GPIO16 + transform: + mirror_x: false + mirror_y: false + swap_xy: false + +i2c: + sda: 3 + scl: 2 + +display: + - id: my_display + platform: ili9xxx + dimensions: 480x320 + model: ST7796 + cs_pin: 15 + dc_pin: 20 + reset_pin: 22 + transform: + swap_xy: true + mirror_x: true + mirror_y: true + auto_clear_enabled: false + +spi: + clk_pin: 14 + mosi_pin: 13 + +binary_sensor: + - platform: cst816 + name: Home Button diff --git a/tests/components/cst816/test.esp32.yaml b/tests/components/cst816/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/cst816/test.esp32.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..e25acc95e16f --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32-c3.yaml b/tests/components/ct_clamp/test.esp32-c3.yaml new file mode 100644 index 000000000000..e25acc95e16f --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32-idf.yaml b/tests/components/ct_clamp/test.esp32-idf.yaml new file mode 100644 index 000000000000..1ea964fa96b3 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp32.yaml b/tests/components/ct_clamp/test.esp32.yaml new file mode 100644 index 000000000000..1ea964fa96b3 --- /dev/null +++ b/tests/components/ct_clamp/test.esp32.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 39 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.esp8266.yaml b/tests/components/ct_clamp/test.esp8266.yaml new file mode 100644 index 000000000000..9c7126480dd7 --- /dev/null +++ b/tests/components/ct_clamp/test.esp8266.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: A0 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/ct_clamp/test.rp2040.yaml b/tests/components/ct_clamp/test.rp2040.yaml new file mode 100644 index 000000000000..47077308aa29 --- /dev/null +++ b/tests/components/ct_clamp/test.rp2040.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: esp_adc_sensor + pin: 26 + - platform: ct_clamp + sensor: esp_adc_sensor + name: CT Clamp + sample_duration: 500ms + update_interval: 5s diff --git a/tests/components/current_based/test.esp32-c3-idf.yaml b/tests/components/current_based/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..69ab8830c330 --- /dev/null +++ b/tests/components/current_based/test.esp32-c3-idf.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32-c3.yaml b/tests/components/current_based/test.esp32-c3.yaml new file mode 100644 index 000000000000..69ab8830c330 --- /dev/null +++ b/tests/components/current_based/test.esp32-c3.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32-idf.yaml b/tests/components/current_based/test.esp32-idf.yaml new file mode 100644 index 000000000000..90781120bcf5 --- /dev/null +++ b/tests/components/current_based/test.esp32-idf.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp32.yaml b/tests/components/current_based/test.esp32.yaml new file mode 100644 index 000000000000..90781120bcf5 --- /dev/null +++ b/tests/components/current_based/test.esp32.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 16 + sda: 17 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.esp8266.yaml b/tests/components/current_based/test.esp8266.yaml new file mode 100644 index 000000000000..42d6d4676b5b --- /dev/null +++ b/tests/components/current_based/test.esp8266.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 15 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/current_based/test.rp2040.yaml b/tests/components/current_based/test.rp2040.yaml new file mode 100644 index 000000000000..69ab8830c330 --- /dev/null +++ b/tests/components/current_based/test.rp2040.yaml @@ -0,0 +1,68 @@ +i2c: + - id: i2c_ade7953 + scl: 5 + sda: 4 + +sensor: + - platform: ade7953_i2c + irq_pin: 6 + voltage: + name: ADE7953 Voltage + id: ade7953_voltage + current_a: + name: ADE7953 Current A + id: ade7953_current_a + current_b: + name: ADE7953 Current B + id: ade7953_current_b + power_factor_a: + name: ADE7953 Power Factor A + power_factor_b: + name: ADE7953 Power Factor B + apparent_power_a: + name: ADE7953 Apparent Power A + apparent_power_b: + name: ADE7953 Apparent Power B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: ADE7953 Reactive Power A + reactive_power_b: + name: ADE7953 Reactive Power B + update_interval: 1s + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: current_based + name: Current Based Cover + id: current_based_cover + open_sensor: ade7953_current_a + open_moving_current_threshold: 0.5 + open_obstacle_current_threshold: 0.8 + open_duration: 12s + open_action: + - switch.turn_on: template_switch1 + close_sensor: ade7953_current_b + close_moving_current_threshold: 0.5 + close_obstacle_current_threshold: 0.8 + close_duration: 10s + close_action: + - switch.turn_on: template_switch2 + stop_action: + - switch.turn_off: template_switch1 + - switch.turn_off: template_switch2 + obstacle_rollback: 30% + start_sensing_delay: 0.8s + malfunction_detection: true + malfunction_action: + then: + - logger.log: Malfunction Detected diff --git a/tests/components/cwww/test.esp32-c3-idf.yaml b/tests/components/cwww/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2760a167ee45 --- /dev/null +++ b/tests/components/cwww/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + channel: 0 + - platform: ledc + id: light_output_2 + pin: 2 + channel: 1 + phase_angle: 180° + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32-c3.yaml b/tests/components/cwww/test.esp32-c3.yaml new file mode 100644 index 000000000000..c829ca2a2b9d --- /dev/null +++ b/tests/components/cwww/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32-idf.yaml b/tests/components/cwww/test.esp32-idf.yaml new file mode 100644 index 000000000000..27fa160e5612 --- /dev/null +++ b/tests/components/cwww/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + channel: 0 + - platform: ledc + id: light_output_2 + pin: 13 + channel: 1 + phase_angle: 180° + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp32.yaml b/tests/components/cwww/test.esp32.yaml new file mode 100644 index 000000000000..f108d96ad3f2 --- /dev/null +++ b/tests/components/cwww/test.esp32.yaml @@ -0,0 +1,16 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.esp8266.yaml b/tests/components/cwww/test.esp8266.yaml new file mode 100644 index 000000000000..50c311f616df --- /dev/null +++ b/tests/components/cwww/test.esp8266.yaml @@ -0,0 +1,16 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/cwww/test.rp2040.yaml b/tests/components/cwww/test.rp2040.yaml new file mode 100644 index 000000000000..505d67f862e3 --- /dev/null +++ b/tests/components/cwww/test.rp2040.yaml @@ -0,0 +1,16 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + +light: + - platform: cwww + name: CWWW Light + cold_white: light_output_1 + warm_white: light_output_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true diff --git a/tests/components/dac7678/test.esp32-c3-idf.yaml b/tests/components/dac7678/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7e3455bb68ad --- /dev/null +++ b/tests/components/dac7678/test.esp32-c3-idf.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32-c3.yaml b/tests/components/dac7678/test.esp32-c3.yaml new file mode 100644 index 000000000000..7e3455bb68ad --- /dev/null +++ b/tests/components/dac7678/test.esp32-c3.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32-idf.yaml b/tests/components/dac7678/test.esp32-idf.yaml new file mode 100644 index 000000000000..946a7ca58d46 --- /dev/null +++ b/tests/components/dac7678/test.esp32-idf.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 16 + sda: 17 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp32.yaml b/tests/components/dac7678/test.esp32.yaml new file mode 100644 index 000000000000..946a7ca58d46 --- /dev/null +++ b/tests/components/dac7678/test.esp32.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 16 + sda: 17 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.esp8266.yaml b/tests/components/dac7678/test.esp8266.yaml new file mode 100644 index 000000000000..7e3455bb68ad --- /dev/null +++ b/tests/components/dac7678/test.esp8266.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/dac7678/test.rp2040.yaml b/tests/components/dac7678/test.rp2040.yaml new file mode 100644 index 000000000000..7e3455bb68ad --- /dev/null +++ b/tests/components/dac7678/test.rp2040.yaml @@ -0,0 +1,43 @@ +i2c: + - id: i2c_dac7678 + scl: 5 + sda: 4 + +dac7678: + address: 0x4A + id: dac7678_hub + internal_reference: true + +output: + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 0 + id: dac7678_1_ch0 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 1 + id: dac7678_1_ch1 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 2 + id: dac7678_1_ch2 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 3 + id: dac7678_1_ch3 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 4 + id: dac7678_1_ch4 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 5 + id: dac7678_1_ch5 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 6 + id: dac7678_1_ch6 + - platform: dac7678 + dac7678_id: dac7678_hub + channel: 7 + id: dac7678_1_ch7 diff --git a/tests/components/daikin/test.esp32.yaml b/tests/components/daikin/test.esp32.yaml new file mode 100644 index 000000000000..6672fe3e45a3 --- /dev/null +++ b/tests/components/daikin/test.esp32.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: daikin + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/daikin/test.esp8266.yaml b/tests/components/daikin/test.esp8266.yaml new file mode 100644 index 000000000000..47f02bbad9ba --- /dev/null +++ b/tests/components/daikin/test.esp8266.yaml @@ -0,0 +1,12 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: daikin + horizontal_default: middle + vertical_default: middle + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/daikin_arc/test.esp32.yaml b/tests/components/daikin_arc/test.esp32.yaml new file mode 100644 index 000000000000..a8556e8576aa --- /dev/null +++ b/tests/components/daikin_arc/test.esp32.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + id: tsvr + +remote_receiver: + id: rcvr + pin: + number: 27 + inverted: true + mode: + input: true + pullup: true + tolerance: 40% + +climate: + - platform: daikin_arc + name: "AC" + receiver_id: rcvr diff --git a/tests/components/daikin_arc/test.esp8266.yaml b/tests/components/daikin_arc/test.esp8266.yaml new file mode 100644 index 000000000000..abf1b34a6e4b --- /dev/null +++ b/tests/components/daikin_arc/test.esp8266.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + id: tsvr + +remote_receiver: + id: rcvr + pin: + number: 2 + inverted: true + mode: + input: true + pullup: true + tolerance: 40% + +climate: + - platform: daikin_arc + name: "AC" + receiver_id: rcvr diff --git a/tests/components/daikin_brc/test.esp32-c3-idf.yaml b/tests/components/daikin_brc/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..89a5b00f5dd4 --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32-c3.yaml b/tests/components/daikin_brc/test.esp32-c3.yaml new file mode 100644 index 000000000000..89a5b00f5dd4 --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32-idf.yaml b/tests/components/daikin_brc/test.esp32-idf.yaml new file mode 100644 index 000000000000..89a5b00f5dd4 --- /dev/null +++ b/tests/components/daikin_brc/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp32.yaml b/tests/components/daikin_brc/test.esp32.yaml new file mode 100644 index 000000000000..89a5b00f5dd4 --- /dev/null +++ b/tests/components/daikin_brc/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/daikin_brc/test.esp8266.yaml b/tests/components/daikin_brc/test.esp8266.yaml new file mode 100644 index 000000000000..b8c74803a242 --- /dev/null +++ b/tests/components/daikin_brc/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: daikin_brc + name: Daikin_brc Climate diff --git a/tests/components/dallas/common.yaml b/tests/components/dallas/common.yaml new file mode 100644 index 000000000000..797597710755 --- /dev/null +++ b/tests/components/dallas/common.yaml @@ -0,0 +1,11 @@ +dallas: + pin: 4 + +sensor: + - platform: dallas + address: 0x1C0000031EDD2A28 + name: Dallas Temperature + resolution: 9 + - platform: dallas + index: 1 + name: Dallas Temperature diff --git a/tests/components/dallas/test.esp32-c3-idf.yaml b/tests/components/dallas/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dallas/test.esp32-c3.yaml b/tests/components/dallas/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dallas/test.esp32-idf.yaml b/tests/components/dallas/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dallas/test.esp32.yaml b/tests/components/dallas/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dallas/test.esp8266.yaml b/tests/components/dallas/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dallas/test.rp2040.yaml b/tests/components/dallas/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dallas/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..237a6570b5d8 --- /dev/null +++ b/tests/components/daly_bms/test.esp32-c3-idf.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32-c3.yaml b/tests/components/daly_bms/test.esp32-c3.yaml new file mode 100644 index 000000000000..237a6570b5d8 --- /dev/null +++ b/tests/components/daly_bms/test.esp32-c3.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32-idf.yaml b/tests/components/daly_bms/test.esp32-idf.yaml new file mode 100644 index 000000000000..ec9607334d5d --- /dev/null +++ b/tests/components/daly_bms/test.esp32-idf.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp32.yaml b/tests/components/daly_bms/test.esp32.yaml new file mode 100644 index 000000000000..ec9607334d5d --- /dev/null +++ b/tests/components/daly_bms/test.esp32.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.esp8266.yaml b/tests/components/daly_bms/test.esp8266.yaml new file mode 100644 index 000000000000..237a6570b5d8 --- /dev/null +++ b/tests/components/daly_bms/test.esp8266.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/daly_bms/test.rp2040.yaml b/tests/components/daly_bms/test.rp2040.yaml new file mode 100644 index 000000000000..237a6570b5d8 --- /dev/null +++ b/tests/components/daly_bms/test.rp2040.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_daly_bms + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 4800 + +daly_bms: + update_interval: 20s + +binary_sensor: + - platform: daly_bms + charging_mos_enabled: + name: Charging MOS + discharging_mos_enabled: + name: Discharging MOS + +sensor: + - platform: daly_bms + voltage: + name: Battery Voltage + current: + name: Battery Current + battery_level: + name: Battery Level + max_cell_voltage: + name: Max Cell Voltage + max_cell_voltage_number: + name: Max Cell Voltage Number + min_cell_voltage: + name: Min Cell Voltage + min_cell_voltage_number: + name: Min Cell Voltage Number + max_temperature: + name: Max Temperature + max_temperature_probe_number: + name: Max Temperature Probe Number + min_temperature: + name: Min Temperature + min_temperature_probe_number: + name: Min Temperature Probe Number + remaining_capacity: + name: Remaining Capacity + cells_number: + name: Cells Number + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + +text_sensor: + - platform: daly_bms + status: + name: BMS Status diff --git a/tests/components/datetime/common.yaml b/tests/components/datetime/common.yaml new file mode 100644 index 000000000000..4e26b6812127 --- /dev/null +++ b/tests/components/datetime/common.yaml @@ -0,0 +1,3 @@ +datetime: + +time: diff --git a/tests/components/datetime/test.all.yaml b/tests/components/datetime/test.all.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/datetime/test.all.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/common.yaml b/tests/components/debug/common.yaml new file mode 100644 index 000000000000..5845beaa80e8 --- /dev/null +++ b/tests/components/debug/common.yaml @@ -0,0 +1 @@ +debug: diff --git a/tests/components/debug/test.esp32-c3-idf.yaml b/tests/components/debug/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.esp32-c3.yaml b/tests/components/debug/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.esp32-idf.yaml b/tests/components/debug/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.esp32.yaml b/tests/components/debug/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.esp8266.yaml b/tests/components/debug/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.host.yaml b/tests/components/debug/test.host.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.host.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/debug/test.rp2040.yaml b/tests/components/debug/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/debug/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/deep_sleep/test.esp32-c3-idf.yaml b/tests/components/deep_sleep/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..94942fd5b05f --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32-c3.yaml b/tests/components/deep_sleep/test.esp32-c3.yaml new file mode 100644 index 000000000000..94942fd5b05f --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32-idf.yaml b/tests/components/deep_sleep/test.esp32-idf.yaml new file mode 100644 index 000000000000..94942fd5b05f --- /dev/null +++ b/tests/components/deep_sleep/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp32.yaml b/tests/components/deep_sleep/test.esp32.yaml new file mode 100644 index 000000000000..94942fd5b05f --- /dev/null +++ b/tests/components/deep_sleep/test.esp32.yaml @@ -0,0 +1,14 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: + default: 10s + gpio_wakeup_reason: 30s + sleep_duration: 50s + wakeup_pin: 4 + wakeup_pin_mode: INVERT_WAKEUP diff --git a/tests/components/deep_sleep/test.esp8266.yaml b/tests/components/deep_sleep/test.esp8266.yaml new file mode 100644 index 000000000000..0992fa96967c --- /dev/null +++ b/tests/components/deep_sleep/test.esp8266.yaml @@ -0,0 +1,10 @@ +esphome: + on_boot: + then: + - deep_sleep.prevent + - delay: 1s + - deep_sleep.allow + +deep_sleep: + run_duration: 10s + sleep_duration: 50s diff --git a/tests/components/delonghi/test.esp32-c3-idf.yaml b/tests/components/delonghi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..cfe0f837f0d8 --- /dev/null +++ b/tests/components/delonghi/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32-c3.yaml b/tests/components/delonghi/test.esp32-c3.yaml new file mode 100644 index 000000000000..cfe0f837f0d8 --- /dev/null +++ b/tests/components/delonghi/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32-idf.yaml b/tests/components/delonghi/test.esp32-idf.yaml new file mode 100644 index 000000000000..cfe0f837f0d8 --- /dev/null +++ b/tests/components/delonghi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp32.yaml b/tests/components/delonghi/test.esp32.yaml new file mode 100644 index 000000000000..cfe0f837f0d8 --- /dev/null +++ b/tests/components/delonghi/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/delonghi/test.esp8266.yaml b/tests/components/delonghi/test.esp8266.yaml new file mode 100644 index 000000000000..adc478a6e690 --- /dev/null +++ b/tests/components/delonghi/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: delonghi + name: Delonghi Climate diff --git a/tests/components/dfplayer/test.esp32-c3-idf.yaml b/tests/components/dfplayer/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..94355915a71d --- /dev/null +++ b/tests/components/dfplayer/test.esp32-c3-idf.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32-c3.yaml b/tests/components/dfplayer/test.esp32-c3.yaml new file mode 100644 index 000000000000..94355915a71d --- /dev/null +++ b/tests/components/dfplayer/test.esp32-c3.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32-idf.yaml b/tests/components/dfplayer/test.esp32-idf.yaml new file mode 100644 index 000000000000..03b44b8ca97e --- /dev/null +++ b/tests/components/dfplayer/test.esp32-idf.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp32.yaml b/tests/components/dfplayer/test.esp32.yaml new file mode 100644 index 000000000000..03b44b8ca97e --- /dev/null +++ b/tests/components/dfplayer/test.esp32.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.esp8266.yaml b/tests/components/dfplayer/test.esp8266.yaml new file mode 100644 index 000000000000..94355915a71d --- /dev/null +++ b/tests/components/dfplayer/test.esp8266.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfplayer/test.rp2040.yaml b/tests/components/dfplayer/test.rp2040.yaml new file mode 100644 index 000000000000..94355915a71d --- /dev/null +++ b/tests/components/dfplayer/test.rp2040.yaml @@ -0,0 +1,42 @@ +esphome: + on_boot: + then: + - dfplayer.play: 5 + - dfplayer.play: + file: 4 + loop: true + - dfplayer.play_folder: + folder: 1 + file: 3 + - dfplayer.play_folder: + folder: 1 + loop: true + - dfplayer.set_device: + device: TF_CARD + - dfplayer.set_volume: 5 + - dfplayer.set_eq: ROCK + - dfplayer.play_next + - dfplayer.play_previous + - dfplayer.reset + - dfplayer.start + - dfplayer.pause + - dfplayer.stop + - dfplayer.random + - dfplayer.volume_up + - dfplayer.volume_down + - dfplayer.sleep + +uart: + - id: uart_dfplayer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfplayer: + on_finished_playback: + then: + if: + condition: + not: dfplayer.is_playing + then: + logger.log: Playback finished event diff --git a/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml b/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..71b17cecd5bb --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32-c3.yaml b/tests/components/dfrobot_sen0395/test.esp32-c3.yaml new file mode 100644 index 000000000000..71b17cecd5bb --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32-idf.yaml b/tests/components/dfrobot_sen0395/test.esp32-idf.yaml new file mode 100644 index 000000000000..5c06fc6660d8 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp32.yaml b/tests/components/dfrobot_sen0395/test.esp32.yaml new file mode 100644 index 000000000000..5c06fc6660d8 --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp32.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.esp8266.yaml b/tests/components/dfrobot_sen0395/test.esp8266.yaml new file mode 100644 index 000000000000..71b17cecd5bb --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.esp8266.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dfrobot_sen0395/test.rp2040.yaml b/tests/components/dfrobot_sen0395/test.rp2040.yaml new file mode 100644 index 000000000000..71b17cecd5bb --- /dev/null +++ b/tests/components/dfrobot_sen0395/test.rp2040.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda "return 7;" + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - dfrobot_sen0395.reset + +uart: + - id: uart_dfrobot_sen0395 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dfrobot_sen0395: + - id: mmwave + +binary_sensor: + - platform: dfrobot_sen0395 + id: mmwave_detected diff --git a/tests/components/dht/common.yaml b/tests/components/dht/common.yaml new file mode 100644 index 000000000000..f134a324cada --- /dev/null +++ b/tests/components/dht/common.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: dht + pin: 4 + model: AM2302 + update_interval: 15s + temperature: + id: dht_temperature + name: DHT Temperature + humidity: + id: dht_humidity + name: DHT Humidity diff --git a/tests/components/dht/test.esp32-c3-idf.yaml b/tests/components/dht/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht/test.esp32-c3.yaml b/tests/components/dht/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht/test.esp32-idf.yaml b/tests/components/dht/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht/test.esp32.yaml b/tests/components/dht/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht/test.esp8266.yaml b/tests/components/dht/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht/test.rp2040.yaml b/tests/components/dht/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/dht/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dht12/test.esp32-c3-idf.yaml b/tests/components/dht12/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c06c20fd9ffc --- /dev/null +++ b/tests/components/dht12/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32-c3.yaml b/tests/components/dht12/test.esp32-c3.yaml new file mode 100644 index 000000000000..c06c20fd9ffc --- /dev/null +++ b/tests/components/dht12/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32-idf.yaml b/tests/components/dht12/test.esp32-idf.yaml new file mode 100644 index 000000000000..02a00f5df784 --- /dev/null +++ b/tests/components/dht12/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 16 + sda: 17 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp32.yaml b/tests/components/dht12/test.esp32.yaml new file mode 100644 index 000000000000..02a00f5df784 --- /dev/null +++ b/tests/components/dht12/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 16 + sda: 17 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.esp8266.yaml b/tests/components/dht12/test.esp8266.yaml new file mode 100644 index 000000000000..c06c20fd9ffc --- /dev/null +++ b/tests/components/dht12/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/dht12/test.rp2040.yaml b/tests/components/dht12/test.rp2040.yaml new file mode 100644 index 000000000000..c06c20fd9ffc --- /dev/null +++ b/tests/components/dht12/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dht12 + scl: 5 + sda: 4 + +sensor: + - platform: dht12 + temperature: + name: DHT12 Temperature + humidity: + name: DHT12 Humidity + update_interval: 15s diff --git a/tests/components/display/common.yaml b/tests/components/display/common.yaml new file mode 100644 index 000000000000..a22aa767806d --- /dev/null +++ b/tests/components/display/common.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw an analog clock in the center of the screen + int centerX = it.get_width() / 2; + int centerY = it.get_height() / 2; + int radius = min(it.get_width(), it.get_height()) / 4; + + // Draw border + it.circle(centerX, centerY, radius); + + // Draw hour ticks + for(int h = 0; h < 12; h++) { + int hourAngle = (h * 30) - 90; + + it.line_at_angle(centerX, centerY, hourAngle, radius - 10, radius); + } + + // Draw minute ticks + for(int m = 0; m < 60; m++) { + int minuteAngle = (m * 6) - 90; + + it.line_at_angle(centerX, centerY, minuteAngle, radius - 5, radius); + } diff --git a/tests/components/display/test.esp32.yaml b/tests/components/display/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/display/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/dps310/test.esp32-c3-idf.yaml b/tests/components/dps310/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0e15e9ccc512 --- /dev/null +++ b/tests/components/dps310/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32-c3.yaml b/tests/components/dps310/test.esp32-c3.yaml new file mode 100644 index 000000000000..0e15e9ccc512 --- /dev/null +++ b/tests/components/dps310/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32-idf.yaml b/tests/components/dps310/test.esp32-idf.yaml new file mode 100644 index 000000000000..417cab5c4000 --- /dev/null +++ b/tests/components/dps310/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 16 + sda: 17 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp32.yaml b/tests/components/dps310/test.esp32.yaml new file mode 100644 index 000000000000..417cab5c4000 --- /dev/null +++ b/tests/components/dps310/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 16 + sda: 17 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.esp8266.yaml b/tests/components/dps310/test.esp8266.yaml new file mode 100644 index 000000000000..0e15e9ccc512 --- /dev/null +++ b/tests/components/dps310/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/dps310/test.rp2040.yaml b/tests/components/dps310/test.rp2040.yaml new file mode 100644 index 000000000000..0e15e9ccc512 --- /dev/null +++ b/tests/components/dps310/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_dps310 + scl: 5 + sda: 4 + +sensor: + - platform: dps310 + temperature: + name: DPS310 Temperature + pressure: + name: DPS310 Pressure + address: 0x77 diff --git a/tests/components/ds1307/test.esp32-c3-idf.yaml b/tests/components/ds1307/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c309b9c21216 --- /dev/null +++ b/tests/components/ds1307/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32-c3.yaml b/tests/components/ds1307/test.esp32-c3.yaml new file mode 100644 index 000000000000..c309b9c21216 --- /dev/null +++ b/tests/components/ds1307/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32-idf.yaml b/tests/components/ds1307/test.esp32-idf.yaml new file mode 100644 index 000000000000..017c7aac924c --- /dev/null +++ b/tests/components/ds1307/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 16 + sda: 17 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp32.yaml b/tests/components/ds1307/test.esp32.yaml new file mode 100644 index 000000000000..017c7aac924c --- /dev/null +++ b/tests/components/ds1307/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 16 + sda: 17 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.esp8266.yaml b/tests/components/ds1307/test.esp8266.yaml new file mode 100644 index 000000000000..c309b9c21216 --- /dev/null +++ b/tests/components/ds1307/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/ds1307/test.rp2040.yaml b/tests/components/ds1307/test.rp2040.yaml new file mode 100644 index 000000000000..c309b9c21216 --- /dev/null +++ b/tests/components/ds1307/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_ds1307 + scl: 5 + sda: 4 + +time: + - platform: ds1307 + id: ds1307_time + update_interval: never diff --git a/tests/components/dsmr/test.esp32-c3.yaml b/tests/components/dsmr/test.esp32-c3.yaml new file mode 100644 index 000000000000..e813556be8fe --- /dev/null +++ b/tests/components/dsmr/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 6 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.esp32.yaml b/tests/components/dsmr/test.esp32.yaml new file mode 100644 index 000000000000..1fd0448ab3dd --- /dev/null +++ b/tests/components/dsmr/test.esp32.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 15 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.esp8266.yaml b/tests/components/dsmr/test.esp8266.yaml new file mode 100644 index 000000000000..8037fb4b1a5b --- /dev/null +++ b/tests/components/dsmr/test.esp8266.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 15 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/dsmr/test.rp2040.yaml b/tests/components/dsmr/test.rp2040.yaml new file mode 100644 index 000000000000..e813556be8fe --- /dev/null +++ b/tests/components/dsmr/test.rp2040.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_dsmr + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +dsmr: + decryption_key: 00112233445566778899aabbccddeeff + max_telegram_length: 1000 + request_pin: 6 + request_interval: 20s + receive_timeout: 100ms diff --git a/tests/components/duty_cycle/common.yaml b/tests/components/duty_cycle/common.yaml new file mode 100644 index 000000000000..2b7f31efbdc7 --- /dev/null +++ b/tests/components/duty_cycle/common.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: duty_cycle + pin: 4 + name: Duty Cycle Sensor diff --git a/tests/components/duty_cycle/test.esp32-c3-idf.yaml b/tests/components/duty_cycle/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.esp32-c3.yaml b/tests/components/duty_cycle/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.esp32-idf.yaml b/tests/components/duty_cycle/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.esp32.yaml b/tests/components/duty_cycle/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.esp8266.yaml b/tests/components/duty_cycle/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_cycle/test.rp2040.yaml b/tests/components/duty_cycle/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_cycle/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/common.yaml b/tests/components/duty_time/common.yaml new file mode 100644 index 000000000000..28fa4afd1ce6 --- /dev/null +++ b/tests/components/duty_time/common.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +sensor: + - platform: duty_time + name: Duty Time + sensor: bin1 diff --git a/tests/components/duty_time/test.esp32-c3-idf.yaml b/tests/components/duty_time/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/test.esp32-c3.yaml b/tests/components/duty_time/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/test.esp32-idf.yaml b/tests/components/duty_time/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/test.esp32.yaml b/tests/components/duty_time/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/test.esp8266.yaml b/tests/components/duty_time/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/duty_time/test.rp2040.yaml b/tests/components/duty_time/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/duty_time/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/e131/test.esp32-c3-idf.yaml b/tests/components/e131/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..25304cd3b44c --- /dev/null +++ b/tests/components/e131/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32-c3.yaml b/tests/components/e131/test.esp32-c3.yaml new file mode 100644 index 000000000000..25304cd3b44c --- /dev/null +++ b/tests/components/e131/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32-idf.yaml b/tests/components/e131/test.esp32-idf.yaml new file mode 100644 index 000000000000..25304cd3b44c --- /dev/null +++ b/tests/components/e131/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp32.yaml b/tests/components/e131/test.esp32.yaml new file mode 100644 index 000000000000..25304cd3b44c --- /dev/null +++ b/tests/components/e131/test.esp32.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.esp8266.yaml b/tests/components/e131/test.esp8266.yaml new file mode 100644 index 000000000000..54245014a570 --- /dev/null +++ b/tests/components/e131/test.esp8266.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: neopixelbus + name: Neopixelbus Light + pin: 1 + type: GRBW + variant: SK6812 + method: ESP8266_UART0 + num_leds: 256 + effects: + - e131: + universe: 1 diff --git a/tests/components/e131/test.rp2040.yaml b/tests/components/e131/test.rp2040.yaml new file mode 100644 index 000000000000..0ae31f540332 --- /dev/null +++ b/tests/components/e131/test.rp2040.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +e131: + +light: + - platform: rp2040_pio_led_strip + id: led_strip + pin: 2 + pio: 0 + num_leds: 256 + rgb_order: GRB + chipset: WS2812 + effects: + - e131: + universe: 1 diff --git a/tests/components/ee895/test.esp32-c3-idf.yaml b/tests/components/ee895/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4a36117fabd6 --- /dev/null +++ b/tests/components/ee895/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32-c3.yaml b/tests/components/ee895/test.esp32-c3.yaml new file mode 100644 index 000000000000..4a36117fabd6 --- /dev/null +++ b/tests/components/ee895/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32-idf.yaml b/tests/components/ee895/test.esp32-idf.yaml new file mode 100644 index 000000000000..241bdb957442 --- /dev/null +++ b/tests/components/ee895/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 16 + sda: 17 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp32.yaml b/tests/components/ee895/test.esp32.yaml new file mode 100644 index 000000000000..241bdb957442 --- /dev/null +++ b/tests/components/ee895/test.esp32.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 16 + sda: 17 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.esp8266.yaml b/tests/components/ee895/test.esp8266.yaml new file mode 100644 index 000000000000..4a36117fabd6 --- /dev/null +++ b/tests/components/ee895/test.esp8266.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ee895/test.rp2040.yaml b/tests/components/ee895/test.rp2040.yaml new file mode 100644 index 000000000000..4a36117fabd6 --- /dev/null +++ b/tests/components/ee895/test.rp2040.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_ee895 + scl: 5 + sda: 4 + +sensor: + - platform: ee895 + address: 0x5F + co2: + name: EE895 CO2 + temperature: + name: EE895 Temperature + pressure: + name: EE895 Pressure diff --git a/tests/components/ektf2232/test.esp32-c3-idf.yaml b/tests/components/ektf2232/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..371f2795a227 --- /dev/null +++ b/tests/components/ektf2232/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32-c3.yaml b/tests/components/ektf2232/test.esp32-c3.yaml new file mode 100644 index 000000000000..371f2795a227 --- /dev/null +++ b/tests/components/ektf2232/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32-idf.yaml b/tests/components/ektf2232/test.esp32-idf.yaml new file mode 100644 index 000000000000..9c6eef8bb3ea --- /dev/null +++ b/tests/components/ektf2232/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 14 + rts_pin: 15 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp32.yaml b/tests/components/ektf2232/test.esp32.yaml new file mode 100644 index 000000000000..9c6eef8bb3ea --- /dev/null +++ b/tests/components/ektf2232/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 14 + rts_pin: 15 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.esp8266.yaml b/tests/components/ektf2232/test.esp8266.yaml new file mode 100644 index 000000000000..03f18f718475 --- /dev/null +++ b/tests/components/ektf2232/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 12 + rts_pin: 13 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ektf2232/test.rp2040.yaml b/tests/components/ektf2232/test.rp2040.yaml new file mode 100644 index 000000000000..371f2795a227 --- /dev/null +++ b/tests/components/ektf2232/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ektf2232 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ektf2232 + interrupt_pin: 6 + rts_pin: 7 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/emc2101/test.esp32-c3-idf.yaml b/tests/components/emc2101/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d95bc1b00165 --- /dev/null +++ b/tests/components/emc2101/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32-c3.yaml b/tests/components/emc2101/test.esp32-c3.yaml new file mode 100644 index 000000000000..d95bc1b00165 --- /dev/null +++ b/tests/components/emc2101/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32-idf.yaml b/tests/components/emc2101/test.esp32-idf.yaml new file mode 100644 index 000000000000..34a7d22b7195 --- /dev/null +++ b/tests/components/emc2101/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 16 + sda: 17 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp32.yaml b/tests/components/emc2101/test.esp32.yaml new file mode 100644 index 000000000000..34a7d22b7195 --- /dev/null +++ b/tests/components/emc2101/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 16 + sda: 17 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.esp8266.yaml b/tests/components/emc2101/test.esp8266.yaml new file mode 100644 index 000000000000..d95bc1b00165 --- /dev/null +++ b/tests/components/emc2101/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emc2101/test.rp2040.yaml b/tests/components/emc2101/test.rp2040.yaml new file mode 100644 index 000000000000..d95bc1b00165 --- /dev/null +++ b/tests/components/emc2101/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_emc2101 + scl: 5 + sda: 4 + +emc2101: + pwm: + resolution: 8 + +output: + - platform: emc2101 + id: fan_duty_cycle + +sensor: + - platform: emc2101 + internal_temperature: + id: internal_temperature_sensor + name: Internal Temperature Sensor + speed: + id: speed_sensor + name: Speed Sensor + duty_cycle: + id: duty_cycle_sensor + name: Duty Cycle Sensor + update_interval: 5s diff --git a/tests/components/emmeti/common.yaml b/tests/components/emmeti/common.yaml new file mode 100644 index 000000000000..ac4201e19b35 --- /dev/null +++ b/tests/components/emmeti/common.yaml @@ -0,0 +1,14 @@ +remote_transmitter: + id: tx + pin: ${remote_transmitter_pin} + carrier_duty_percent: 100% + +remote_receiver: + id: rcvr + pin: ${remote_receiver_pin} + +climate: + - platform: emmeti + name: Emmeti + receiver_id: rcvr + transmitter_id: tx diff --git a/tests/components/emmeti/test.esp32-idf.yaml b/tests/components/emmeti/test.esp32-idf.yaml new file mode 100644 index 000000000000..2689ff279e12 --- /dev/null +++ b/tests/components/emmeti/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO33 + remote_receiver_pin: GPIO32 + +<<: !include common.yaml diff --git a/tests/components/emmeti/test.esp32.yaml b/tests/components/emmeti/test.esp32.yaml new file mode 100644 index 000000000000..2689ff279e12 --- /dev/null +++ b/tests/components/emmeti/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO33 + remote_receiver_pin: GPIO32 + +<<: !include common.yaml diff --git a/tests/components/emmeti/test.esp8266.yaml b/tests/components/emmeti/test.esp8266.yaml new file mode 100644 index 000000000000..2fb00aea6126 --- /dev/null +++ b/tests/components/emmeti/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + remote_transmitter_pin: GPIO4 + remote_receiver_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/endstop/common.yaml b/tests/components/endstop/common.yaml new file mode 100644 index 000000000000..341fbf726065 --- /dev/null +++ b/tests/components/endstop/common.yaml @@ -0,0 +1,33 @@ +binary_sensor: + - platform: template + id: bin1 + lambda: |- + if (millis() > 10000) { + return true; + } else { + return false; + } + +switch: + - platform: template + id: template_switch1 + optimistic: true + - platform: template + id: template_switch2 + optimistic: true + +cover: + - platform: endstop + id: endstop_cover + name: Endstop Cover + stop_action: + - switch.turn_on: template_switch1 + open_endstop: bin1 + open_action: + - switch.turn_on: template_switch1 + open_duration: 5min + close_endstop: bin1 + close_action: + - switch.turn_on: template_switch2 + close_duration: 4.5min + max_duration: 10min diff --git a/tests/components/endstop/test.esp32-c3-idf.yaml b/tests/components/endstop/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/endstop/test.esp32-c3.yaml b/tests/components/endstop/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/endstop/test.esp32-idf.yaml b/tests/components/endstop/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/endstop/test.esp32.yaml b/tests/components/endstop/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/endstop/test.esp8266.yaml b/tests/components/endstop/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/endstop/test.rp2040.yaml b/tests/components/endstop/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/endstop/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ens160/test.esp32-c3-idf.yaml b/tests/components/ens160/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..29f48e812f4b --- /dev/null +++ b/tests/components/ens160/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32-c3.yaml b/tests/components/ens160/test.esp32-c3.yaml new file mode 100644 index 000000000000..29f48e812f4b --- /dev/null +++ b/tests/components/ens160/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32-idf.yaml b/tests/components/ens160/test.esp32-idf.yaml new file mode 100644 index 000000000000..23f7674aefe4 --- /dev/null +++ b/tests/components/ens160/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 16 + sda: 17 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp32.yaml b/tests/components/ens160/test.esp32.yaml new file mode 100644 index 000000000000..23f7674aefe4 --- /dev/null +++ b/tests/components/ens160/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 16 + sda: 17 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.esp8266.yaml b/tests/components/ens160/test.esp8266.yaml new file mode 100644 index 000000000000..29f48e812f4b --- /dev/null +++ b/tests/components/ens160/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens160/test.rp2040.yaml b/tests/components/ens160/test.rp2040.yaml new file mode 100644 index 000000000000..29f48e812f4b --- /dev/null +++ b/tests/components/ens160/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ens160 + scl: 5 + sda: 4 + +sensor: + - platform: ens160 + eco2: + name: ENS160 eCO2 + tvoc: + name: ENS160 Total Volatile Organic Compounds + aqi: + name: ENS160 Air Quality Index diff --git a/tests/components/ens210/test.esp32-c3-idf.yaml b/tests/components/ens210/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..bacb71f9f875 --- /dev/null +++ b/tests/components/ens210/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32-c3.yaml b/tests/components/ens210/test.esp32-c3.yaml new file mode 100644 index 000000000000..bacb71f9f875 --- /dev/null +++ b/tests/components/ens210/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32-idf.yaml b/tests/components/ens210/test.esp32-idf.yaml new file mode 100644 index 000000000000..8b2d29cc2511 --- /dev/null +++ b/tests/components/ens210/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 16 + sda: 17 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp32.yaml b/tests/components/ens210/test.esp32.yaml new file mode 100644 index 000000000000..8b2d29cc2511 --- /dev/null +++ b/tests/components/ens210/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 16 + sda: 17 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.esp8266.yaml b/tests/components/ens210/test.esp8266.yaml new file mode 100644 index 000000000000..bacb71f9f875 --- /dev/null +++ b/tests/components/ens210/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/ens210/test.rp2040.yaml b/tests/components/ens210/test.rp2040.yaml new file mode 100644 index 000000000000..bacb71f9f875 --- /dev/null +++ b/tests/components/ens210/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_ens210 + scl: 5 + sda: 4 + +sensor: + - platform: ens210 + temperature: + name: ENS210 Temperature + humidity: + name: ENS210 Humidity + update_interval: 15s diff --git a/tests/components/esp32_ble/common.yaml b/tests/components/esp32_ble/common.yaml new file mode 100644 index 000000000000..76b35fc8f8bf --- /dev/null +++ b/tests/components/esp32_ble/common.yaml @@ -0,0 +1,2 @@ +esp32_ble: + io_capability: keyboard_only diff --git a/tests/components/esp32_ble/test.esp32-c3-idf.yaml b/tests/components/esp32_ble/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble/test.esp32-c3.yaml b/tests/components/esp32_ble/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble/test.esp32-idf.yaml b/tests/components/esp32_ble/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble/test.esp32.yaml b/tests/components/esp32_ble/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_beacon/common.yaml b/tests/components/esp32_ble_beacon/common.yaml new file mode 100644 index 000000000000..aafb0341d7b1 --- /dev/null +++ b/tests/components/esp32_ble_beacon/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_beacon: + type: iBeacon + uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' diff --git a/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_beacon/test.esp32-c3.yaml b/tests/components/esp32_ble_beacon/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_beacon/test.esp32-idf.yaml b/tests/components/esp32_ble_beacon/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_beacon/test.esp32.yaml b/tests/components/esp32_ble_beacon/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_beacon/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_client/common.yaml b/tests/components/esp32_ble_client/common.yaml new file mode 100644 index 000000000000..33b7205bf216 --- /dev/null +++ b/tests/components/esp32_ble_client/common.yaml @@ -0,0 +1,5 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: blec diff --git a/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_client/test.esp32-c3.yaml b/tests/components/esp32_ble_client/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_client/test.esp32-idf.yaml b/tests/components/esp32_ble_client/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_client/test.esp32.yaml b/tests/components/esp32_ble_client/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_client/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_server/common.yaml b/tests/components/esp32_ble_server/common.yaml new file mode 100644 index 000000000000..29a5407f846e --- /dev/null +++ b/tests/components/esp32_ble_server/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] diff --git a/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_server/test.esp32-c3.yaml b/tests/components/esp32_ble_server/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_server/test.esp32-idf.yaml b/tests/components/esp32_ble_server/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_server/test.esp32.yaml b/tests/components/esp32_ble_server/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_server/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_tracker/common.yaml b/tests/components/esp32_ble_tracker/common.yaml new file mode 100644 index 000000000000..ef23635c9e2f --- /dev/null +++ b/tests/components/esp32_ble_tracker/common.yaml @@ -0,0 +1,41 @@ +esphome: + on_boot: + then: + - esp32_ble_tracker.start_scan + - esp32_ble_tracker.stop_scan + +esp32_ble_tracker: + on_ble_advertise: + - mac_address: + - AA:BB:CC:DD:EE:FF + - FF:EE:DD:CC:BB:AA + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str()); + # yamllint enable rule:line-length + - mac_address: AC:37:43:77:5F:4C + then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + - then: + # yamllint disable rule:line-length + - lambda: !lambda |- + ESP_LOGD("main", "The device address is %s", x.address_str().c_str()); + # yamllint enable rule:line-length + on_ble_service_data_advertise: + - service_uuid: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of service data is %i", x.size()); + on_ble_manufacturer_data_advertise: + - manufacturer_id: ABCD + then: + - lambda: !lambda |- + ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); + on_scan_end: + - then: + - lambda: |- + ESP_LOGD("ble_auto", "The scan has ended!"); diff --git a/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml b/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_tracker/test.esp32-c3.yaml b/tests/components/esp32_ble_tracker/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_tracker/test.esp32-idf.yaml b/tests/components/esp32_ble_tracker/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_ble_tracker/test.esp32.yaml b/tests/components/esp32_ble_tracker/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_ble_tracker/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_camera/common.yaml b/tests/components/esp32_camera/common.yaml new file mode 100644 index 000000000000..2f5f792f1c13 --- /dev/null +++ b/tests/components/esp32_camera/common.yaml @@ -0,0 +1,28 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); diff --git a/tests/components/esp32_camera/test.esp32-idf.yaml b/tests/components/esp32_camera/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_camera/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_camera/test.esp32.yaml b/tests/components/esp32_camera/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_camera/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_camera_web_server/common.yaml b/tests/components/esp32_camera_web_server/common.yaml new file mode 100644 index 000000000000..5edefdf0a85a --- /dev/null +++ b/tests/components/esp32_camera_web_server/common.yaml @@ -0,0 +1,34 @@ +esp32_camera: + name: ESP32 Camera + data_pins: + - number: 17 + - number: 35 + - number: 34 + - number: 5 + - number: 39 + - number: 18 + - number: 36 + - number: 19 + vsync_pin: 22 + href_pin: 26 + pixel_clock_pin: 21 + external_clock: + pin: 27 + frequency: 20MHz + i2c_pins: + sda: 25 + scl: 23 + reset_pin: 15 + power_down_pin: 1 + resolution: 640x480 + jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); + +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot diff --git a/tests/components/esp32_camera_web_server/test.esp32-idf.yaml b/tests/components/esp32_camera_web_server/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_camera_web_server/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_camera_web_server/test.esp32.yaml b/tests/components/esp32_camera_web_server/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_camera_web_server/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_can/test.esp32-c3-idf.yaml b/tests/components/esp32_can/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b4fd34cf51a8 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-c3-idf.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32-c3.yaml b/tests/components/esp32_can/test.esp32-c3.yaml new file mode 100644 index 000000000000..b4fd34cf51a8 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-c3.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32-idf.yaml b/tests/components/esp32_can/test.esp32-idf.yaml new file mode 100644 index 000000000000..159a69585390 --- /dev/null +++ b/tests/components/esp32_can/test.esp32-idf.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 13 + tx_pin: 14 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_can/test.esp32.yaml b/tests/components/esp32_can/test.esp32.yaml new file mode 100644 index 000000000000..159a69585390 --- /dev/null +++ b/tests/components/esp32_can/test.esp32.yaml @@ -0,0 +1,45 @@ +esphome: + on_boot: + then: + - canbus.send: + # Extended ID explicit + use_extended_id: true + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + # Standard ID by default + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: 13 + tx_pin: 14 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", b.c_str() ); + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/esp32_dac/common.yaml b/tests/components/esp32_dac/common.yaml new file mode 100644 index 000000000000..225627f5af99 --- /dev/null +++ b/tests/components/esp32_dac/common.yaml @@ -0,0 +1,4 @@ +output: + - platform: esp32_dac + id: dac_output + pin: 25 diff --git a/tests/components/esp32_dac/test.esp32-idf.yaml b/tests/components/esp32_dac/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_dac/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_dac/test.esp32.yaml b/tests/components/esp32_dac/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_dac/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_hall/common.yaml b/tests/components/esp32_hall/common.yaml new file mode 100644 index 000000000000..f8429f5aa01b --- /dev/null +++ b/tests/components/esp32_hall/common.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: esp32_hall + name: ESP32 Hall Sensor diff --git a/tests/components/esp32_hall/test.esp32-idf.yaml b/tests/components/esp32_hall/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_hall/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_hall/test.esp32.yaml b/tests/components/esp32_hall/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_hall/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_improv/common.yaml b/tests/components/esp32_improv/common.yaml new file mode 100644 index 000000000000..7eb3f9c0be42 --- /dev/null +++ b/tests/components/esp32_improv/common.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +binary_sensor: + - platform: gpio + pin: 0 + id: io0_button + +output: + - platform: gpio + pin: 2 + id: built_in_led + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led diff --git a/tests/components/esp32_improv/test.esp32-c3-idf.yaml b/tests/components/esp32_improv/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_improv/test.esp32-c3.yaml b/tests/components/esp32_improv/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_improv/test.esp32-idf.yaml b/tests/components/esp32_improv/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_improv/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_improv/test.esp32.yaml b/tests/components/esp32_improv/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_improv/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b226d1de06ba --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 4 + num_leds: 60 + rmt_channel: 0 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 5 + num_leds: 60 + rmt_channel: 1 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml new file mode 100644 index 000000000000..b226d1de06ba --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 4 + num_leds: 60 + rmt_channel: 0 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 5 + num_leds: 60 + rmt_channel: 1 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml new file mode 100644 index 000000000000..d51a66451fe9 --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 14 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_rmt_led_strip/test.esp32.yaml b/tests/components/esp32_rmt_led_strip/test.esp32.yaml new file mode 100644 index 000000000000..d51a66451fe9 --- /dev/null +++ b/tests/components/esp32_rmt_led_strip/test.esp32.yaml @@ -0,0 +1,18 @@ +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 14 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/components/esp32_touch/common.yaml b/tests/components/esp32_touch/common.yaml new file mode 100644 index 000000000000..691cce8d86dd --- /dev/null +++ b/tests/components/esp32_touch/common.yaml @@ -0,0 +1,16 @@ +esp32_touch: + setup_mode: false + iir_filter: 10ms + sleep_duration: 27ms + measurement_duration: 8ms + low_voltage_reference: 0.5V + high_voltage_reference: 2.7V + voltage_attenuation: 1.5V + +binary_sensor: + - platform: esp32_touch + name: ESP32 Touch Pad + pin: 27 + threshold: 1000 + on_press: + - logger.log: "I'm touched!" diff --git a/tests/components/esp32_touch/test.esp32-idf.yaml b/tests/components/esp32_touch/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_touch/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp32_touch/test.esp32.yaml b/tests/components/esp32_touch/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp32_touch/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/esp8266_pwm/common.yaml b/tests/components/esp8266_pwm/common.yaml new file mode 100644 index 000000000000..52b290f91bcc --- /dev/null +++ b/tests/components/esp8266_pwm/common.yaml @@ -0,0 +1,8 @@ +output: + - platform: esp8266_pwm + id: out + pin: 4 + frequency: 50Hz + - platform: esp8266_pwm + id: out2 + pin: 5 diff --git a/tests/components/esp8266_pwm/test.esp8266.yaml b/tests/components/esp8266_pwm/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/esp8266_pwm/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ethernet/common.yaml b/tests/components/ethernet/common.yaml new file mode 100644 index 000000000000..b9ed9cb036a4 --- /dev/null +++ b/tests/components/ethernet/common.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/lan8720.esp32-idf.yaml b/tests/components/ethernet/lan8720.esp32-idf.yaml new file mode 100644 index 000000000000..b9ed9cb036a4 --- /dev/null +++ b/tests/components/ethernet/lan8720.esp32-idf.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/lan8720.esp32.yaml b/tests/components/ethernet/lan8720.esp32.yaml new file mode 100644 index 000000000000..b9ed9cb036a4 --- /dev/null +++ b/tests/components/ethernet/lan8720.esp32.yaml @@ -0,0 +1,12 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/test.esp32-idf.yaml b/tests/components/ethernet/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ethernet/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ethernet/test.esp32.yaml b/tests/components/ethernet/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ethernet/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ethernet/w5500.esp32-idf.yaml b/tests/components/ethernet/w5500.esp32-idf.yaml new file mode 100644 index 000000000000..6fdccb36e3f4 --- /dev/null +++ b/tests/components/ethernet/w5500.esp32-idf.yaml @@ -0,0 +1,14 @@ +ethernet: + type: W5500 + clk_pin: GPIO19 + mosi_pin: GPIO21 + miso_pin: GPIO23 + cs_pin: GPIO18 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 10Mhz + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet/w5500.esp32.yaml b/tests/components/ethernet/w5500.esp32.yaml new file mode 100644 index 000000000000..6fdccb36e3f4 --- /dev/null +++ b/tests/components/ethernet/w5500.esp32.yaml @@ -0,0 +1,14 @@ +ethernet: + type: W5500 + clk_pin: GPIO19 + mosi_pin: GPIO21 + miso_pin: GPIO23 + cs_pin: GPIO18 + interrupt_pin: GPIO36 + reset_pin: GPIO22 + clock_speed: 10Mhz + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local diff --git a/tests/components/ethernet_info/common.yaml b/tests/components/ethernet_info/common.yaml new file mode 100644 index 000000000000..dade4d7ca58c --- /dev/null +++ b/tests/components/ethernet_info/common.yaml @@ -0,0 +1,19 @@ +ethernet: + type: LAN8720 + mdc_pin: 23 + mdio_pin: 25 + clk_mode: GPIO0_IN + phy_addr: 0 + power_pin: 26 + manual_ip: + static_ip: 192.168.178.56 + gateway: 192.168.178.1 + subnet: 255.255.255.0 + domain: .local + +text_sensor: + - platform: ethernet_info + ip_address: + name: IP Address + dns_address: + name: DNS Address diff --git a/tests/components/ethernet_info/test.esp32-idf.yaml b/tests/components/ethernet_info/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ethernet_info/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ethernet_info/test.esp32.yaml b/tests/components/ethernet_info/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ethernet_info/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/common.yaml b/tests/components/event/common.yaml new file mode 100644 index 000000000000..71cc19a6b010 --- /dev/null +++ b/tests/components/event/common.yaml @@ -0,0 +1,9 @@ +event: + - platform: template + name: Event + id: some_event + event_types: + - template_event_type1 + - template_event_type2 + on_event: + - logger.log: Event fired diff --git a/tests/components/event/test.esp32-c3-idf.yaml b/tests/components/event/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/test.esp32-c3.yaml b/tests/components/event/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/test.esp32-idf.yaml b/tests/components/event/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/test.esp32.yaml b/tests/components/event/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/test.esp8266.yaml b/tests/components/event/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/event/test.rp2040.yaml b/tests/components/event/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/event/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/exposure_notifications/common.yaml b/tests/components/exposure_notifications/common.yaml new file mode 100644 index 000000000000..faba5bb2d104 --- /dev/null +++ b/tests/components/exposure_notifications/common.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +exposure_notifications: + on_exposure_notification: + then: + - lambda: | + ESP_LOGD("main", "Got notification:"); + ESP_LOGD("main", " RPI: %s", format_hex(x.rolling_proximity_identifier).c_str()); + ESP_LOGD("main", " RSSI: %d", x.rssi); diff --git a/tests/components/exposure_notifications/test.esp32-c3-idf.yaml b/tests/components/exposure_notifications/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/exposure_notifications/test.esp32-c3.yaml b/tests/components/exposure_notifications/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/exposure_notifications/test.esp32-idf.yaml b/tests/components/exposure_notifications/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/exposure_notifications/test.esp32.yaml b/tests/components/exposure_notifications/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/exposure_notifications/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/common.yaml b/tests/components/external_components/common.yaml new file mode 100644 index 000000000000..2b51267ec684 --- /dev/null +++ b/tests/components/external_components/common.yaml @@ -0,0 +1,6 @@ +external_components: + - source: github://esphome/esphome@dev + refresh: 1d + components: [bh1750] + - source: ../../../esphome/components + components: [sntp] diff --git a/tests/components/external_components/test.esp32-c3-idf.yaml b/tests/components/external_components/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/test.esp32-c3.yaml b/tests/components/external_components/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/test.esp32-idf.yaml b/tests/components/external_components/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/test.esp32.yaml b/tests/components/external_components/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/test.esp8266.yaml b/tests/components/external_components/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/external_components/test.rp2040.yaml b/tests/components/external_components/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/external_components/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ezo/test.esp32-c3-idf.yaml b/tests/components/ezo/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..93ceb9efd36c --- /dev/null +++ b/tests/components/ezo/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32-c3.yaml b/tests/components/ezo/test.esp32-c3.yaml new file mode 100644 index 000000000000..93ceb9efd36c --- /dev/null +++ b/tests/components/ezo/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32-idf.yaml b/tests/components/ezo/test.esp32-idf.yaml new file mode 100644 index 000000000000..61a8d2b25f62 --- /dev/null +++ b/tests/components/ezo/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 16 + sda: 17 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp32.yaml b/tests/components/ezo/test.esp32.yaml new file mode 100644 index 000000000000..61a8d2b25f62 --- /dev/null +++ b/tests/components/ezo/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 16 + sda: 17 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.esp8266.yaml b/tests/components/ezo/test.esp8266.yaml new file mode 100644 index 000000000000..93ceb9efd36c --- /dev/null +++ b/tests/components/ezo/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo/test.rp2040.yaml b/tests/components/ezo/test.rp2040.yaml new file mode 100644 index 000000000000..93ceb9efd36c --- /dev/null +++ b/tests/components/ezo/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_ezo + scl: 5 + sda: 4 + +sensor: + - platform: ezo + id: ph_ezo + address: 99 + unit_of_measurement: pH diff --git a/tests/components/ezo_pmp/test.esp32-c3-idf.yaml b/tests/components/ezo_pmp/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fa047de3de91 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-c3-idf.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32-c3.yaml b/tests/components/ezo_pmp/test.esp32-c3.yaml new file mode 100644 index 000000000000..fa047de3de91 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-c3.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32-idf.yaml b/tests/components/ezo_pmp/test.esp32-idf.yaml new file mode 100644 index 000000000000..9fc929b36504 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32-idf.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 16 + sda: 17 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp32.yaml b/tests/components/ezo_pmp/test.esp32.yaml new file mode 100644 index 000000000000..9fc929b36504 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp32.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 16 + sda: 17 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.esp8266.yaml b/tests/components/ezo_pmp/test.esp8266.yaml new file mode 100644 index 000000000000..fa047de3de91 --- /dev/null +++ b/tests/components/ezo_pmp/test.esp8266.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/ezo_pmp/test.rp2040.yaml b/tests/components/ezo_pmp/test.rp2040.yaml new file mode 100644 index 000000000000..fa047de3de91 --- /dev/null +++ b/tests/components/ezo_pmp/test.rp2040.yaml @@ -0,0 +1,61 @@ +i2c: + - id: i2c_ezo_pmp + scl: 5 + sda: 4 + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +binary_sensor: + - platform: ezo_pmp + pump_state: + name: Pump State + is_paused: + name: Is Paused + +sensor: + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? diff --git a/tests/components/factory_reset/common.yaml b/tests/components/factory_reset/common.yaml new file mode 100644 index 000000000000..ad3abd603e0a --- /dev/null +++ b/tests/components/factory_reset/common.yaml @@ -0,0 +1,3 @@ +button: + - platform: factory_reset + name: Reset to Factory Default Settings diff --git a/tests/components/factory_reset/test.esp32-c3-idf.yaml b/tests/components/factory_reset/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/factory_reset/test.esp32-c3.yaml b/tests/components/factory_reset/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/factory_reset/test.esp32-idf.yaml b/tests/components/factory_reset/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/factory_reset/test.esp32.yaml b/tests/components/factory_reset/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/factory_reset/test.esp8266.yaml b/tests/components/factory_reset/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/factory_reset/test.rp2040.yaml b/tests/components/factory_reset/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/factory_reset/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/fastled_clockless/common.yaml b/tests/components/fastled_clockless/common.yaml new file mode 100644 index 000000000000..8b1447a17a04 --- /dev/null +++ b/tests/components/fastled_clockless/common.yaml @@ -0,0 +1,71 @@ +light: + - platform: fastled_clockless + id: addr1 + chipset: WS2811 + pin: 13 + num_leds: 100 + rgb_order: BRG + max_refresh_rate: 20ms + color_correct: [75%, 100%, 50%] + name: FastLED WS2811 Light + effects: + - addressable_color_wipe: + - addressable_color_wipe: + name: Color Wipe Effect With Custom Values + colors: + - red: 100% + green: 100% + blue: 100% + num_leds: 1 + - red: 0% + green: 0% + blue: 0% + num_leds: 1 + add_led_interval: 100ms + reverse: false + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - addressable_lambda: + name: Test For Custom Lambda Effect + lambda: |- + if (initial_run) { + it[0] = current_color; + } + - automation: + name: Custom Effect + sequence: + - light.addressable_set: + id: addr1 + red: 100% + green: 100% + blue: 0% + - delay: 100ms + - light.addressable_set: + id: addr1 + red: 0% + green: 100% + blue: 0% diff --git a/tests/components/fastled_clockless/test.esp32.yaml b/tests/components/fastled_clockless/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/fastled_clockless/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/fastled_spi/common.yaml b/tests/components/fastled_spi/common.yaml new file mode 100644 index 000000000000..f6f7c5553b43 --- /dev/null +++ b/tests/components/fastled_spi/common.yaml @@ -0,0 +1,71 @@ +light: + - platform: fastled_spi + id: addr1 + chipset: WS2801 + clock_pin: 22 + data_pin: 23 + data_rate: 2MHz + num_leds: 60 + rgb_order: BRG + name: FastLED SPI Light + effects: + - addressable_color_wipe: + - addressable_color_wipe: + name: Color Wipe Effect With Custom Values + colors: + - red: 100% + green: 100% + blue: 100% + num_leds: 1 + - red: 0% + green: 0% + blue: 0% + num_leds: 1 + add_led_interval: 100ms + reverse: false + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - addressable_lambda: + name: Test For Custom Lambda Effect + lambda: |- + if (initial_run) { + it[0] = current_color; + } + - automation: + name: Custom Effect + sequence: + - light.addressable_set: + id: addr1 + red: 100% + green: 100% + blue: 0% + - delay: 100ms + - light.addressable_set: + id: addr1 + red: 0% + green: 100% + blue: 0% diff --git a/tests/components/fastled_spi/test.esp32.yaml b/tests/components/fastled_spi/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/fastled_spi/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/common.yaml b/tests/components/feedback/common.yaml new file mode 100644 index 000000000000..f93d54e8b63d --- /dev/null +++ b/tests/components/feedback/common.yaml @@ -0,0 +1,39 @@ +binary_sensor: + - platform: template + id: open_endstop_sensor + - platform: template + id: open_sensor + - platform: template + id: open_obstacle_sensor + - platform: template + id: close_endstop_sensor + - platform: template + id: close_sensor + - platform: template + id: close_obstacle_sensor + +cover: + - platform: feedback + name: Feedback Cover + id: gate + device_class: gate + infer_endstop_from_movement: false + has_built_in_endstop: false + max_duration: 30s + direction_change_wait_time: 300ms + acceleration_wait_time: 150ms + obstacle_rollback: 10% + open_duration: 22.1s + open_endstop: open_endstop_sensor + open_sensor: open_sensor + open_obstacle_sensor: open_obstacle_sensor + close_duration: 22.4s + close_endstop: close_endstop_sensor + close_sensor: close_sensor + close_obstacle_sensor: close_obstacle_sensor + open_action: + - logger.log: Open Action + close_action: + - logger.log: Close Action + stop_action: + - logger.log: Stop Action diff --git a/tests/components/feedback/test.esp32-c3-idf.yaml b/tests/components/feedback/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/test.esp32-c3.yaml b/tests/components/feedback/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/test.esp32-idf.yaml b/tests/components/feedback/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/test.esp32.yaml b/tests/components/feedback/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/test.esp8266.yaml b/tests/components/feedback/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/feedback/test.rp2040.yaml b/tests/components/feedback/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/feedback/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..e7ac08eb2852 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-c3-idf.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32-c3.yaml b/tests/components/fingerprint_grow/test.esp32-c3.yaml new file mode 100644 index 000000000000..e7ac08eb2852 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-c3.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32-idf.yaml b/tests/components/fingerprint_grow/test.esp32-idf.yaml new file mode 100644 index 000000000000..0950145a0569 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32-idf.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 17 + rx_pin: 16 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 18 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp32.yaml b/tests/components/fingerprint_grow/test.esp32.yaml new file mode 100644 index 000000000000..0950145a0569 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp32.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 17 + rx_pin: 16 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 18 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.esp8266.yaml b/tests/components/fingerprint_grow/test.esp8266.yaml new file mode 100644 index 000000000000..1d00d977b952 --- /dev/null +++ b/tests/components/fingerprint_grow/test.esp8266.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 16 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/fingerprint_grow/test.rp2040.yaml b/tests/components/fingerprint_grow/test.rp2040.yaml new file mode 100644 index 000000000000..e7ac08eb2852 --- /dev/null +++ b/tests/components/fingerprint_grow/test.rp2040.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - fingerprint_grow.enroll: + finger_id: 2 + num_scans: 2 + - fingerprint_grow.cancel_enroll: + - fingerprint_grow.delete: + finger_id: 2 + - fingerprint_grow.delete_all: + +uart: + - id: uart_fingerprint_grow + tx_pin: 4 + rx_pin: 5 + baud_rate: 57600 + +fingerprint_grow: + sensing_pin: 6 + password: 0x12FE37DC + new_password: 0xA65B9840 + on_finger_scan_start: + - logger.log: test_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - logger.log: test_fingerprint_grow_finger_scan_invalid + on_finger_scan_matched: + - logger.log: test_fingerprint_grow_finger_scan_matched + on_finger_scan_unmatched: + - logger.log: test_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - logger.log: test_fingerprint_grow_finger_scan_misplaced + on_enrollment_scan: + - logger.log: test_fingerprint_grow_enrollment_scan + on_enrollment_done: + - logger.log: test_fingerprint_grow_node_enrollment_done + on_enrollment_failed: + - logger.log: test_fingerprint_grow_enrollment_failed + +binary_sensor: + - platform: fingerprint_grow + name: Fingerprint Enrolling + +sensor: + - platform: fingerprint_grow + fingerprint_count: + name: Fingerprint Count + status: + name: Fingerprint Status + capacity: + name: Fingerprint Capacity + security_level: + name: Fingerprint Security Level + last_finger_id: + name: Fingerprint Last Finger ID + last_confidence: + name: Fingerprint Last Confidence diff --git a/tests/components/font/Monocraft.ttf b/tests/components/font/Monocraft.ttf new file mode 100644 index 000000000000..4066b0a9889c Binary files /dev/null and b/tests/components/font/Monocraft.ttf differ diff --git a/tests/components/font/common.yaml b/tests/components/font/common.yaml new file mode 100644 index 000000000000..a81457a05d33 --- /dev/null +++ b/tests/components/font/common.yaml @@ -0,0 +1,38 @@ +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + glyphs: "0123456789." + extras: + - file: "gfonts://Roboto" + glyphs: ["\u00C4", "\u00C5", "\U000000C7"] + - file: "gfonts://Roboto" + id: roboto_web + size: 20 + - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft + size: 20 + - file: + type: web + url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft2 + size: 24 + - file: $component_dir/Monocraft.ttf + id: monocraft3 + size: 28 + +i2c: + scl: ${i2c_scl} + sda: ${i2c_sda} + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: ${display_reset_pin} + lambda: |- + it.print(0, 0, id(roboto), "Hello, World!"); + it.print(0, 20, id(roboto_web), "Hello, World!"); + it.print(0, 40, id(monocraft), "Hello, World!"); + it.print(0, 60, id(monocraft2), "Hello, World!"); + it.print(0, 80, id(monocraft3), "Hello, World!"); diff --git a/tests/components/font/test.esp32-c3-idf.yaml b/tests/components/font/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ad14a2e9a681 --- /dev/null +++ b/tests/components/font/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + display_reset_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/font/test.esp32-c3.yaml b/tests/components/font/test.esp32-c3.yaml new file mode 100644 index 000000000000..ad14a2e9a681 --- /dev/null +++ b/tests/components/font/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + display_reset_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/font/test.esp32-idf.yaml b/tests/components/font/test.esp32-idf.yaml new file mode 100644 index 000000000000..d98600a51ba3 --- /dev/null +++ b/tests/components/font/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + display_reset_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/font/test.esp32.yaml b/tests/components/font/test.esp32.yaml new file mode 100644 index 000000000000..d98600a51ba3 --- /dev/null +++ b/tests/components/font/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + display_reset_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/font/test.esp8266.yaml b/tests/components/font/test.esp8266.yaml new file mode 100644 index 000000000000..ad14a2e9a681 --- /dev/null +++ b/tests/components/font/test.esp8266.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + display_reset_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/font/test.rp2040.yaml b/tests/components/font/test.rp2040.yaml new file mode 100644 index 000000000000..ad14a2e9a681 --- /dev/null +++ b/tests/components/font/test.rp2040.yaml @@ -0,0 +1,7 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + display_reset_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/fs3000/test.esp32-c3-idf.yaml b/tests/components/fs3000/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..69de83b46377 --- /dev/null +++ b/tests/components/fs3000/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32-c3.yaml b/tests/components/fs3000/test.esp32-c3.yaml new file mode 100644 index 000000000000..69de83b46377 --- /dev/null +++ b/tests/components/fs3000/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32-idf.yaml b/tests/components/fs3000/test.esp32-idf.yaml new file mode 100644 index 000000000000..53b49cc9a23f --- /dev/null +++ b/tests/components/fs3000/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 16 + sda: 17 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp32.yaml b/tests/components/fs3000/test.esp32.yaml new file mode 100644 index 000000000000..53b49cc9a23f --- /dev/null +++ b/tests/components/fs3000/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 16 + sda: 17 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.esp8266.yaml b/tests/components/fs3000/test.esp8266.yaml new file mode 100644 index 000000000000..69de83b46377 --- /dev/null +++ b/tests/components/fs3000/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/fs3000/test.rp2040.yaml b/tests/components/fs3000/test.rp2040.yaml new file mode 100644 index 000000000000..69de83b46377 --- /dev/null +++ b/tests/components/fs3000/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_fs3000 + scl: 5 + sda: 4 + +sensor: + - platform: fs3000 + name: Air Velocity + model: 1005 + update_interval: 60s diff --git a/tests/components/ft5x06/test.esp32-c3-idf.yaml b/tests/components/ft5x06/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4fa9532f5352 --- /dev/null +++ b/tests/components/ft5x06/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32-c3.yaml b/tests/components/ft5x06/test.esp32-c3.yaml new file mode 100644 index 000000000000..4fa9532f5352 --- /dev/null +++ b/tests/components/ft5x06/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32-idf.yaml b/tests/components/ft5x06/test.esp32-idf.yaml new file mode 100644 index 000000000000..648929896d98 --- /dev/null +++ b/tests/components/ft5x06/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp32.yaml b/tests/components/ft5x06/test.esp32.yaml new file mode 100644 index 000000000000..648929896d98 --- /dev/null +++ b/tests/components/ft5x06/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.esp8266.yaml b/tests/components/ft5x06/test.esp8266.yaml new file mode 100644 index 000000000000..4fa9532f5352 --- /dev/null +++ b/tests/components/ft5x06/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft5x06/test.rp2040.yaml b/tests/components/ft5x06/test.rp2040.yaml new file mode 100644 index 000000000000..4fa9532f5352 --- /dev/null +++ b/tests/components/ft5x06/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft5x06 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft5x06 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-c3-idf.yaml b/tests/components/ft63x6/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..19ca4cfc19e9 --- /dev/null +++ b/tests/components/ft63x6/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-c3.yaml b/tests/components/ft63x6/test.esp32-c3.yaml new file mode 100644 index 000000000000..19ca4cfc19e9 --- /dev/null +++ b/tests/components/ft63x6/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32-idf.yaml b/tests/components/ft63x6/test.esp32-idf.yaml new file mode 100644 index 000000000000..5ceb107e31ed --- /dev/null +++ b/tests/components/ft63x6/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 18 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.esp32.yaml b/tests/components/ft63x6/test.esp32.yaml new file mode 100644 index 000000000000..32d6634dae9f --- /dev/null +++ b/tests/components/ft63x6/test.esp32.yaml @@ -0,0 +1,38 @@ +spi: + clk_pin: 14 + mosi_pin: 13 + +i2c: + sda: GPIO18 + scl: GPIO19 + +display: + - id: my_display + platform: ili9xxx + dimensions: 480x320 + model: ST7796 + cs_pin: 15 + dc_pin: 21 + reset_pin: 22 + transform: + swap_xy: true + mirror_x: true + mirror_y: true + auto_clear_enabled: false + +touchscreen: + - platform: ft63x6 + interrupt_pin: GPIO39 + transform: + swap_xy: true + mirror_x: false + mirror_y: true + on_touch: + - logger.log: + format: tp touched + on_update: + - logger.log: + format: to updated + on_release: + - logger.log: + format: to released diff --git a/tests/components/ft63x6/test.esp8266.yaml b/tests/components/ft63x6/test.esp8266.yaml new file mode 100644 index 000000000000..19ca4cfc19e9 --- /dev/null +++ b/tests/components/ft63x6/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/ft63x6/test.rp2040.yaml b/tests/components/ft63x6/test.rp2040.yaml new file mode 100644 index 000000000000..19ca4cfc19e9 --- /dev/null +++ b/tests/components/ft63x6/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_ft63x6 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: ft63x6 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/fujitsu_general/test.esp32-c3-idf.yaml b/tests/components/fujitsu_general/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b4146f2a18d9 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32-c3.yaml b/tests/components/fujitsu_general/test.esp32-c3.yaml new file mode 100644 index 000000000000..b4146f2a18d9 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32-idf.yaml b/tests/components/fujitsu_general/test.esp32-idf.yaml new file mode 100644 index 000000000000..b4146f2a18d9 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp32.yaml b/tests/components/fujitsu_general/test.esp32.yaml new file mode 100644 index 000000000000..b4146f2a18d9 --- /dev/null +++ b/tests/components/fujitsu_general/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/fujitsu_general/test.esp8266.yaml b/tests/components/fujitsu_general/test.esp8266.yaml new file mode 100644 index 000000000000..2a05bdde6b0d --- /dev/null +++ b/tests/components/fujitsu_general/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: fujitsu_general + name: Fujitsu General Climate diff --git a/tests/components/gcja5/test.esp32-c3-idf.yaml b/tests/components/gcja5/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ec8765be52d2 --- /dev/null +++ b/tests/components/gcja5/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32-c3.yaml b/tests/components/gcja5/test.esp32-c3.yaml new file mode 100644 index 000000000000..ec8765be52d2 --- /dev/null +++ b/tests/components/gcja5/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32-idf.yaml b/tests/components/gcja5/test.esp32-idf.yaml new file mode 100644 index 000000000000..bc0f89eb9e8d --- /dev/null +++ b/tests/components/gcja5/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp32.yaml b/tests/components/gcja5/test.esp32.yaml new file mode 100644 index 000000000000..bc0f89eb9e8d --- /dev/null +++ b/tests/components/gcja5/test.esp32.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.esp8266.yaml b/tests/components/gcja5/test.esp8266.yaml new file mode 100644 index 000000000000..ec8765be52d2 --- /dev/null +++ b/tests/components/gcja5/test.esp8266.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gcja5/test.rp2040.yaml b/tests/components/gcja5/test.rp2040.yaml new file mode 100644 index 000000000000..ec8765be52d2 --- /dev/null +++ b/tests/components/gcja5/test.rp2040.yaml @@ -0,0 +1,25 @@ +uart: + - id: uart_gcja5 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +sensor: + - platform: gcja5 + pm_1_0: + name: "Particulate Matter <1.0µm Concentration" + pm_2_5: + name: "Particulate Matter <2.5µm Concentration" + pm_10_0: + name: "Particulate Matter <10.0µm Concentration" + pmc_0_5: + name: "PMC 0.5" + pmc_1_0: + name: "PMC 1.0" + pmc_2_5: + name: "PMC 2.5" + pmc_5_0: + name: "PMC 5.0" + pmc_10_0: + name: "PMC 10.0" diff --git a/tests/components/gdk101/common.yaml b/tests/components/gdk101/common.yaml new file mode 100644 index 000000000000..f886fc415b37 --- /dev/null +++ b/tests/components/gdk101/common.yaml @@ -0,0 +1,28 @@ +i2c: + id: i2c_bus + sda: ${i2c_sda} + scl: ${i2c_scl} + +gdk101: + id: my_gdk101 + i2c_id: i2c_bus + +sensor: + - platform: gdk101 + gdk101_id: my_gdk101 + radiation_dose_per_1m: + name: Radiation Dose @ 1 min + radiation_dose_per_10m: + name: Radiation Dose @ 10 min + status: + name: Status + version: + name: FW Version + measurement_duration: + name: Measuring Time + +binary_sensor: + - platform: gdk101 + gdk101_id: my_gdk101 + vibrations: + name: Vibrations diff --git a/tests/components/gdk101/test.esp32-idf.yaml b/tests/components/gdk101/test.esp32-idf.yaml new file mode 100644 index 000000000000..1037d5d35baf --- /dev/null +++ b/tests/components/gdk101/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.esp32.yaml b/tests/components/gdk101/test.esp32.yaml new file mode 100644 index 000000000000..1037d5d35baf --- /dev/null +++ b/tests/components/gdk101/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO16 + i2c_sda: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.esp8266.yaml b/tests/components/gdk101/test.esp8266.yaml new file mode 100644 index 000000000000..d7ae0d5161d4 --- /dev/null +++ b/tests/components/gdk101/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/gdk101/test.rp2040.yaml b/tests/components/gdk101/test.rp2040.yaml new file mode 100644 index 000000000000..d7ae0d5161d4 --- /dev/null +++ b/tests/components/gdk101/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + i2c_scl: GPIO5 + i2c_sda: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/globals/common.yaml b/tests/components/globals/common.yaml new file mode 100644 index 000000000000..224a91a2709d --- /dev/null +++ b/tests/components/globals/common.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - globals.set: + id: glob_int + value: "10" + +globals: + - id: glob_int + type: int + restore_value: true + initial_value: "0" + - id: glob_float + type: float + restore_value: true + initial_value: "0.0f" + - id: glob_bool + type: bool + restore_value: false + initial_value: "true" + - id: glob_string + type: std::string + restore_value: false + # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: false + initial_value: "false" diff --git a/tests/components/globals/test.esp32-c3-idf.yaml b/tests/components/globals/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/globals/test.esp32-c3.yaml b/tests/components/globals/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/globals/test.esp32-idf.yaml b/tests/components/globals/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/globals/test.esp32.yaml b/tests/components/globals/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/globals/test.esp8266.yaml b/tests/components/globals/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/globals/test.rp2040.yaml b/tests/components/globals/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/globals/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/gp8403/test.esp32-c3-idf.yaml b/tests/components/gp8403/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fbc40b948bb8 --- /dev/null +++ b/tests/components/gp8403/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32-c3.yaml b/tests/components/gp8403/test.esp32-c3.yaml new file mode 100644 index 000000000000..fbc40b948bb8 --- /dev/null +++ b/tests/components/gp8403/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32-idf.yaml b/tests/components/gp8403/test.esp32-idf.yaml new file mode 100644 index 000000000000..8470a303e1f8 --- /dev/null +++ b/tests/components/gp8403/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 16 + sda: 17 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp32.yaml b/tests/components/gp8403/test.esp32.yaml new file mode 100644 index 000000000000..8470a303e1f8 --- /dev/null +++ b/tests/components/gp8403/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 16 + sda: 17 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.esp8266.yaml b/tests/components/gp8403/test.esp8266.yaml new file mode 100644 index 000000000000..fbc40b948bb8 --- /dev/null +++ b/tests/components/gp8403/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gp8403/test.rp2040.yaml b/tests/components/gp8403/test.rp2040.yaml new file mode 100644 index 000000000000..fbc40b948bb8 --- /dev/null +++ b/tests/components/gp8403/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_gp8403 + scl: 5 + sda: 4 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 diff --git a/tests/components/gpio/test.esp32-c3-idf.yaml b/tests/components/gpio/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3ca285117d2e --- /dev/null +++ b/tests/components/gpio/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32-c3.yaml b/tests/components/gpio/test.esp32-c3.yaml new file mode 100644 index 000000000000..3ca285117d2e --- /dev/null +++ b/tests/components/gpio/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32-idf.yaml b/tests/components/gpio/test.esp32-idf.yaml new file mode 100644 index 000000000000..30dfa94b6855 --- /dev/null +++ b/tests/components/gpio/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.esp32.yaml b/tests/components/gpio/test.esp32.yaml new file mode 100644 index 000000000000..30dfa94b6855 --- /dev/null +++ b/tests/components/gpio/test.esp32.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.esp8266.yaml b/tests/components/gpio/test.esp8266.yaml new file mode 100644 index 000000000000..30dfa94b6855 --- /dev/null +++ b/tests/components/gpio/test.esp8266.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 12 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 13 + id: gpio_output + +switch: + - platform: gpio + pin: 14 + id: gpio_switch diff --git a/tests/components/gpio/test.rp2040.yaml b/tests/components/gpio/test.rp2040.yaml new file mode 100644 index 000000000000..3ca285117d2e --- /dev/null +++ b/tests/components/gpio/test.rp2040.yaml @@ -0,0 +1,14 @@ +binary_sensor: + - platform: gpio + pin: 2 + id: gpio_binary_sensor + +output: + - platform: gpio + pin: 3 + id: gpio_output + +switch: + - platform: gpio + pin: 4 + id: gpio_switch diff --git a/tests/components/gps/test.esp32-c3.yaml b/tests/components/gps/test.esp32-c3.yaml new file mode 100644 index 000000000000..031f45b87365 --- /dev/null +++ b/tests/components/gps/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.esp32.yaml b/tests/components/gps/test.esp32.yaml new file mode 100644 index 000000000000..c4e4cf9f6fcb --- /dev/null +++ b/tests/components/gps/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.esp8266.yaml b/tests/components/gps/test.esp8266.yaml new file mode 100644 index 000000000000..031f45b87365 --- /dev/null +++ b/tests/components/gps/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/gps/test.rp2040.yaml b/tests/components/gps/test.rp2040.yaml new file mode 100644 index 000000000000..031f45b87365 --- /dev/null +++ b/tests/components/gps/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_gps + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + parity: EVEN + +gps: + +time: + - platform: gps + on_time_sync: + then: + logger.log: "It's time!" diff --git a/tests/components/graph/test.esp32-c3-idf.yaml b/tests/components/graph/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8ce40e84ac12 --- /dev/null +++ b/tests/components/graph/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32-c3.yaml b/tests/components/graph/test.esp32-c3.yaml new file mode 100644 index 000000000000..8ce40e84ac12 --- /dev/null +++ b/tests/components/graph/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32-idf.yaml b/tests/components/graph/test.esp32-idf.yaml new file mode 100644 index 000000000000..8c0c0d4c9e64 --- /dev/null +++ b/tests/components/graph/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 16 + sda: 17 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp32.yaml b/tests/components/graph/test.esp32.yaml new file mode 100644 index 000000000000..8c0c0d4c9e64 --- /dev/null +++ b/tests/components/graph/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 16 + sda: 17 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.esp8266.yaml b/tests/components/graph/test.esp8266.yaml new file mode 100644 index 000000000000..33318355d59e --- /dev/null +++ b/tests/components/graph/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graph/test.rp2040.yaml b/tests/components/graph/test.rp2040.yaml new file mode 100644 index 000000000000..8ce40e84ac12 --- /dev/null +++ b/tests/components/graph/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_graph + scl: 5 + sda: 4 + +sensor: + - platform: template + id: some_sensor + +graph: + - id: some_graph + sensor: some_sensor + duration: 1h + width: 100 + height: 100 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml b/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..23acd4e4d9fa --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-c3-idf.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32-c3.yaml b/tests/components/graphical_display_menu/test.esp32-c3.yaml new file mode 100644 index 000000000000..23acd4e4d9fa --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-c3.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32-idf.yaml b/tests/components/graphical_display_menu/test.esp32-idf.yaml new file mode 100644 index 000000000000..a0897536d7e0 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32-idf.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp32.yaml b/tests/components/graphical_display_menu/test.esp32.yaml new file mode 100644 index 000000000000..a0897536d7e0 --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp32.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.esp8266.yaml b/tests/components/graphical_display_menu/test.esp8266.yaml new file mode 100644 index 000000000000..28c1a7298d0a --- /dev/null +++ b/tests/components/graphical_display_menu/test.esp8266.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/graphical_display_menu/test.rp2040.yaml b/tests/components/graphical_display_menu/test.rp2040.yaml new file mode 100644 index 000000000000..23acd4e4d9fa --- /dev/null +++ b/tests/components/graphical_display_menu/test.rp2040.yaml @@ -0,0 +1,120 @@ +i2c: + - id: i2c_graphical_display_menu + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + id: test_switch + optimistic: true + +graphical_display_menu: + id: test_graphical_display_menu + display: ssd1306_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: test_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/gree/test.esp32-c3-idf.yaml b/tests/components/gree/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..91491d7e1697 --- /dev/null +++ b/tests/components/gree/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32-c3.yaml b/tests/components/gree/test.esp32-c3.yaml new file mode 100644 index 000000000000..91491d7e1697 --- /dev/null +++ b/tests/components/gree/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32-idf.yaml b/tests/components/gree/test.esp32-idf.yaml new file mode 100644 index 000000000000..91491d7e1697 --- /dev/null +++ b/tests/components/gree/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp32.yaml b/tests/components/gree/test.esp32.yaml new file mode 100644 index 000000000000..91491d7e1697 --- /dev/null +++ b/tests/components/gree/test.esp32.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/gree/test.esp8266.yaml b/tests/components/gree/test.esp8266.yaml new file mode 100644 index 000000000000..d0542973ce0b --- /dev/null +++ b/tests/components/gree/test.esp8266.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: gree + name: GREE + model: generic diff --git a/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml b/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ef6dff6539a0 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32-c3.yaml b/tests/components/grove_tb6612fng/test.esp32-c3.yaml new file mode 100644 index 000000000000..ef6dff6539a0 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32-idf.yaml b/tests/components/grove_tb6612fng/test.esp32-idf.yaml new file mode 100644 index 000000000000..3271fb754f6d --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 16 + sda: 17 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp32.yaml b/tests/components/grove_tb6612fng/test.esp32.yaml new file mode 100644 index 000000000000..3271fb754f6d --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp32.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 16 + sda: 17 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.esp8266.yaml b/tests/components/grove_tb6612fng/test.esp8266.yaml new file mode 100644 index 000000000000..ef6dff6539a0 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.esp8266.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/grove_tb6612fng/test.rp2040.yaml b/tests/components/grove_tb6612fng/test.rp2040.yaml new file mode 100644 index 000000000000..ef6dff6539a0 --- /dev/null +++ b/tests/components/grove_tb6612fng/test.rp2040.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - grove_tb6612fng.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor + - grove_tb6612fng.stop: + channel: 1 + id: test_motor + - grove_tb6612fng.break: + channel: 1 + id: test_motor + +i2c: + - id: i2c_grove_tb6612fng + scl: 5 + sda: 4 + +grove_tb6612fng: + id: test_motor + address: 0x14 diff --git a/tests/components/growatt_solar/test.esp32-c3-idf.yaml b/tests/components/growatt_solar/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7e738978567e --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-c3-idf.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32-c3.yaml b/tests/components/growatt_solar/test.esp32-c3.yaml new file mode 100644 index 000000000000..7e738978567e --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-c3.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32-idf.yaml b/tests/components/growatt_solar/test.esp32-idf.yaml new file mode 100644 index 000000000000..654f2ccedff2 --- /dev/null +++ b/tests/components/growatt_solar/test.esp32-idf.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp32.yaml b/tests/components/growatt_solar/test.esp32.yaml new file mode 100644 index 000000000000..654f2ccedff2 --- /dev/null +++ b/tests/components/growatt_solar/test.esp32.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.esp8266.yaml b/tests/components/growatt_solar/test.esp8266.yaml new file mode 100644 index 000000000000..a1cf8267ae32 --- /dev/null +++ b/tests/components/growatt_solar/test.esp8266.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/growatt_solar/test.rp2040.yaml b/tests/components/growatt_solar/test.rp2040.yaml new file mode 100644 index 000000000000..7e738978567e --- /dev/null +++ b/tests/components/growatt_solar/test.rp2040.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_growatt_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: growatt_solar + update_interval: 10s + protocol_version: RTU + inverter_status: + name: Growatt Status Code + phase_a: + voltage: + name: Growatt Voltage Phase A + current: + name: Growatt Current Phase A + active_power: + name: Growatt Power Phase A + phase_b: + voltage: + name: Growatt Voltage Phase B + current: + name: Growatt Current Phase B + active_power: + name: Growatt Power Phase B + phase_c: + voltage: + name: Growatt Voltage Phase C + current: + name: Growatt Current Phase C + active_power: + name: Growatt Power Phase C + pv1: + voltage: + name: Growatt PV1 Voltage + current: + name: Growatt PV1 Current + active_power: + name: Growatt PV1 Active Power + pv2: + voltage: + name: Growatt PV2 Voltage + current: + name: Growatt PV2 Current + active_power: + name: Growatt PV2 Active Power + active_power: + name: Growatt Grid Active Power + pv_active_power: + name: Growatt PV Active Power + frequency: + name: Growatt Frequency + energy_production_day: + name: Growatt Today's Generation + total_energy_production: + name: Growatt Total Energy Production + inverter_module_temp: + name: Growatt Inverter Module Temp diff --git a/tests/components/gt911/test.esp32-c3-idf.yaml b/tests/components/gt911/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..43f7ac5902b9 --- /dev/null +++ b/tests/components/gt911/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32-c3.yaml b/tests/components/gt911/test.esp32-c3.yaml new file mode 100644 index 000000000000..43f7ac5902b9 --- /dev/null +++ b/tests/components/gt911/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32-idf.yaml b/tests/components/gt911/test.esp32-idf.yaml new file mode 100644 index 000000000000..a47f7bf2607b --- /dev/null +++ b/tests/components/gt911/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 14 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32.yaml b/tests/components/gt911/test.esp32.yaml new file mode 100644 index 000000000000..a47f7bf2607b --- /dev/null +++ b/tests/components/gt911/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 14 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp8266.yaml b/tests/components/gt911/test.esp8266.yaml new file mode 100644 index 000000000000..8b76eff29e5e --- /dev/null +++ b/tests/components/gt911/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 12 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.rp2040.yaml b/tests/components/gt911/test.rp2040.yaml new file mode 100644 index 000000000000..43f7ac5902b9 --- /dev/null +++ b/tests/components/gt911/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 6 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/haier/test.esp32-c3-idf.yaml b/tests/components/haier/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..72cfb781a7ad --- /dev/null +++ b/tests/components/haier/test.esp32-c3-idf.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32-c3.yaml b/tests/components/haier/test.esp32-c3.yaml new file mode 100644 index 000000000000..72cfb781a7ad --- /dev/null +++ b/tests/components/haier/test.esp32-c3.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32-idf.yaml b/tests/components/haier/test.esp32-idf.yaml new file mode 100644 index 000000000000..d3eeb04d655e --- /dev/null +++ b/tests/components/haier/test.esp32-idf.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp32.yaml b/tests/components/haier/test.esp32.yaml new file mode 100644 index 000000000000..d3eeb04d655e --- /dev/null +++ b/tests/components/haier/test.esp32.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.esp8266.yaml b/tests/components/haier/test.esp8266.yaml new file mode 100644 index 000000000000..72cfb781a7ad --- /dev/null +++ b/tests/components/haier/test.esp8266.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/haier/test.rp2040.yaml b/tests/components/haier/test.rp2040.yaml new file mode 100644 index 000000000000..72cfb781a7ad --- /dev/null +++ b/tests/components/haier/test.rp2040.yaml @@ -0,0 +1,95 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status diff --git a/tests/components/havells_solar/test.esp32-c3-idf.yaml b/tests/components/havells_solar/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5cb911cf7111 --- /dev/null +++ b/tests/components/havells_solar/test.esp32-c3-idf.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/havells_solar/test.esp32-c3.yaml b/tests/components/havells_solar/test.esp32-c3.yaml new file mode 100644 index 000000000000..5cb911cf7111 --- /dev/null +++ b/tests/components/havells_solar/test.esp32-c3.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/havells_solar/test.esp32-idf.yaml b/tests/components/havells_solar/test.esp32-idf.yaml new file mode 100644 index 000000000000..2cda8e37be78 --- /dev/null +++ b/tests/components/havells_solar/test.esp32-idf.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/havells_solar/test.esp32.yaml b/tests/components/havells_solar/test.esp32.yaml new file mode 100644 index 000000000000..2cda8e37be78 --- /dev/null +++ b/tests/components/havells_solar/test.esp32.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/havells_solar/test.esp8266.yaml b/tests/components/havells_solar/test.esp8266.yaml new file mode 100644 index 000000000000..5cb911cf7111 --- /dev/null +++ b/tests/components/havells_solar/test.esp8266.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/havells_solar/test.rp2040.yaml b/tests/components/havells_solar/test.rp2040.yaml new file mode 100644 index 000000000000..5cb911cf7111 --- /dev/null +++ b/tests/components/havells_solar/test.rp2040.yaml @@ -0,0 +1,79 @@ +uart: + - id: uart_havells_solar + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: havells_solar + update_interval: 60s + phase_a: + voltage: + name: Phase A Voltage + current: + name: Phase A Current + phase_b: + voltage: + name: Voltage Phase B + current: + name: Current Phase B + phase_c: + voltage: + name: Voltage Phase C + current: + name: Current Phase C + pv1: + voltage: + name: PV1 Voltage + current: + name: PV1 Current + active_power: + name: PV1 Active Power + voltage_sampled_by_secondary_cpu: + name: PV1 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV1 Insulation Of +VE To Ground + pv2: + voltage: + name: PV2 Voltage + current: + name: PV2 Current + active_power: + name: PV2 Active Power + voltage_sampled_by_secondary_cpu: + name: PV2 Voltage Sampled By Secondary CPU + insulation_of_p_to_ground: + name: PV2 Insulation Of +VE To Ground + active_power: + name: Active Power + reactive_power: + name: Reactive Power + frequency: + name: Frequency + energy_production_day: + name: Today's Generation + total_energy_production: + name: Total Energy Production + total_generation_time: + name: Total Generation Time + today_generation_time: + name: Today Generation Time + inverter_module_temp: + name: Inverter Module Temp + inverter_inner_temp: + name: Inverter Inner Temp + inverter_bus_voltage: + name: Inverter BUS Voltage + insulation_of_pv_n_to_ground: + name: Insulation Of PV- To Ground + gfci_value: + name: GFCI Value + dci_of_r: + name: DCI Of R + dci_of_s: + name: DCI Of S + dci_of_t: + name: DCI Of T diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..70cfd6ab6f73 --- /dev/null +++ b/tests/components/hbridge/test.esp32-c3-idf.yaml @@ -0,0 +1,33 @@ +output: + - platform: ledc + pin: 4 + id: gpio_output1 + - platform: ledc + pin: 5 + id: gpio_output2 + - platform: ledc + pin: 6 + id: gpio_output3 + - platform: ledc + pin: 7 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hbridge/test.esp32-c3.yaml b/tests/components/hbridge/test.esp32-c3.yaml new file mode 100644 index 000000000000..70cfd6ab6f73 --- /dev/null +++ b/tests/components/hbridge/test.esp32-c3.yaml @@ -0,0 +1,33 @@ +output: + - platform: ledc + pin: 4 + id: gpio_output1 + - platform: ledc + pin: 5 + id: gpio_output2 + - platform: ledc + pin: 6 + id: gpio_output3 + - platform: ledc + pin: 7 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hbridge/test.esp32-idf.yaml b/tests/components/hbridge/test.esp32-idf.yaml new file mode 100644 index 000000000000..6a80aaaf3b15 --- /dev/null +++ b/tests/components/hbridge/test.esp32-idf.yaml @@ -0,0 +1,33 @@ +output: + - platform: ledc + pin: 14 + id: gpio_output1 + - platform: ledc + pin: 15 + id: gpio_output2 + - platform: ledc + pin: 12 + id: gpio_output3 + - platform: ledc + pin: 13 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hbridge/test.esp32.yaml b/tests/components/hbridge/test.esp32.yaml new file mode 100644 index 000000000000..6a80aaaf3b15 --- /dev/null +++ b/tests/components/hbridge/test.esp32.yaml @@ -0,0 +1,33 @@ +output: + - platform: ledc + pin: 14 + id: gpio_output1 + - platform: ledc + pin: 15 + id: gpio_output2 + - platform: ledc + pin: 12 + id: gpio_output3 + - platform: ledc + pin: 13 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hbridge/test.esp8266.yaml b/tests/components/hbridge/test.esp8266.yaml new file mode 100644 index 000000000000..4f8915879de1 --- /dev/null +++ b/tests/components/hbridge/test.esp8266.yaml @@ -0,0 +1,33 @@ +output: + - platform: esp8266_pwm + pin: 4 + id: gpio_output1 + - platform: esp8266_pwm + pin: 5 + id: gpio_output2 + - platform: esp8266_pwm + pin: 12 + id: gpio_output3 + - platform: esp8266_pwm + pin: 13 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hbridge/test.rp2040.yaml b/tests/components/hbridge/test.rp2040.yaml new file mode 100644 index 000000000000..e21b55091d45 --- /dev/null +++ b/tests/components/hbridge/test.rp2040.yaml @@ -0,0 +1,33 @@ +output: + - platform: rp2040_pwm + pin: 4 + id: gpio_output1 + - platform: rp2040_pwm + pin: 5 + id: gpio_output2 + - platform: rp2040_pwm + pin: 6 + id: gpio_output3 + - platform: rp2040_pwm + pin: 7 + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! diff --git a/tests/components/hdc1080/test.esp32-c3-idf.yaml b/tests/components/hdc1080/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7bf7af6fa7f1 --- /dev/null +++ b/tests/components/hdc1080/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 5 + sda: 4 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc1080/test.esp32-c3.yaml b/tests/components/hdc1080/test.esp32-c3.yaml new file mode 100644 index 000000000000..7bf7af6fa7f1 --- /dev/null +++ b/tests/components/hdc1080/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 5 + sda: 4 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc1080/test.esp32-idf.yaml b/tests/components/hdc1080/test.esp32-idf.yaml new file mode 100644 index 000000000000..8e313dfa40ef --- /dev/null +++ b/tests/components/hdc1080/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 16 + sda: 17 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc1080/test.esp32.yaml b/tests/components/hdc1080/test.esp32.yaml new file mode 100644 index 000000000000..8e313dfa40ef --- /dev/null +++ b/tests/components/hdc1080/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 16 + sda: 17 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc1080/test.esp8266.yaml b/tests/components/hdc1080/test.esp8266.yaml new file mode 100644 index 000000000000..7bf7af6fa7f1 --- /dev/null +++ b/tests/components/hdc1080/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 5 + sda: 4 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/hdc1080/test.rp2040.yaml b/tests/components/hdc1080/test.rp2040.yaml new file mode 100644 index 000000000000..7bf7af6fa7f1 --- /dev/null +++ b/tests/components/hdc1080/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hdc1080 + scl: 5 + sda: 4 + +sensor: + - platform: hdc1080 + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/he60r/test.esp32-c3-idf.yaml b/tests/components/he60r/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..bcbb5444420e --- /dev/null +++ b/tests/components/he60r/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/he60r/test.esp32-c3.yaml b/tests/components/he60r/test.esp32-c3.yaml new file mode 100644 index 000000000000..bcbb5444420e --- /dev/null +++ b/tests/components/he60r/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/he60r/test.esp32-idf.yaml b/tests/components/he60r/test.esp32-idf.yaml new file mode 100644 index 000000000000..840387ae36c9 --- /dev/null +++ b/tests/components/he60r/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 17 + rx_pin: 16 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/he60r/test.esp32.yaml b/tests/components/he60r/test.esp32.yaml new file mode 100644 index 000000000000..840387ae36c9 --- /dev/null +++ b/tests/components/he60r/test.esp32.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 17 + rx_pin: 16 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/he60r/test.esp8266.yaml b/tests/components/he60r/test.esp8266.yaml new file mode 100644 index 000000000000..bcbb5444420e --- /dev/null +++ b/tests/components/he60r/test.esp8266.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/he60r/test.rp2040.yaml b/tests/components/he60r/test.rp2040.yaml new file mode 100644 index 000000000000..bcbb5444420e --- /dev/null +++ b/tests/components/he60r/test.rp2040.yaml @@ -0,0 +1,13 @@ +uart: + - id: uart_he60r + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +cover: + - platform: he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s diff --git a/tests/components/heatpumpir/test.esp32.yaml b/tests/components/heatpumpir/test.esp32.yaml new file mode 100644 index 000000000000..db3f81f6a0a1 --- /dev/null +++ b/tests/components/heatpumpir/test.esp32.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 + - platform: heatpumpir + protocol: greeyt + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/heatpumpir/test.esp8266.yaml b/tests/components/heatpumpir/test.esp8266.yaml new file mode 100644 index 000000000000..26a01cb1980f --- /dev/null +++ b/tests/components/heatpumpir/test.esp8266.yaml @@ -0,0 +1,19 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 + - platform: heatpumpir + protocol: greeyt + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 diff --git a/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml b/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..684d5899de26 --- /dev/null +++ b/tests/components/hitachi_ac344/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac344 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac344/test.esp32-c3.yaml b/tests/components/hitachi_ac344/test.esp32-c3.yaml new file mode 100644 index 000000000000..684d5899de26 --- /dev/null +++ b/tests/components/hitachi_ac344/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac344 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac344/test.esp32-idf.yaml b/tests/components/hitachi_ac344/test.esp32-idf.yaml new file mode 100644 index 000000000000..684d5899de26 --- /dev/null +++ b/tests/components/hitachi_ac344/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac344 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac344/test.esp32.yaml b/tests/components/hitachi_ac344/test.esp32.yaml new file mode 100644 index 000000000000..684d5899de26 --- /dev/null +++ b/tests/components/hitachi_ac344/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac344 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac344/test.esp8266.yaml b/tests/components/hitachi_ac344/test.esp8266.yaml new file mode 100644 index 000000000000..e6203e30844c --- /dev/null +++ b/tests/components/hitachi_ac344/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac344 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml b/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a09821b9c6c1 --- /dev/null +++ b/tests/components/hitachi_ac424/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac424 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac424/test.esp32-c3.yaml b/tests/components/hitachi_ac424/test.esp32-c3.yaml new file mode 100644 index 000000000000..a09821b9c6c1 --- /dev/null +++ b/tests/components/hitachi_ac424/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac424 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac424/test.esp32-idf.yaml b/tests/components/hitachi_ac424/test.esp32-idf.yaml new file mode 100644 index 000000000000..a09821b9c6c1 --- /dev/null +++ b/tests/components/hitachi_ac424/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac424 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac424/test.esp32.yaml b/tests/components/hitachi_ac424/test.esp32.yaml new file mode 100644 index 000000000000..a09821b9c6c1 --- /dev/null +++ b/tests/components/hitachi_ac424/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac424 + name: Hitachi Climate diff --git a/tests/components/hitachi_ac424/test.esp8266.yaml b/tests/components/hitachi_ac424/test.esp8266.yaml new file mode 100644 index 000000000000..78b9e7c98c6c --- /dev/null +++ b/tests/components/hitachi_ac424/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: hitachi_ac424 + name: Hitachi Climate diff --git a/tests/components/hlw8012/test.esp32-c3-idf.yaml b/tests/components/hlw8012/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..da6053a1b9de --- /dev/null +++ b/tests/components/hlw8012/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 2 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hlw8012/test.esp32-c3.yaml b/tests/components/hlw8012/test.esp32-c3.yaml new file mode 100644 index 000000000000..da6053a1b9de --- /dev/null +++ b/tests/components/hlw8012/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 2 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hlw8012/test.esp32-idf.yaml b/tests/components/hlw8012/test.esp32-idf.yaml new file mode 100644 index 000000000000..5b2d86572255 --- /dev/null +++ b/tests/components/hlw8012/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 12 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hlw8012/test.esp32.yaml b/tests/components/hlw8012/test.esp32.yaml new file mode 100644 index 000000000000..5b2d86572255 --- /dev/null +++ b/tests/components/hlw8012/test.esp32.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 12 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hlw8012/test.esp8266.yaml b/tests/components/hlw8012/test.esp8266.yaml new file mode 100644 index 000000000000..5b2d86572255 --- /dev/null +++ b/tests/components/hlw8012/test.esp8266.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 12 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hlw8012/test.rp2040.yaml b/tests/components/hlw8012/test.rp2040.yaml new file mode 100644 index 000000000000..da6053a1b9de --- /dev/null +++ b/tests/components/hlw8012/test.rp2040.yaml @@ -0,0 +1,21 @@ +sensor: + - platform: hlw8012 + model: hlw8012 + sel_pin: 2 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE diff --git a/tests/components/hm3301/test.esp32-c3-idf.yaml b/tests/components/hm3301/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..eb6c4a14e67a --- /dev/null +++ b/tests/components/hm3301/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 5 + sda: 4 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hm3301/test.esp32-c3.yaml b/tests/components/hm3301/test.esp32-c3.yaml new file mode 100644 index 000000000000..eb6c4a14e67a --- /dev/null +++ b/tests/components/hm3301/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 5 + sda: 4 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hm3301/test.esp32-idf.yaml b/tests/components/hm3301/test.esp32-idf.yaml new file mode 100644 index 000000000000..413e88a26525 --- /dev/null +++ b/tests/components/hm3301/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 16 + sda: 17 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hm3301/test.esp32.yaml b/tests/components/hm3301/test.esp32.yaml new file mode 100644 index 000000000000..413e88a26525 --- /dev/null +++ b/tests/components/hm3301/test.esp32.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 16 + sda: 17 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hm3301/test.esp8266.yaml b/tests/components/hm3301/test.esp8266.yaml new file mode 100644 index 000000000000..eb6c4a14e67a --- /dev/null +++ b/tests/components/hm3301/test.esp8266.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 5 + sda: 4 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hm3301/test.rp2040.yaml b/tests/components/hm3301/test.rp2040.yaml new file mode 100644 index 000000000000..eb6c4a14e67a --- /dev/null +++ b/tests/components/hm3301/test.rp2040.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_hm3301 + scl: 5 + sda: 4 + +sensor: + - platform: hm3301 + pm_1_0: + name: PM1.0 + pm_2_5: + name: PM2.5 + pm_10_0: + name: PM10.0 + aqi: + name: AQI + calculation_type: CAQI diff --git a/tests/components/hmc5883l/test.esp32-c3-idf.yaml b/tests/components/hmc5883l/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e65b2e5c5b23 --- /dev/null +++ b/tests/components/hmc5883l/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/hmc5883l/test.esp32-c3.yaml b/tests/components/hmc5883l/test.esp32-c3.yaml new file mode 100644 index 000000000000..e65b2e5c5b23 --- /dev/null +++ b/tests/components/hmc5883l/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/hmc5883l/test.esp32-idf.yaml b/tests/components/hmc5883l/test.esp32-idf.yaml new file mode 100644 index 000000000000..db632fc4114b --- /dev/null +++ b/tests/components/hmc5883l/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/hmc5883l/test.esp32.yaml b/tests/components/hmc5883l/test.esp32.yaml new file mode 100644 index 000000000000..db632fc4114b --- /dev/null +++ b/tests/components/hmc5883l/test.esp32.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/hmc5883l/test.esp8266.yaml b/tests/components/hmc5883l/test.esp8266.yaml new file mode 100644 index 000000000000..e65b2e5c5b23 --- /dev/null +++ b/tests/components/hmc5883l/test.esp8266.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/hmc5883l/test.rp2040.yaml b/tests/components/hmc5883l/test.rp2040.yaml new file mode 100644 index 000000000000..e65b2e5c5b23 --- /dev/null +++ b/tests/components/hmc5883l/test.rp2040.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_hmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: hmc5883l + address: 0x68 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z + heading: + name: HMC5883L Heading + range: 130uT + oversampling: 8x + update_interval: 15s diff --git a/tests/components/homeassistant/common.yaml b/tests/components/homeassistant/common.yaml new file mode 100644 index 000000000000..ae016a3beaa4 --- /dev/null +++ b/tests/components/homeassistant/common.yaml @@ -0,0 +1,67 @@ +esphome: + on_boot: + then: + - homeassistant.event: + event: esphome.button_pressed + data: + message: Button was pressed + - homeassistant.event: + event: esphome.html5 + data: + message: New Humidity + data_template: + message: The humidity is {{ my_variable }}%. + variables: + my_variable: "return id(ha_hello_world_temperature).state;" + - homeassistant.service: + service: notify.html5 + data: + message: Button was pressed + - homeassistant.service: + service: notify.html5 + data: + title: New Humidity + data_template: + message: The humidity is {{ my_variable }}%. + variables: + my_variable: "return id(ha_hello_world_temperature).state;" + +wifi: + ssid: MySSID + password: password1 + +api: + +binary_sensor: + - platform: homeassistant + entity_id: binary_sensor.hello_world + id: ha_hello_world_binary + - platform: homeassistant + entity_id: binary_sensor.hello + attribute: world + id: ha_hello_world_binary_attribute + +sensor: + - platform: homeassistant + entity_id: sensor.hello_world + id: ha_hello_world + - platform: homeassistant + entity_id: climate.living_room + attribute: temperature + id: ha_hello_world_temperature + +text_sensor: + - platform: homeassistant + entity_id: sensor.hello_world + id: ha_hello_world_text + - platform: homeassistant + entity_id: sensor.hello_world1 + id: ha_hello_world_text2 + attribute: some_attribute + +time: + - platform: homeassistant + on_time: + - at: "16:00:00" + then: + - logger.log: It's 16:00 diff --git a/tests/components/homeassistant/test.bk72xx.yaml b/tests/components/homeassistant/test.bk72xx.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.bk72xx.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.esp32-c3-idf.yaml b/tests/components/homeassistant/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.esp32-c3.yaml b/tests/components/homeassistant/test.esp32-c3.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.esp32-c3.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.esp32-idf.yaml b/tests/components/homeassistant/test.esp32-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.esp32.yaml b/tests/components/homeassistant/test.esp32.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.esp32.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.esp8266.yaml b/tests/components/homeassistant/test.esp8266.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.esp8266.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/homeassistant/test.rp2040.yaml b/tests/components/homeassistant/test.rp2040.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/homeassistant/test.rp2040.yaml @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000000..5dae3d5d523d --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 5 + sda: 4 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywell_hih_i2c/test.esp32-c3.yaml b/tests/components/honeywell_hih_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..5dae3d5d523d --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 5 + sda: 4 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywell_hih_i2c/test.esp32-idf.yaml b/tests/components/honeywell_hih_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..0119aec3f367 --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 16 + sda: 17 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywell_hih_i2c/test.esp32.yaml b/tests/components/honeywell_hih_i2c/test.esp32.yaml new file mode 100644 index 000000000000..0119aec3f367 --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 16 + sda: 17 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywell_hih_i2c/test.esp8266.yaml b/tests/components/honeywell_hih_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..5dae3d5d523d --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 5 + sda: 4 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywell_hih_i2c/test.rp2040.yaml b/tests/components/honeywell_hih_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..5dae3d5d523d --- /dev/null +++ b/tests/components/honeywell_hih_i2c/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_honeywell_hih + scl: 5 + sda: 4 + +sensor: + - platform: honeywell_hih_i2c + temperature: + name: Temperature + humidity: + name: Humidity + update_interval: 15s diff --git a/tests/components/honeywellabp/test.esp32-c3-idf.yaml b/tests/components/honeywellabp/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a53e3296dd0a --- /dev/null +++ b/tests/components/honeywellabp/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_honeywellabp + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: honeywellabp + cs_pin: 8 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp/test.esp32-c3.yaml b/tests/components/honeywellabp/test.esp32-c3.yaml new file mode 100644 index 000000000000..a53e3296dd0a --- /dev/null +++ b/tests/components/honeywellabp/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_honeywellabp + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: honeywellabp + cs_pin: 8 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp/test.esp32-idf.yaml b/tests/components/honeywellabp/test.esp32-idf.yaml new file mode 100644 index 000000000000..6bf9fa0f4d80 --- /dev/null +++ b/tests/components/honeywellabp/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: honeywellabp + cs_pin: 12 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp/test.esp32.yaml b/tests/components/honeywellabp/test.esp32.yaml new file mode 100644 index 000000000000..6bf9fa0f4d80 --- /dev/null +++ b/tests/components/honeywellabp/test.esp32.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: honeywellabp + cs_pin: 12 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp/test.esp8266.yaml b/tests/components/honeywellabp/test.esp8266.yaml new file mode 100644 index 000000000000..31988c035ef1 --- /dev/null +++ b/tests/components/honeywellabp/test.esp8266.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_bme280 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: honeywellabp + cs_pin: 15 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp/test.rp2040.yaml b/tests/components/honeywellabp/test.rp2040.yaml new file mode 100644 index 000000000000..2e0c471fa0d6 --- /dev/null +++ b/tests/components/honeywellabp/test.rp2040.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_bme280 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: honeywellabp + cs_pin: 6 + pressure: + name: Honeywell pressure + min_pressure: 0 + max_pressure: 15 + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml b/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b47411c23840 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 5 + sda: 4 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.esp32-c3.yaml b/tests/components/honeywellabp2_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..b47411c23840 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 5 + sda: 4 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.esp32-idf.yaml b/tests/components/honeywellabp2_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..0f0d61ca06b7 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 16 + sda: 17 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.esp32.yaml b/tests/components/honeywellabp2_i2c/test.esp32.yaml new file mode 100644 index 000000000000..0f0d61ca06b7 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 16 + sda: 17 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.esp8266.yaml b/tests/components/honeywellabp2_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..b47411c23840 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 5 + sda: 4 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/honeywellabp2_i2c/test.rp2040.yaml b/tests/components/honeywellabp2_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..b47411c23840 --- /dev/null +++ b/tests/components/honeywellabp2_i2c/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_honeywellabp2 + scl: 5 + sda: 4 + +sensor: + - platform: honeywellabp2_i2c + address: 0x28 + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature diff --git a/tests/components/host/common.yaml b/tests/components/host/common.yaml new file mode 100644 index 000000000000..3d14c190a68d --- /dev/null +++ b/tests/components/host/common.yaml @@ -0,0 +1,15 @@ +time: + - platform: sntp + id: esptime + timezone: Australia/Sydney + +logger: + level: VERBOSE + logs: + lvgl: INFO + display: DEBUG + sensor: INFO + vnc: DEBUG + +host: + mac_address: "62:23:45:AF:B3:DD" diff --git a/tests/components/host/test.host.yaml b/tests/components/host/test.host.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/host/test.host.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..729cb961205e --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hrxl_maxsonar_wr/test.esp32-c3.yaml b/tests/components/hrxl_maxsonar_wr/test.esp32-c3.yaml new file mode 100644 index 000000000000..729cb961205e --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hrxl_maxsonar_wr/test.esp32-idf.yaml b/tests/components/hrxl_maxsonar_wr/test.esp32-idf.yaml new file mode 100644 index 000000000000..da283cc07202 --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hrxl_maxsonar_wr/test.esp32.yaml b/tests/components/hrxl_maxsonar_wr/test.esp32.yaml new file mode 100644 index 000000000000..da283cc07202 --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.esp32.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hrxl_maxsonar_wr/test.esp8266.yaml b/tests/components/hrxl_maxsonar_wr/test.esp8266.yaml new file mode 100644 index 000000000000..729cb961205e --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.esp8266.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hrxl_maxsonar_wr/test.rp2040.yaml b/tests/components/hrxl_maxsonar_wr/test.rp2040.yaml new file mode 100644 index 000000000000..729cb961205e --- /dev/null +++ b/tests/components/hrxl_maxsonar_wr/test.rp2040.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_hrxl_maxsonar_wr + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: hrxl_maxsonar_wr + id: hrxl_maxsonar_wr_sensor + name: Rainwater Tank Level diff --git a/tests/components/hte501/test.esp32-c3-idf.yaml b/tests/components/hte501/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e14b589dbd6a --- /dev/null +++ b/tests/components/hte501/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 5 + sda: 4 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hte501/test.esp32-c3.yaml b/tests/components/hte501/test.esp32-c3.yaml new file mode 100644 index 000000000000..e14b589dbd6a --- /dev/null +++ b/tests/components/hte501/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 5 + sda: 4 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hte501/test.esp32-idf.yaml b/tests/components/hte501/test.esp32-idf.yaml new file mode 100644 index 000000000000..83e4d26603ca --- /dev/null +++ b/tests/components/hte501/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 16 + sda: 17 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hte501/test.esp32.yaml b/tests/components/hte501/test.esp32.yaml new file mode 100644 index 000000000000..83e4d26603ca --- /dev/null +++ b/tests/components/hte501/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 16 + sda: 17 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hte501/test.esp8266.yaml b/tests/components/hte501/test.esp8266.yaml new file mode 100644 index 000000000000..e14b589dbd6a --- /dev/null +++ b/tests/components/hte501/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 5 + sda: 4 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hte501/test.rp2040.yaml b/tests/components/hte501/test.rp2040.yaml new file mode 100644 index 000000000000..e14b589dbd6a --- /dev/null +++ b/tests/components/hte501/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_hte501 + scl: 5 + sda: 4 + +sensor: + - platform: hte501 + address: 0x40 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/http_request/common.yaml b/tests/components/http_request/common.yaml new file mode 100644 index 000000000000..848fe3f509fb --- /dev/null +++ b/tests/components/http_request/common.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - http_request.get: + url: https://esphome.io + headers: + Content-Type: application/json + verify_ssl: false + on_response: + then: + - logger.log: + format: 'Response status: %d, Duration: %u ms' + args: + - status_code + - duration_ms + - http_request.post: + url: https://esphome.io + headers: + Content-Type: application/json + json: + key: value + verify_ssl: false + - http_request.send: + method: PUT + url: https://esphome.io + headers: + Content-Type: application/json + body: "Some data" + verify_ssl: false + +wifi: + ssid: MySSID + password: password1 + +http_request: + useragent: esphome/tagreader + timeout: 10s diff --git a/tests/components/http_request/test.esp32-c3.yaml b/tests/components/http_request/test.esp32-c3.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/http_request/test.esp32-c3.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/http_request/test.esp32.yaml b/tests/components/http_request/test.esp32.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/http_request/test.esp32.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/http_request/test.esp8266.yaml b/tests/components/http_request/test.esp8266.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/http_request/test.esp8266.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/htu21d/test.esp32-c3-idf.yaml b/tests/components/htu21d/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8131d1366154 --- /dev/null +++ b/tests/components/htu21d/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 5 + sda: 4 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu21d/test.esp32-c3.yaml b/tests/components/htu21d/test.esp32-c3.yaml new file mode 100644 index 000000000000..8131d1366154 --- /dev/null +++ b/tests/components/htu21d/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 5 + sda: 4 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu21d/test.esp32-idf.yaml b/tests/components/htu21d/test.esp32-idf.yaml new file mode 100644 index 000000000000..6655a1cc1a56 --- /dev/null +++ b/tests/components/htu21d/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 16 + sda: 17 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu21d/test.esp32.yaml b/tests/components/htu21d/test.esp32.yaml new file mode 100644 index 000000000000..6655a1cc1a56 --- /dev/null +++ b/tests/components/htu21d/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 16 + sda: 17 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu21d/test.esp8266.yaml b/tests/components/htu21d/test.esp8266.yaml new file mode 100644 index 000000000000..8131d1366154 --- /dev/null +++ b/tests/components/htu21d/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 5 + sda: 4 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu21d/test.rp2040.yaml b/tests/components/htu21d/test.rp2040.yaml new file mode 100644 index 000000000000..8131d1366154 --- /dev/null +++ b/tests/components/htu21d/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_htu21d + scl: 5 + sda: 4 + +sensor: + - platform: htu21d + model: htu21d + temperature: + name: Temperature + humidity: + name: Humidity + heater: + name: Heater + update_interval: 15s diff --git a/tests/components/htu31d/common.yaml b/tests/components/htu31d/common.yaml new file mode 100644 index 000000000000..c8ef2fede95c --- /dev/null +++ b/tests/components/htu31d/common.yaml @@ -0,0 +1,9 @@ +i2c: + +sensor: + - platform: htu31d + temperature: + name: Living Room Temperature 7 + humidity: + name: Living Room Humidity 7 + update_interval: 15s diff --git a/tests/components/htu31d/test.esp32-idf.yaml b/tests/components/htu31d/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/htu31d/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/htu31d/test.esp32.yaml b/tests/components/htu31d/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/htu31d/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/htu31d/test.esp8266.yaml b/tests/components/htu31d/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/htu31d/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hx711/test.esp32-c3-idf.yaml b/tests/components/hx711/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..985041744068 --- /dev/null +++ b/tests/components/hx711/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 4 + clk_pin: 5 + gain: 128 + update_interval: 15s diff --git a/tests/components/hx711/test.esp32-c3.yaml b/tests/components/hx711/test.esp32-c3.yaml new file mode 100644 index 000000000000..985041744068 --- /dev/null +++ b/tests/components/hx711/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 4 + clk_pin: 5 + gain: 128 + update_interval: 15s diff --git a/tests/components/hx711/test.esp32-idf.yaml b/tests/components/hx711/test.esp32-idf.yaml new file mode 100644 index 000000000000..554b18442273 --- /dev/null +++ b/tests/components/hx711/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 14 + clk_pin: 15 + gain: 128 + update_interval: 15s diff --git a/tests/components/hx711/test.esp32.yaml b/tests/components/hx711/test.esp32.yaml new file mode 100644 index 000000000000..554b18442273 --- /dev/null +++ b/tests/components/hx711/test.esp32.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 14 + clk_pin: 15 + gain: 128 + update_interval: 15s diff --git a/tests/components/hx711/test.esp8266.yaml b/tests/components/hx711/test.esp8266.yaml new file mode 100644 index 000000000000..985041744068 --- /dev/null +++ b/tests/components/hx711/test.esp8266.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 4 + clk_pin: 5 + gain: 128 + update_interval: 15s diff --git a/tests/components/hx711/test.rp2040.yaml b/tests/components/hx711/test.rp2040.yaml new file mode 100644 index 000000000000..985041744068 --- /dev/null +++ b/tests/components/hx711/test.rp2040.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: hx711 + name: HX711 Value + dout_pin: 4 + clk_pin: 5 + gain: 128 + update_interval: 15s diff --git a/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml b/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f62f4942dbda --- /dev/null +++ b/tests/components/hydreon_rgxx/test.esp32-c3-idf.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hydreon_rgxx/test.esp32-c3.yaml b/tests/components/hydreon_rgxx/test.esp32-c3.yaml new file mode 100644 index 000000000000..f62f4942dbda --- /dev/null +++ b/tests/components/hydreon_rgxx/test.esp32-c3.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hydreon_rgxx/test.esp32-idf.yaml b/tests/components/hydreon_rgxx/test.esp32-idf.yaml new file mode 100644 index 000000000000..b6f9486d86de --- /dev/null +++ b/tests/components/hydreon_rgxx/test.esp32-idf.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hydreon_rgxx/test.esp32.yaml b/tests/components/hydreon_rgxx/test.esp32.yaml new file mode 100644 index 000000000000..b6f9486d86de --- /dev/null +++ b/tests/components/hydreon_rgxx/test.esp32.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hydreon_rgxx/test.esp8266.yaml b/tests/components/hydreon_rgxx/test.esp8266.yaml new file mode 100644 index 000000000000..f62f4942dbda --- /dev/null +++ b/tests/components/hydreon_rgxx/test.esp8266.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hydreon_rgxx/test.rp2040.yaml b/tests/components/hydreon_rgxx/test.rp2040.yaml new file mode 100644 index 000000000000..f62f4942dbda --- /dev/null +++ b/tests/components/hydreon_rgxx/test.rp2040.yaml @@ -0,0 +1,37 @@ +uart: + - id: uart_hydreon_rgxx + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: hydreon_rgxx + hydreon_rgxx_id: hydreon_rg9 + too_cold: + name: rg9_toocold + em_sat: + name: rg9_emsat + lens_bad: + name: rg9_lens_bad + +sensor: + - platform: hydreon_rgxx + id: hydreon_rg9 + model: RG 9 + moisture: + name: hydreon_rain + id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx + id: hydreon_rg15 + model: RG_15 + acc: + name: hydreon_acc + event_acc: + name: hydreon_event_acc + total_acc: + name: hydreon_total_acc + r_int: + name: hydreon_r_int diff --git a/tests/components/hyt271/test.esp32-c3-idf.yaml b/tests/components/hyt271/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c44f0a1f5d9b --- /dev/null +++ b/tests/components/hyt271/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 5 + sda: 4 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hyt271/test.esp32-c3.yaml b/tests/components/hyt271/test.esp32-c3.yaml new file mode 100644 index 000000000000..c44f0a1f5d9b --- /dev/null +++ b/tests/components/hyt271/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 5 + sda: 4 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hyt271/test.esp32-idf.yaml b/tests/components/hyt271/test.esp32-idf.yaml new file mode 100644 index 000000000000..297611a89ba9 --- /dev/null +++ b/tests/components/hyt271/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 16 + sda: 17 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hyt271/test.esp32.yaml b/tests/components/hyt271/test.esp32.yaml new file mode 100644 index 000000000000..297611a89ba9 --- /dev/null +++ b/tests/components/hyt271/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 16 + sda: 17 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hyt271/test.esp8266.yaml b/tests/components/hyt271/test.esp8266.yaml new file mode 100644 index 000000000000..c44f0a1f5d9b --- /dev/null +++ b/tests/components/hyt271/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 5 + sda: 4 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/hyt271/test.rp2040.yaml b/tests/components/hyt271/test.rp2040.yaml new file mode 100644 index 000000000000..c44f0a1f5d9b --- /dev/null +++ b/tests/components/hyt271/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_hyt271 + scl: 5 + sda: 4 + +sensor: + - platform: hyt271 + temperature: + name: Temperature + humidity: + name: Humidity diff --git a/tests/components/i2c/test.esp32-c3-idf.yaml b/tests/components/i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a881438faa5f --- /dev/null +++ b/tests/components/i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 diff --git a/tests/components/i2c/test.esp32-c3.yaml b/tests/components/i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..a881438faa5f --- /dev/null +++ b/tests/components/i2c/test.esp32-c3.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 diff --git a/tests/components/i2c/test.esp32-idf.yaml b/tests/components/i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..19114a9e5d0a --- /dev/null +++ b/tests/components/i2c/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 16 + sda: 17 diff --git a/tests/components/i2c/test.esp32.yaml b/tests/components/i2c/test.esp32.yaml new file mode 100644 index 000000000000..19114a9e5d0a --- /dev/null +++ b/tests/components/i2c/test.esp32.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 16 + sda: 17 diff --git a/tests/components/i2c/test.esp8266.yaml b/tests/components/i2c/test.esp8266.yaml new file mode 100644 index 000000000000..a881438faa5f --- /dev/null +++ b/tests/components/i2c/test.esp8266.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 diff --git a/tests/components/i2c/test.rp2040.yaml b/tests/components/i2c/test.rp2040.yaml new file mode 100644 index 000000000000..a881438faa5f --- /dev/null +++ b/tests/components/i2c/test.rp2040.yaml @@ -0,0 +1,4 @@ +i2c: + - id: i2c_i2c + scl: 5 + sda: 4 diff --git a/tests/components/i2s_audio/test.esp32-c3-idf.yaml b/tests/components/i2s_audio/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..08cd56b1a789 --- /dev/null +++ b/tests/components/i2s_audio/test.esp32-c3-idf.yaml @@ -0,0 +1,4 @@ +i2s_audio: + i2s_bclk_pin: 7 + i2s_lrclk_pin: 6 + i2s_mclk_pin: 5 diff --git a/tests/components/i2s_audio/test.esp32-c3.yaml b/tests/components/i2s_audio/test.esp32-c3.yaml new file mode 100644 index 000000000000..08cd56b1a789 --- /dev/null +++ b/tests/components/i2s_audio/test.esp32-c3.yaml @@ -0,0 +1,4 @@ +i2s_audio: + i2s_bclk_pin: 7 + i2s_lrclk_pin: 6 + i2s_mclk_pin: 5 diff --git a/tests/components/i2s_audio/test.esp32-idf.yaml b/tests/components/i2s_audio/test.esp32-idf.yaml new file mode 100644 index 000000000000..938dd5c25f83 --- /dev/null +++ b/tests/components/i2s_audio/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +i2s_audio: + i2s_bclk_pin: 27 + i2s_lrclk_pin: 26 + i2s_mclk_pin: 25 diff --git a/tests/components/i2s_audio/test.esp32.yaml b/tests/components/i2s_audio/test.esp32.yaml new file mode 100644 index 000000000000..938dd5c25f83 --- /dev/null +++ b/tests/components/i2s_audio/test.esp32.yaml @@ -0,0 +1,4 @@ +i2s_audio: + i2s_bclk_pin: 27 + i2s_lrclk_pin: 26 + i2s_mclk_pin: 25 diff --git a/tests/components/iaqcore/test.esp32-c3-idf.yaml b/tests/components/iaqcore/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a1809dffd735 --- /dev/null +++ b/tests/components/iaqcore/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 5 + sda: 4 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/iaqcore/test.esp32-c3.yaml b/tests/components/iaqcore/test.esp32-c3.yaml new file mode 100644 index 000000000000..a1809dffd735 --- /dev/null +++ b/tests/components/iaqcore/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 5 + sda: 4 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/iaqcore/test.esp32-idf.yaml b/tests/components/iaqcore/test.esp32-idf.yaml new file mode 100644 index 000000000000..26b01dadf97b --- /dev/null +++ b/tests/components/iaqcore/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 16 + sda: 17 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/iaqcore/test.esp32.yaml b/tests/components/iaqcore/test.esp32.yaml new file mode 100644 index 000000000000..26b01dadf97b --- /dev/null +++ b/tests/components/iaqcore/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 16 + sda: 17 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/iaqcore/test.esp8266.yaml b/tests/components/iaqcore/test.esp8266.yaml new file mode 100644 index 000000000000..a1809dffd735 --- /dev/null +++ b/tests/components/iaqcore/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 5 + sda: 4 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/iaqcore/test.rp2040.yaml b/tests/components/iaqcore/test.rp2040.yaml new file mode 100644 index 000000000000..a1809dffd735 --- /dev/null +++ b/tests/components/iaqcore/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_iaqcore + scl: 5 + sda: 4 + +sensor: + - platform: iaqcore + co2: + name: iAQ Core CO2 Sensor + tvoc: + name: iAQ Core TVOC Sensor diff --git a/tests/components/ili9xxx/test.esp32-c3-idf.yaml b/tests/components/ili9xxx/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9526ae1f6ba6 --- /dev/null +++ b/tests/components/ili9xxx/test.esp32-c3-idf.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/ili9xxx/test.esp32-c3.yaml b/tests/components/ili9xxx/test.esp32-c3.yaml new file mode 100644 index 000000000000..9526ae1f6ba6 --- /dev/null +++ b/tests/components/ili9xxx/test.esp32-c3.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/ili9xxx/test.esp32-idf.yaml b/tests/components/ili9xxx/test.esp32-idf.yaml new file mode 100644 index 000000000000..0d7bda8ac696 --- /dev/null +++ b/tests/components/ili9xxx/test.esp32-idf.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 25 + dc_pin: 26 + reset_pin: 27 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/ili9xxx/test.esp32.yaml b/tests/components/ili9xxx/test.esp32.yaml new file mode 100644 index 000000000000..ecee21686e6f --- /dev/null +++ b/tests/components/ili9xxx/test.esp32.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 25 + dc_pin: 26 + reset_pin: 27 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/ili9xxx/test.esp8266.yaml b/tests/components/ili9xxx/test.esp8266.yaml new file mode 100644 index 000000000000..0791c25aca9f --- /dev/null +++ b/tests/components/ili9xxx/test.esp8266.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 2 + dc_pin: 4 + reset_pin: 0 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/ili9xxx/test.rp2040.yaml b/tests/components/ili9xxx/test.rp2040.yaml new file mode 100644 index 000000000000..54083ebce845 --- /dev/null +++ b/tests/components/ili9xxx/test.rp2040.yaml @@ -0,0 +1,35 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false + model: TFT 2.4 + color_palette: GRAYSCALE + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 + model: TFT 2.4 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + auto_clear_enabled: false + rotation: 90 + lambda: |- + it.fill(Color::WHITE); diff --git a/tests/components/image/test.esp32-c3-idf.yaml b/tests/components/image/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c083a97c9453 --- /dev/null +++ b/tests/components/image/test.esp32-c3-idf.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.esp32-c3.yaml b/tests/components/image/test.esp32-c3.yaml new file mode 100644 index 000000000000..c083a97c9453 --- /dev/null +++ b/tests/components/image/test.esp32-c3.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.esp32-idf.yaml b/tests/components/image/test.esp32-idf.yaml new file mode 100644 index 000000000000..ff9adde6b1b4 --- /dev/null +++ b/tests/components/image/test.esp32-idf.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.esp32.yaml b/tests/components/image/test.esp32.yaml new file mode 100644 index 000000000000..ff9adde6b1b4 --- /dev/null +++ b/tests/components/image/test.esp32.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.esp8266.yaml b/tests/components/image/test.esp8266.yaml new file mode 100644 index 000000000000..3632b9548585 --- /dev/null +++ b/tests/components/image/test.esp8266.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/image/test.rp2040.yaml b/tests/components/image/test.rp2040.yaml new file mode 100644 index 000000000000..b79c8a919509 --- /dev/null +++ b/tests/components/image/test.rp2040.yaml @@ -0,0 +1,52 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + +image: + - id: binary_image + file: ../../pnglogo.png + type: BINARY + dither: FloydSteinberg + - id: transparent_transparent_image + file: ../../pnglogo.png + type: TRANSPARENT_BINARY + - id: rgba_image + file: ../../pnglogo.png + type: RGBA + resize: 50x50 + - id: rgb24_image + file: ../../pnglogo.png + type: RGB24 + use_transparency: yes + - id: rgb565_image + file: ../../pnglogo.png + type: RGB565 + use_transparency: no + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 + - id: mdi_alert + file: mdi:alert-circle-outline + resize: 50x50 + - id: another_alert_icon + file: mdi:alert-outline + type: BINARY diff --git a/tests/components/improv_serial/common.yaml b/tests/components/improv_serial/common.yaml new file mode 100644 index 000000000000..b36fe5a4a7c6 --- /dev/null +++ b/tests/components/improv_serial/common.yaml @@ -0,0 +1,5 @@ +wifi: + ssid: MySSID + password: password1 + +improv_serial: diff --git a/tests/components/improv_serial/test.esp32-c3-idf.yaml b/tests/components/improv_serial/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/improv_serial/test.esp32-c3.yaml b/tests/components/improv_serial/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/improv_serial/test.esp32-idf.yaml b/tests/components/improv_serial/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/improv_serial/test.esp32.yaml b/tests/components/improv_serial/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/improv_serial/test.esp8266.yaml b/tests/components/improv_serial/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/improv_serial/test.rp2040.yaml b/tests/components/improv_serial/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/improv_serial/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ina219/test.esp32-c3-idf.yaml b/tests/components/ina219/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..586add9d1653 --- /dev/null +++ b/tests/components/ina219/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 5 + sda: 4 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina219/test.esp32-c3.yaml b/tests/components/ina219/test.esp32-c3.yaml new file mode 100644 index 000000000000..586add9d1653 --- /dev/null +++ b/tests/components/ina219/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 5 + sda: 4 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina219/test.esp32-idf.yaml b/tests/components/ina219/test.esp32-idf.yaml new file mode 100644 index 000000000000..affbec67c490 --- /dev/null +++ b/tests/components/ina219/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 16 + sda: 17 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina219/test.esp32.yaml b/tests/components/ina219/test.esp32.yaml new file mode 100644 index 000000000000..affbec67c490 --- /dev/null +++ b/tests/components/ina219/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 16 + sda: 17 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina219/test.esp8266.yaml b/tests/components/ina219/test.esp8266.yaml new file mode 100644 index 000000000000..586add9d1653 --- /dev/null +++ b/tests/components/ina219/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 5 + sda: 4 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina219/test.rp2040.yaml b/tests/components/ina219/test.rp2040.yaml new file mode 100644 index 000000000000..586add9d1653 --- /dev/null +++ b/tests/components/ina219/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ina219 + scl: 5 + sda: 4 + +sensor: + - platform: ina219 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA219 Current + power: + name: INA219 Power + bus_voltage: + name: INA219 Bus Voltage + shunt_voltage: + name: INA219 Shunt Voltage + max_voltage: 32.0V + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.esp32-c3-idf.yaml b/tests/components/ina226/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..65817632940a --- /dev/null +++ b/tests/components/ina226/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 5 + sda: 4 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.esp32-c3.yaml b/tests/components/ina226/test.esp32-c3.yaml new file mode 100644 index 000000000000..65817632940a --- /dev/null +++ b/tests/components/ina226/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 5 + sda: 4 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.esp32-idf.yaml b/tests/components/ina226/test.esp32-idf.yaml new file mode 100644 index 000000000000..feab5e146c55 --- /dev/null +++ b/tests/components/ina226/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 16 + sda: 17 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.esp32.yaml b/tests/components/ina226/test.esp32.yaml new file mode 100644 index 000000000000..feab5e146c55 --- /dev/null +++ b/tests/components/ina226/test.esp32.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 16 + sda: 17 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.esp8266.yaml b/tests/components/ina226/test.esp8266.yaml new file mode 100644 index 000000000000..65817632940a --- /dev/null +++ b/tests/components/ina226/test.esp8266.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 5 + sda: 4 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina226/test.rp2040.yaml b/tests/components/ina226/test.rp2040.yaml new file mode 100644 index 000000000000..65817632940a --- /dev/null +++ b/tests/components/ina226/test.rp2040.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina226 + scl: 5 + sda: 4 + +sensor: + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: INA226 Current + power: + name: INA226 Power + bus_voltage: + name: INA226 Bus Voltage + shunt_voltage: + name: INA226 Shunt Voltage + max_current: 3.2A + update_interval: 15s diff --git a/tests/components/ina260/test.esp32-c3-idf.yaml b/tests/components/ina260/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a1da63351d5c --- /dev/null +++ b/tests/components/ina260/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 5 + sda: 4 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina260/test.esp32-c3.yaml b/tests/components/ina260/test.esp32-c3.yaml new file mode 100644 index 000000000000..a1da63351d5c --- /dev/null +++ b/tests/components/ina260/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 5 + sda: 4 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina260/test.esp32-idf.yaml b/tests/components/ina260/test.esp32-idf.yaml new file mode 100644 index 000000000000..be6cf73bffd1 --- /dev/null +++ b/tests/components/ina260/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 16 + sda: 17 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina260/test.esp32.yaml b/tests/components/ina260/test.esp32.yaml new file mode 100644 index 000000000000..be6cf73bffd1 --- /dev/null +++ b/tests/components/ina260/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 16 + sda: 17 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina260/test.esp8266.yaml b/tests/components/ina260/test.esp8266.yaml new file mode 100644 index 000000000000..a1da63351d5c --- /dev/null +++ b/tests/components/ina260/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 5 + sda: 4 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina260/test.rp2040.yaml b/tests/components/ina260/test.rp2040.yaml new file mode 100644 index 000000000000..a1da63351d5c --- /dev/null +++ b/tests/components/ina260/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_ina260 + scl: 5 + sda: 4 + +sensor: + - platform: ina260 + address: 0x40 + current: + name: INA260 Current + power: + name: INA260 Power + bus_voltage: + name: INA260 Voltage + update_interval: 60s diff --git a/tests/components/ina3221/test.esp32-c3-idf.yaml b/tests/components/ina3221/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..55990871a0bc --- /dev/null +++ b/tests/components/ina3221/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 5 + sda: 4 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/ina3221/test.esp32-c3.yaml b/tests/components/ina3221/test.esp32-c3.yaml new file mode 100644 index 000000000000..55990871a0bc --- /dev/null +++ b/tests/components/ina3221/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 5 + sda: 4 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/ina3221/test.esp32-idf.yaml b/tests/components/ina3221/test.esp32-idf.yaml new file mode 100644 index 000000000000..ad9cf79e3899 --- /dev/null +++ b/tests/components/ina3221/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 16 + sda: 17 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/ina3221/test.esp32.yaml b/tests/components/ina3221/test.esp32.yaml new file mode 100644 index 000000000000..ad9cf79e3899 --- /dev/null +++ b/tests/components/ina3221/test.esp32.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 16 + sda: 17 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/ina3221/test.esp8266.yaml b/tests/components/ina3221/test.esp8266.yaml new file mode 100644 index 000000000000..55990871a0bc --- /dev/null +++ b/tests/components/ina3221/test.esp8266.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 5 + sda: 4 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/ina3221/test.rp2040.yaml b/tests/components/ina3221/test.rp2040.yaml new file mode 100644 index 000000000000..55990871a0bc --- /dev/null +++ b/tests/components/ina3221/test.rp2040.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_ina3221 + scl: 5 + sda: 4 + +sensor: + - platform: ina3221 + address: 0x40 + channel_1: + shunt_resistance: 0.1 ohm + current: + name: INA3221 Channel 1 Current + power: + name: INA3221 Channel 1 Power + bus_voltage: + name: INA3221 Channel 1 Bus Voltage + shunt_voltage: + name: INA3221 Channel 1 Shunt Voltage + update_interval: 15s diff --git a/tests/components/inkbird_ibsth1_mini/common.yaml b/tests/components/inkbird_ibsth1_mini/common.yaml new file mode 100644 index 000000000000..ba46b7dbf63b --- /dev/null +++ b/tests/components/inkbird_ibsth1_mini/common.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: inkbird_ibsth1_mini + mac_address: 38:81:D7:0A:9C:11 + temperature: + name: Inkbird IBS-TH1 Temperature + humidity: + name: Inkbird IBS-TH1 Humidity + battery_level: + name: Inkbird IBS-TH1 Battery Level diff --git a/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml b/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/inkbird_ibsth1_mini/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/inkbird_ibsth1_mini/test.esp32-c3.yaml b/tests/components/inkbird_ibsth1_mini/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/inkbird_ibsth1_mini/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/inkbird_ibsth1_mini/test.esp32-idf.yaml b/tests/components/inkbird_ibsth1_mini/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/inkbird_ibsth1_mini/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/inkbird_ibsth1_mini/test.esp32.yaml b/tests/components/inkbird_ibsth1_mini/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/inkbird_ibsth1_mini/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/inkplate6/common.yaml b/tests/components/inkplate6/common.yaml new file mode 100644 index 000000000000..31b14e6c730e --- /dev/null +++ b/tests/components/inkplate6/common.yaml @@ -0,0 +1,62 @@ +i2c: + - id: i2c_inkplate6 + scl: 16 + sda: 17 + +display: + - platform: inkplate6 + id: inkplate_display + greyscale: false + partial_updating: false + update_interval: 60s + display_data_0_pin: + number: 1 + allow_other_uses: true + display_data_1_pin: + number: 1 + allow_other_uses: true + display_data_2_pin: + number: 1 + allow_other_uses: true + display_data_3_pin: + number: 1 + allow_other_uses: true + display_data_5_pin: + number: 1 + allow_other_uses: true + display_data_4_pin: + number: 1 + allow_other_uses: true + display_data_6_pin: + number: 1 + allow_other_uses: true + display_data_7_pin: + number: 1 + allow_other_uses: true + ckv_pin: + number: 1 + allow_other_uses: true + sph_pin: + number: 1 + allow_other_uses: true + gmod_pin: + number: 1 + allow_other_uses: true + gpio0_enable_pin: + number: 1 + allow_other_uses: true + oe_pin: + number: 1 + allow_other_uses: true + spv_pin: + number: 1 + allow_other_uses: true + powerup_pin: + number: 1 + allow_other_uses: true + wakeup_pin: + number: 1 + allow_other_uses: true + vcom_pin: + number: 1 + allow_other_uses: true diff --git a/tests/components/inkplate6/test.esp32.yaml b/tests/components/inkplate6/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/inkplate6/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/integration/test.esp32-c3.yaml b/tests/components/integration/test.esp32-c3.yaml new file mode 100644 index 000000000000..b68cb9f87dfe --- /dev/null +++ b/tests/components/integration/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.esp32-idf.yaml b/tests/components/integration/test.esp32-idf.yaml new file mode 100644 index 000000000000..0095fdb1ffb1 --- /dev/null +++ b/tests/components/integration/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: my_sensor + pin: A0 + attenuation: 2.5db + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.esp32-s2.yaml b/tests/components/integration/test.esp32-s2.yaml new file mode 100644 index 000000000000..14159525710c --- /dev/null +++ b/tests/components/integration/test.esp32-s2.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.esp32-s3.yaml b/tests/components/integration/test.esp32-s3.yaml new file mode 100644 index 000000000000..14159525710c --- /dev/null +++ b/tests/components/integration/test.esp32-s3.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.esp32.yaml b/tests/components/integration/test.esp32.yaml new file mode 100644 index 000000000000..0095fdb1ffb1 --- /dev/null +++ b/tests/components/integration/test.esp32.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: adc + id: my_sensor + pin: A0 + attenuation: 2.5db + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.esp8266.yaml b/tests/components/integration/test.esp8266.yaml new file mode 100644 index 000000000000..51d3e1907727 --- /dev/null +++ b/tests/components/integration/test.esp8266.yaml @@ -0,0 +1,8 @@ +sensor: + - platform: adc + id: my_sensor + pin: VCC + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/integration/test.rp2040.yaml b/tests/components/integration/test.rp2040.yaml new file mode 100644 index 000000000000..51d3e1907727 --- /dev/null +++ b/tests/components/integration/test.rp2040.yaml @@ -0,0 +1,8 @@ +sensor: + - platform: adc + id: my_sensor + pin: VCC + - platform: integration + sensor: my_sensor + name: Integration Sensor + time_unit: s diff --git a/tests/components/internal_temperature/test.bk72xx.yaml b/tests/components/internal_temperature/test.bk72xx.yaml new file mode 100644 index 000000000000..28df4a6d9f05 --- /dev/null +++ b/tests/components/internal_temperature/test.bk72xx.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: "Internal Temperature" diff --git a/tests/components/internal_temperature/test.esp32-c3-idf.yaml b/tests/components/internal_temperature/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..28df4a6d9f05 --- /dev/null +++ b/tests/components/internal_temperature/test.esp32-c3-idf.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: "Internal Temperature" diff --git a/tests/components/internal_temperature/test.esp32-c3.yaml b/tests/components/internal_temperature/test.esp32-c3.yaml new file mode 100644 index 000000000000..19f740339d5d --- /dev/null +++ b/tests/components/internal_temperature/test.esp32-c3.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature diff --git a/tests/components/internal_temperature/test.esp32-idf.yaml b/tests/components/internal_temperature/test.esp32-idf.yaml new file mode 100644 index 000000000000..19f740339d5d --- /dev/null +++ b/tests/components/internal_temperature/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature diff --git a/tests/components/internal_temperature/test.esp32-s2.yaml b/tests/components/internal_temperature/test.esp32-s2.yaml new file mode 100644 index 000000000000..19f740339d5d --- /dev/null +++ b/tests/components/internal_temperature/test.esp32-s2.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature diff --git a/tests/components/internal_temperature/test.esp32-s3.yaml b/tests/components/internal_temperature/test.esp32-s3.yaml new file mode 100644 index 000000000000..9eb1ec0b0fef --- /dev/null +++ b/tests/components/internal_temperature/test.esp32-s3.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature + +esp32: + framework: + version: 2.0.9 diff --git a/tests/components/internal_temperature/test.esp32.yaml b/tests/components/internal_temperature/test.esp32.yaml new file mode 100644 index 000000000000..19f740339d5d --- /dev/null +++ b/tests/components/internal_temperature/test.esp32.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature diff --git a/tests/components/internal_temperature/test.rp2040.yaml b/tests/components/internal_temperature/test.rp2040.yaml new file mode 100644 index 000000000000..19f740339d5d --- /dev/null +++ b/tests/components/internal_temperature/test.rp2040.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: internal_temperature + name: Internal Temperature diff --git a/tests/components/interval/common.yaml b/tests/components/interval/common.yaml new file mode 100644 index 000000000000..2a3c979ae244 --- /dev/null +++ b/tests/components/interval/common.yaml @@ -0,0 +1,4 @@ +interval: + - interval: 1s + then: + - logger.log: Tick diff --git a/tests/components/interval/test.esp32-c3-idf.yaml b/tests/components/interval/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/interval/test.esp32-c3.yaml b/tests/components/interval/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/interval/test.esp32-idf.yaml b/tests/components/interval/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/interval/test.esp32.yaml b/tests/components/interval/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/interval/test.esp8266.yaml b/tests/components/interval/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/interval/test.rp2040.yaml b/tests/components/interval/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/interval/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..5a37418a7db0 --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32-c3.yaml b/tests/components/jsn_sr04t/test.esp32-c3.yaml new file mode 100644 index 000000000000..5a37418a7db0 --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32-idf.yaml b/tests/components/jsn_sr04t/test.esp32-idf.yaml new file mode 100644 index 000000000000..32b4221b3f05 --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp32.yaml b/tests/components/jsn_sr04t/test.esp32.yaml new file mode 100644 index 000000000000..32b4221b3f05 --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 17 + rx_pin: + number: 16 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.esp8266.yaml b/tests/components/jsn_sr04t/test.esp8266.yaml new file mode 100644 index 000000000000..5a37418a7db0 --- /dev/null +++ b/tests/components/jsn_sr04t/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/jsn_sr04t/test.rp2040.yaml b/tests/components/jsn_sr04t/test.rp2040.yaml new file mode 100644 index 000000000000..5a37418a7db0 --- /dev/null +++ b/tests/components/jsn_sr04t/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_jsn_sr04t + tx_pin: + number: 4 + rx_pin: + number: 5 + baud_rate: 9600 + +sensor: + - platform: jsn_sr04t + id: jsn_sr04t_sensor + name: "jsn_sr04t Distance" + uart_id: uart_jsn_sr04t + update_interval: 1s diff --git a/tests/components/kamstrup_kmp/common.yaml b/tests/components/kamstrup_kmp/common.yaml new file mode 100644 index 000000000000..b348d03c7218 --- /dev/null +++ b/tests/components/kamstrup_kmp/common.yaml @@ -0,0 +1,25 @@ +uart: + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 1200 + stop_bits: 2 + +sensor: + - platform: kamstrup_kmp + heat_energy: + name: Heat Energy + power: + name: Power + temp1: + name: Temperature 1 + temp2: + name: Temperature 2 + temp_diff: + name: Temperature Difference + flow: + name: Flow + volume: + name: Volume + custom: + - name: Custom 1 + command: 0x1234 diff --git a/tests/components/kamstrup_kmp/test.esp32-idf.yaml b/tests/components/kamstrup_kmp/test.esp32-idf.yaml new file mode 100644 index 000000000000..adc2c4d24a88 --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/kamstrup_kmp/test.esp32.yaml b/tests/components/kamstrup_kmp/test.esp32.yaml new file mode 100644 index 000000000000..adc2c4d24a88 --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/kamstrup_kmp/test.esp8266.yaml b/tests/components/kamstrup_kmp/test.esp8266.yaml new file mode 100644 index 000000000000..adc2c4d24a88 --- /dev/null +++ b/tests/components/kamstrup_kmp/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO1 + uart_rx_pin: GPIO3 + +<<: !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 new file mode 100644 index 000000000000..1f133c5cd8cf --- /dev/null +++ b/tests/components/key_collector/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32-c3.yaml b/tests/components/key_collector/test.esp32-c3.yaml new file mode 100644 index 000000000000..1f133c5cd8cf --- /dev/null +++ b/tests/components/key_collector/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32-idf.yaml b/tests/components/key_collector/test.esp32-idf.yaml new file mode 100644 index 000000000000..7cbe9c0fc160 --- /dev/null +++ b/tests/components/key_collector/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp32.yaml b/tests/components/key_collector/test.esp32.yaml new file mode 100644 index 000000000000..7cbe9c0fc160 --- /dev/null +++ b/tests/components/key_collector/test.esp32.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.esp8266.yaml b/tests/components/key_collector/test.esp8266.yaml new file mode 100644 index 000000000000..7cbe9c0fc160 --- /dev/null +++ b/tests/components/key_collector/test.esp8266.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/key_collector/test.rp2040.yaml b/tests/components/key_collector/test.rp2040.yaml new file mode 100644 index 000000000000..1f133c5cd8cf --- /dev/null +++ b/tests/components/key_collector/test.rp2040.yaml @@ -0,0 +1,28 @@ +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + on_progress: + - logger.log: + format: "input progress: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] + on_result: + - logger.log: + format: "input result: '%s', started by '%c', ended by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"] + on_timeout: + - logger.log: + format: "input timeout: '%s', started by '%c'" + args: ['x.c_str()', "(start == 0 ? '~' : start)"] diff --git a/tests/components/kmeteriso/test.esp32-c3-idf.yaml b/tests/components/kmeteriso/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7780cfea32d1 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32-c3.yaml b/tests/components/kmeteriso/test.esp32-c3.yaml new file mode 100644 index 000000000000..7780cfea32d1 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32-idf.yaml b/tests/components/kmeteriso/test.esp32-idf.yaml new file mode 100644 index 000000000000..2c375dda31c2 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 16 + sda: 17 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp32.yaml b/tests/components/kmeteriso/test.esp32.yaml new file mode 100644 index 000000000000..2c375dda31c2 --- /dev/null +++ b/tests/components/kmeteriso/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 16 + sda: 17 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.esp8266.yaml b/tests/components/kmeteriso/test.esp8266.yaml new file mode 100644 index 000000000000..7780cfea32d1 --- /dev/null +++ b/tests/components/kmeteriso/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kmeteriso/test.rp2040.yaml b/tests/components/kmeteriso/test.rp2040.yaml new file mode 100644 index 000000000000..7780cfea32d1 --- /dev/null +++ b/tests/components/kmeteriso/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_kmeteriso + scl: 5 + sda: 4 + +sensor: + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Temperature + update_interval: 15s diff --git a/tests/components/kuntze/test.esp32-c3-idf.yaml b/tests/components/kuntze/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..08278c3c8228 --- /dev/null +++ b/tests/components/kuntze/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32-c3.yaml b/tests/components/kuntze/test.esp32-c3.yaml new file mode 100644 index 000000000000..08278c3c8228 --- /dev/null +++ b/tests/components/kuntze/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32-idf.yaml b/tests/components/kuntze/test.esp32-idf.yaml new file mode 100644 index 000000000000..6b6c638971ac --- /dev/null +++ b/tests/components/kuntze/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp32.yaml b/tests/components/kuntze/test.esp32.yaml new file mode 100644 index 000000000000..6b6c638971ac --- /dev/null +++ b/tests/components/kuntze/test.esp32.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.esp8266.yaml b/tests/components/kuntze/test.esp8266.yaml new file mode 100644 index 000000000000..eba6cddc2dea --- /dev/null +++ b/tests/components/kuntze/test.esp8266.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 13 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/kuntze/test.rp2040.yaml b/tests/components/kuntze/test.rp2040.yaml new file mode 100644 index 000000000000..08278c3c8228 --- /dev/null +++ b/tests/components/kuntze/test.rp2040.yaml @@ -0,0 +1,15 @@ +uart: + - id: uart_kuntze + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + flow_control_pin: 3 + +sensor: + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature diff --git a/tests/components/lcd_gpio/test.esp32-c3-idf.yaml b/tests/components/lcd_gpio/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b89715a755a6 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32-c3.yaml b/tests/components/lcd_gpio/test.esp32-c3.yaml new file mode 100644 index 000000000000..b89715a755a6 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32-idf.yaml b/tests/components/lcd_gpio/test.esp32-idf.yaml new file mode 100644 index 000000000000..d2b33aeb3a92 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp32.yaml b/tests/components/lcd_gpio/test.esp32.yaml new file mode 100644 index 000000000000..d2b33aeb3a92 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp32.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.esp8266.yaml b/tests/components/lcd_gpio/test.esp8266.yaml new file mode 100644 index 000000000000..d2b33aeb3a92 --- /dev/null +++ b/tests/components/lcd_gpio/test.esp8266.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_gpio/test.rp2040.yaml b/tests/components/lcd_gpio/test.rp2040.yaml new file mode 100644 index 000000000000..b89715a755a6 --- /dev/null +++ b/tests/components/lcd_gpio/test.rp2040.yaml @@ -0,0 +1,13 @@ +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_menu/test.esp32-c3-idf.yaml b/tests/components/lcd_menu/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..39d2278d3d11 --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-c3-idf.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32-c3.yaml b/tests/components/lcd_menu/test.esp32-c3.yaml new file mode 100644 index 000000000000..39d2278d3d11 --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-c3.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32-idf.yaml b/tests/components/lcd_menu/test.esp32-idf.yaml new file mode 100644 index 000000000000..833ea2169ac0 --- /dev/null +++ b/tests/components/lcd_menu/test.esp32-idf.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp32.yaml b/tests/components/lcd_menu/test.esp32.yaml new file mode 100644 index 000000000000..833ea2169ac0 --- /dev/null +++ b/tests/components/lcd_menu/test.esp32.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.esp8266.yaml b/tests/components/lcd_menu/test.esp8266.yaml new file mode 100644 index 000000000000..833ea2169ac0 --- /dev/null +++ b/tests/components/lcd_menu/test.esp8266.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 12 + - number: 13 + - number: 14 + - number: 15 + enable_pin: 16 + rs_pin: 5 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_menu/test.rp2040.yaml b/tests/components/lcd_menu/test.rp2040.yaml new file mode 100644 index 000000000000..39d2278d3d11 --- /dev/null +++ b/tests/components/lcd_menu/test.rp2040.yaml @@ -0,0 +1,118 @@ +number: + - platform: template + id: test_number + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + options: + - one + - two + optimistic: true + +switch: + - platform: template + name: Template Switch + id: my_switch + optimistic: true + +display: + - platform: lcd_gpio + id: my_lcd_gpio + dimensions: 18x4 + data_pins: + - number: 1 + - number: 2 + - number: 3 + - number: 4 + enable_pin: 5 + rs_pin: 6 + lambda: |- + it.print("Hello World!"); + +lcd_menu: + id: test_lcd_menu + display_id: my_lcd_gpio + mark_back: 0x5e + mark_selected: 0x3e + mark_editing: 0x2a + mark_submenu: 0x7e + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "root leave");' + items: + - type: back + text: Back + - type: label + - type: menu + text: Submenu 1 + items: + - type: back + text: Back + - type: menu + text: Submenu 21 + items: + - type: back + text: Back + - type: command + text: Show Main + on_value: + then: + - display_menu.show_main: test_lcd_menu + - type: select + text: Enum Item + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: Number + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("lcd_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("lcd_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: Hide + on_value: + then: + - display_menu.hide: test_lcd_menu + - type: switch + text: Switch + switch: my_switch + on_text: Bright + off_text: Dark + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("lcd_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' diff --git a/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml b/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..41eba26950f0 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32-c3.yaml b/tests/components/lcd_pcf8574/test.esp32-c3.yaml new file mode 100644 index 000000000000..41eba26950f0 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32-idf.yaml b/tests/components/lcd_pcf8574/test.esp32-idf.yaml new file mode 100644 index 000000000000..9d7d475f3069 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 16 + sda: 17 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp32.yaml b/tests/components/lcd_pcf8574/test.esp32.yaml new file mode 100644 index 000000000000..9d7d475f3069 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 16 + sda: 17 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.esp8266.yaml b/tests/components/lcd_pcf8574/test.esp8266.yaml new file mode 100644 index 000000000000..41eba26950f0 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/lcd_pcf8574/test.rp2040.yaml b/tests/components/lcd_pcf8574/test.rp2040.yaml new file mode 100644 index 000000000000..41eba26950f0 --- /dev/null +++ b/tests/components/lcd_pcf8574/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_lcd_pcf8574 + scl: 5 + sda: 4 + +display: + - platform: lcd_pcf8574 + dimensions: 18x4 + address: 0x3F + user_characters: + - position: 0 + data: + - 0b00000 + - 0b01010 + - 0b00000 + - 0b00100 + - 0b00100 + - 0b10001 + - 0b01110 + - 0b00000 + lambda: |- + it.print("Hello World!"); diff --git a/tests/components/ld2410/test.esp32-c3-idf.yaml b/tests/components/ld2410/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..afcaa81b3ec1 --- /dev/null +++ b/tests/components/ld2410/test.esp32-c3-idf.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32-c3.yaml b/tests/components/ld2410/test.esp32-c3.yaml new file mode 100644 index 000000000000..afcaa81b3ec1 --- /dev/null +++ b/tests/components/ld2410/test.esp32-c3.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32-idf.yaml b/tests/components/ld2410/test.esp32-idf.yaml new file mode 100644 index 000000000000..48ed179d9345 --- /dev/null +++ b/tests/components/ld2410/test.esp32-idf.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp32.yaml b/tests/components/ld2410/test.esp32.yaml new file mode 100644 index 000000000000..48ed179d9345 --- /dev/null +++ b/tests/components/ld2410/test.esp32.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.esp8266.yaml b/tests/components/ld2410/test.esp8266.yaml new file mode 100644 index 000000000000..afcaa81b3ec1 --- /dev/null +++ b/tests/components/ld2410/test.esp8266.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2410/test.rp2040.yaml b/tests/components/ld2410/test.rp2040.yaml new file mode 100644 index 000000000000..afcaa81b3ec1 --- /dev/null +++ b/tests/components/ld2410/test.rp2040.yaml @@ -0,0 +1,169 @@ +uart: + - id: uart_ld2410 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2410: + id: my_ld2410 + +binary_sensor: + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still + out_pin_presence_status: + name: out pin presence status + +button: + - platform: ld2410 + factory_reset: + name: factory reset + restart: + name: restart + query_params: + name: query params + +number: + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level + +sensor: + - platform: ld2410 + light: + name: light + moving_distance: + name: Moving distance + still_distance: + name: Still Distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Distance Detection + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + +switch: + - platform: ld2410 + engineering_mode: + name: control ld2410 engineering mode + bluetooth: + name: control ld2410 bluetooth + +text_sensor: + - platform: ld2410 + version: + name: presenece sensor version + mac_address: + name: presenece sensor mac address diff --git a/tests/components/ld2420/test.esp32-c3-idf.yaml b/tests/components/ld2420/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5e0b9db1c283 --- /dev/null +++ b/tests/components/ld2420/test.esp32-c3-idf.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32-c3.yaml b/tests/components/ld2420/test.esp32-c3.yaml new file mode 100644 index 000000000000..5e0b9db1c283 --- /dev/null +++ b/tests/components/ld2420/test.esp32-c3.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32-idf.yaml b/tests/components/ld2420/test.esp32-idf.yaml new file mode 100644 index 000000000000..8c883664ec44 --- /dev/null +++ b/tests/components/ld2420/test.esp32-idf.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp32.yaml b/tests/components/ld2420/test.esp32.yaml new file mode 100644 index 000000000000..8c883664ec44 --- /dev/null +++ b/tests/components/ld2420/test.esp32.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.esp8266.yaml b/tests/components/ld2420/test.esp8266.yaml new file mode 100644 index 000000000000..5e0b9db1c283 --- /dev/null +++ b/tests/components/ld2420/test.esp8266.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ld2420/test.rp2040.yaml b/tests/components/ld2420/test.rp2040.yaml new file mode 100644 index 000000000000..5e0b9db1c283 --- /dev/null +++ b/tests/components/ld2420/test.rp2040.yaml @@ -0,0 +1,132 @@ +uart: + - id: uart_ld2420 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +ld2420: + id: my_ld2420 + +binary_sensor: + - platform: ld2420 + has_target: + name: Presence + +button: + - platform: ld2420 + apply_config: + name: Apply Config + factory_reset: + name: Factory Reset + restart_module: + name: Restart Module + revert_config: + name: Undo Edits + +number: + - platform: ld2420 + presence_timeout: + name: Detection Presence Timeout + min_gate_distance: + name: Detection Gate Minimum + max_gate_distance: + name: Detection Gate Maximum + gate_move_sensitivity: + name: Move Calibration Sensitivity Factor + gate_still_sensitivity: + name: Still Calibration Sensitivity Factor + gate_0: + move_threshold: + name: Gate 0 Move Threshold + still_threshold: + name: Gate 0 Still Threshold + gate_1: + move_threshold: + name: Gate 1 Move Threshold + still_threshold: + name: Gate 1 Still Threshold + gate_2: + move_threshold: + name: Gate 2 Move Threshold + still_threshold: + name: Gate 2 Still Threshold + gate_3: + move_threshold: + name: Gate 3 Move Threshold + still_threshold: + name: Gate 3 Still Threshold + gate_4: + move_threshold: + name: Gate 4 Move Threshold + still_threshold: + name: Gate 4 Still Threshold + gate_5: + move_threshold: + name: Gate 5 Move Threshold + still_threshold: + name: Gate 5 Still Threshold + gate_6: + move_threshold: + name: Gate 6 Move Threshold + still_threshold: + name: Gate 6 Still Threshold + gate_7: + move_threshold: + name: Gate 7 Move Threshold + still_threshold: + name: Gate 7 Still Threshold + gate_8: + move_threshold: + name: Gate 8 Move Threshold + still_threshold: + name: Gate 8 Still Threshold + gate_9: + move_threshold: + name: Gate 9 Move Threshold + still_threshold: + name: Gate 9 Still Threshold + gate_10: + move_threshold: + name: Gate 10 Move Threshold + still_threshold: + name: Gate 10 Still Threshold + gate_11: + move_threshold: + name: Gate 11 Move Threshold + still_threshold: + name: Gate 11 Still Threshold + gate_12: + move_threshold: + name: Gate 12 Move Threshold + still_threshold: + name: Gate 12 Still Threshold + gate_13: + move_threshold: + name: Gate 13 Move Threshold + still_threshold: + name: Gate 13 Still Threshold + gate_14: + move_threshold: + name: Gate 14 Move Threshold + still_threshold: + name: Gate 14 Still Threshold + gate_15: + move_threshold: + name: Gate 15 Move Threshold + still_threshold: + name: Gate 15 Still Threshold + +select: + - platform: ld2420 + operating_mode: + name: Operating Mode + +sensor: + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" + +text_sensor: + - platform: ld2420 + fw_version: + name: LD2420 Firmware diff --git a/tests/components/ledc/common.yaml b/tests/components/ledc/common.yaml new file mode 100644 index 000000000000..70352b451952 --- /dev/null +++ b/tests/components/ledc/common.yaml @@ -0,0 +1,11 @@ +esphome: + on_boot: + then: + - output.ledc.set_frequency: + id: test_ledc + frequency: 100Hz + +output: + - platform: ledc + id: test_ledc + pin: 4 diff --git a/tests/components/ledc/test.esp32-c3-idf.yaml b/tests/components/ledc/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ledc/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ledc/test.esp32-c3.yaml b/tests/components/ledc/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ledc/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ledc/test.esp32-idf.yaml b/tests/components/ledc/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ledc/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ledc/test.esp32.yaml b/tests/components/ledc/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ledc/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/light/test.esp32-c3-idf.yaml b/tests/components/light/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8e1709838a60 --- /dev/null +++ b/tests/components/light/test.esp32-c3-idf.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +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 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32-c3.yaml b/tests/components/light/test.esp32-c3.yaml new file mode 100644 index 000000000000..8e1709838a60 --- /dev/null +++ b/tests/components/light/test.esp32-c3.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +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 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32-idf.yaml b/tests/components/light/test.esp32-idf.yaml new file mode 100644 index 000000000000..7e5718d8d436 --- /dev/null +++ b/tests/components/light/test.esp32-idf.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 12 + - platform: ledc + id: test_ledc_1 + pin: 13 + - platform: ledc + id: test_ledc_2 + pin: 14 + - platform: ledc + id: test_ledc_3 + pin: 15 + - platform: ledc + id: test_ledc_4 + pin: 16 + - platform: ledc + id: test_ledc_5 + pin: 17 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp32.yaml b/tests/components/light/test.esp32.yaml new file mode 100644 index 000000000000..7e5718d8d436 --- /dev/null +++ b/tests/components/light/test.esp32.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 12 + - platform: ledc + id: test_ledc_1 + pin: 13 + - platform: ledc + id: test_ledc_2 + pin: 14 + - platform: ledc + id: test_ledc_3 + pin: 15 + - platform: ledc + id: test_ledc_4 + pin: 16 + - platform: ledc + id: test_ledc_5 + pin: 17 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.esp8266.yaml b/tests/components/light/test.esp8266.yaml new file mode 100644 index 000000000000..4611fb374a43 --- /dev/null +++ b/tests/components/light/test.esp8266.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 4 + - platform: esp8266_pwm + id: test_ledc_1 + pin: 12 + - platform: esp8266_pwm + id: test_ledc_2 + pin: 13 + - platform: esp8266_pwm + id: test_ledc_3 + pin: 14 + - platform: esp8266_pwm + id: test_ledc_4 + pin: 15 + - platform: esp8266_pwm + id: test_ledc_5 + pin: 16 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/light/test.rp2040.yaml b/tests/components/light/test.rp2040.yaml new file mode 100644 index 000000000000..0215a17e715e --- /dev/null +++ b/tests/components/light/test.rp2040.yaml @@ -0,0 +1,132 @@ +esphome: + on_boot: + then: + - light.toggle: test_binary_light + - light.turn_off: test_rgb_light + - light.turn_on: + id: test_rgb_light + brightness: 100% + red: 100% + green: 100% + blue: 1.0 + - light.control: + id: test_monochromatic_light + state: on + - light.dim_relative: + id: test_monochromatic_light + relative_brightness: 5% + +output: + - platform: gpio + id: test_binary + pin: 0 + - platform: rp2040_pwm + id: test_ledc_1 + pin: 1 + - platform: rp2040_pwm + id: test_ledc_2 + pin: 2 + - platform: rp2040_pwm + id: test_ledc_3 + pin: 3 + - platform: rp2040_pwm + id: test_ledc_4 + pin: 4 + - platform: rp2040_pwm + id: test_ledc_5 + pin: 5 + +light: + - platform: binary + id: test_binary_light + name: Binary Light + output: test_binary + effects: + - strobe: + on_state: + - logger.log: Binary light state changed + - platform: monochromatic + id: test_monochromatic_light + name: Monochromatic Light + output: test_ledc_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb + id: test_rgb_light + name: RGB Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + - platform: rgbw + id: test_rgbw_light + name: RGBW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + white: test_ledc_4 + color_interlock: true + - platform: rgbww + id: test_rgbww_light + name: RGBWW Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + cold_white: test_ledc_4 + warm_white: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: rgbct + id: test_rgbct_light + name: RGBCT Light + red: test_ledc_1 + green: test_ledc_2 + blue: test_ledc_3 + color_temperature: test_ledc_4 + white_brightness: test_ledc_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true + - platform: cwww + id: test_cwww_light + name: CWWW Light + cold_white: test_ledc_1 + warm_white: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + constant_brightness: true + - platform: color_temperature + id: test_color_temperature_light + name: CT Light + color_temperature: test_ledc_1 + brightness: test_ledc_2 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds diff --git a/tests/components/lightwaverf/common.yaml b/tests/components/lightwaverf/common.yaml new file mode 100644 index 000000000000..7ed8000271c5 --- /dev/null +++ b/tests/components/lightwaverf/common.yaml @@ -0,0 +1,13 @@ +lightwaverf: + read_pin: 5 + write_pin: 4 + +button: + - platform: template + name: "Turn off sofa" + id: light_off_ceiling_sofa + on_press: + lightwaverf.send_raw: + code: [0x04, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x0d, 0x09, 0x08, 0x08] + name: "Sofa" + repeat: 1 diff --git a/tests/components/lightwaverf/test.esp8266.yaml b/tests/components/lightwaverf/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lightwaverf/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..41e81103accd --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32-c3.yaml b/tests/components/lilygo_t5_47/test.esp32-c3.yaml new file mode 100644 index 000000000000..41e81103accd --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32-idf.yaml b/tests/components/lilygo_t5_47/test.esp32-idf.yaml new file mode 100644 index 000000000000..35eb3df022b8 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 14 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp32.yaml b/tests/components/lilygo_t5_47/test.esp32.yaml new file mode 100644 index 000000000000..35eb3df022b8 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 14 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.esp8266.yaml b/tests/components/lilygo_t5_47/test.esp8266.yaml new file mode 100644 index 000000000000..766c492b12e9 --- /dev/null +++ b/tests/components/lilygo_t5_47/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 12 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lilygo_t5_47/test.rp2040.yaml b/tests/components/lilygo_t5_47/test.rp2040.yaml new file mode 100644 index 000000000000..41e81103accd --- /dev/null +++ b/tests/components/lilygo_t5_47/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_lilygo_t5_47 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: 6 + display: ssd1306_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/lock/common.yaml b/tests/components/lock/common.yaml new file mode 100644 index 000000000000..82297a3da488 --- /dev/null +++ b/tests/components/lock/common.yaml @@ -0,0 +1,36 @@ +esphome: + on_boot: + then: + - lock.lock: test_lock1 + - lock.unlock: test_lock1 + - lock.open: test_lock1 + +output: + - platform: gpio + id: test_binary + pin: 4 + +lock: + - platform: template + id: test_lock1 + name: Template Lock + lambda: |- + if (millis() > 10000) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: false + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: Generic Output Lock + id: test_lock2 + output: test_binary diff --git a/tests/components/lock/test.esp32-c3-idf.yaml b/tests/components/lock/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/lock/test.esp32-c3.yaml b/tests/components/lock/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/lock/test.esp32-idf.yaml b/tests/components/lock/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/lock/test.esp32.yaml b/tests/components/lock/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/lock/test.esp8266.yaml b/tests/components/lock/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/lock/test.rp2040.yaml b/tests/components/lock/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/lock/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/common.yaml b/tests/components/logger/common.yaml new file mode 100644 index 000000000000..70b485daac21 --- /dev/null +++ b/tests/components/logger/common.yaml @@ -0,0 +1,7 @@ +esphome: + on_boot: + then: + - logger.log: Hello world + +logger: + level: DEBUG diff --git a/tests/components/logger/test.esp32-c3-idf.yaml b/tests/components/logger/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/test.esp32-c3.yaml b/tests/components/logger/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/test.esp32-idf.yaml b/tests/components/logger/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/test.esp32.yaml b/tests/components/logger/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/test.esp8266.yaml b/tests/components/logger/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/logger/test.rp2040.yaml b/tests/components/logger/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/logger/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ltr390/test.esp32-c3-idf.yaml b/tests/components/ltr390/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fee0f37ce108 --- /dev/null +++ b/tests/components/ltr390/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32-c3.yaml b/tests/components/ltr390/test.esp32-c3.yaml new file mode 100644 index 000000000000..fee0f37ce108 --- /dev/null +++ b/tests/components/ltr390/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32-idf.yaml b/tests/components/ltr390/test.esp32-idf.yaml new file mode 100644 index 000000000000..9786c7dac3bf --- /dev/null +++ b/tests/components/ltr390/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 16 + sda: 17 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp32.yaml b/tests/components/ltr390/test.esp32.yaml new file mode 100644 index 000000000000..9786c7dac3bf --- /dev/null +++ b/tests/components/ltr390/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 16 + sda: 17 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.esp8266.yaml b/tests/components/ltr390/test.esp8266.yaml new file mode 100644 index 000000000000..fee0f37ce108 --- /dev/null +++ b/tests/components/ltr390/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/ltr390/test.rp2040.yaml b/tests/components/ltr390/test.rp2040.yaml new file mode 100644 index 000000000000..fee0f37ce108 --- /dev/null +++ b/tests/components/ltr390/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_ltr390 + scl: 5 + sda: 4 + +sensor: + - platform: ltr390 + uv: + name: LTR390 UV + uv_index: + name: LTR390 UVI + light: + name: LTR390 Light + ambient_light: + name: LTR390 ALS + gain: X3 + resolution: 18 + window_correction_factor: 1.0 + address: 0x53 + update_interval: 60s diff --git a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d15e6af21ac0 --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/matrix_keypad/test.esp32-c3.yaml b/tests/components/matrix_keypad/test.esp32-c3.yaml new file mode 100644 index 000000000000..d15e6af21ac0 --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/matrix_keypad/test.esp32-idf.yaml b/tests/components/matrix_keypad/test.esp32-idf.yaml new file mode 100644 index 000000000000..c8e9b54534db --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/matrix_keypad/test.esp32.yaml b/tests/components/matrix_keypad/test.esp32.yaml new file mode 100644 index 000000000000..c8e9b54534db --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/matrix_keypad/test.esp8266.yaml b/tests/components/matrix_keypad/test.esp8266.yaml new file mode 100644 index 000000000000..c8e9b54534db --- /dev/null +++ b/tests/components/matrix_keypad/test.esp8266.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 12 + - pin: 13 + columns: + - pin: 14 + - pin: 15 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/matrix_keypad/test.rp2040.yaml b/tests/components/matrix_keypad/test.rp2040.yaml new file mode 100644 index 000000000000..d15e6af21ac0 --- /dev/null +++ b/tests/components/matrix_keypad/test.rp2040.yaml @@ -0,0 +1,19 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + +matrix_keypad: + id: keypad + rows: + - pin: 1 + - pin: 2 + columns: + - pin: 3 + - pin: 4 + keys: "1234" + has_pulldowns: true diff --git a/tests/components/max31855/test.esp32-c3-idf.yaml b/tests/components/max31855/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e7c8f3f824e9 --- /dev/null +++ b/tests/components/max31855/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 8 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31855/test.esp32-c3.yaml b/tests/components/max31855/test.esp32-c3.yaml new file mode 100644 index 000000000000..e7c8f3f824e9 --- /dev/null +++ b/tests/components/max31855/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 8 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31855/test.esp32-idf.yaml b/tests/components/max31855/test.esp32-idf.yaml new file mode 100644 index 000000000000..25fee986d2c5 --- /dev/null +++ b/tests/components/max31855/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 12 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31855/test.esp32.yaml b/tests/components/max31855/test.esp32.yaml new file mode 100644 index 000000000000..25fee986d2c5 --- /dev/null +++ b/tests/components/max31855/test.esp32.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 12 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31855/test.esp8266.yaml b/tests/components/max31855/test.esp8266.yaml new file mode 100644 index 000000000000..7e02d41fceb2 --- /dev/null +++ b/tests/components/max31855/test.esp8266.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 15 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31855/test.rp2040.yaml b/tests/components/max31855/test.rp2040.yaml new file mode 100644 index 000000000000..379d4d33d6c2 --- /dev/null +++ b/tests/components/max31855/test.rp2040.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31855 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: max31855 + name: MAX31855 Temperature + cs_pin: 6 + update_interval: 15s + reference_temperature: + name: MAX31855 Internal Temperature diff --git a/tests/components/max31856/test.esp32-c3-idf.yaml b/tests/components/max31856/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2794866c59c0 --- /dev/null +++ b/tests/components/max31856/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 8 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31856/test.esp32-c3.yaml b/tests/components/max31856/test.esp32-c3.yaml new file mode 100644 index 000000000000..2794866c59c0 --- /dev/null +++ b/tests/components/max31856/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 8 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31856/test.esp32-idf.yaml b/tests/components/max31856/test.esp32-idf.yaml new file mode 100644 index 000000000000..556190320792 --- /dev/null +++ b/tests/components/max31856/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 12 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31856/test.esp32.yaml b/tests/components/max31856/test.esp32.yaml new file mode 100644 index 000000000000..556190320792 --- /dev/null +++ b/tests/components/max31856/test.esp32.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 12 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31856/test.esp8266.yaml b/tests/components/max31856/test.esp8266.yaml new file mode 100644 index 000000000000..dfd9572ca952 --- /dev/null +++ b/tests/components/max31856/test.esp8266.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 15 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31856/test.rp2040.yaml b/tests/components/max31856/test.rp2040.yaml new file mode 100644 index 000000000000..0abc8a081bac --- /dev/null +++ b/tests/components/max31856/test.rp2040.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max31856 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: max31856 + name: MAX31856 Temperature + cs_pin: 6 + update_interval: 15s + mains_filter: 50Hz diff --git a/tests/components/max31865/test.esp32-c3-idf.yaml b/tests/components/max31865/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..45de22331ebe --- /dev/null +++ b/tests/components/max31865/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 8 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max31865/test.esp32-c3.yaml b/tests/components/max31865/test.esp32-c3.yaml new file mode 100644 index 000000000000..45de22331ebe --- /dev/null +++ b/tests/components/max31865/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 8 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max31865/test.esp32-idf.yaml b/tests/components/max31865/test.esp32-idf.yaml new file mode 100644 index 000000000000..8326a578ee39 --- /dev/null +++ b/tests/components/max31865/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 12 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max31865/test.esp32.yaml b/tests/components/max31865/test.esp32.yaml new file mode 100644 index 000000000000..8326a578ee39 --- /dev/null +++ b/tests/components/max31865/test.esp32.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 12 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max31865/test.esp8266.yaml b/tests/components/max31865/test.esp8266.yaml new file mode 100644 index 000000000000..4828019accce --- /dev/null +++ b/tests/components/max31865/test.esp8266.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 15 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max31865/test.rp2040.yaml b/tests/components/max31865/test.rp2040.yaml new file mode 100644 index 000000000000..5af64b41add5 --- /dev/null +++ b/tests/components/max31865/test.rp2040.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_max31865 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: max31865 + name: MAX31865 Temperature + cs_pin: 6 + update_interval: 15s + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω diff --git a/tests/components/max44009/test.esp32-c3-idf.yaml b/tests/components/max44009/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..593d4bd48cfd --- /dev/null +++ b/tests/components/max44009/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 5 + sda: 4 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max44009/test.esp32-c3.yaml b/tests/components/max44009/test.esp32-c3.yaml new file mode 100644 index 000000000000..593d4bd48cfd --- /dev/null +++ b/tests/components/max44009/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 5 + sda: 4 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max44009/test.esp32-idf.yaml b/tests/components/max44009/test.esp32-idf.yaml new file mode 100644 index 000000000000..56eecebc4aff --- /dev/null +++ b/tests/components/max44009/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 16 + sda: 17 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max44009/test.esp32.yaml b/tests/components/max44009/test.esp32.yaml new file mode 100644 index 000000000000..56eecebc4aff --- /dev/null +++ b/tests/components/max44009/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 16 + sda: 17 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max44009/test.esp8266.yaml b/tests/components/max44009/test.esp8266.yaml new file mode 100644 index 000000000000..593d4bd48cfd --- /dev/null +++ b/tests/components/max44009/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 5 + sda: 4 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max44009/test.rp2040.yaml b/tests/components/max44009/test.rp2040.yaml new file mode 100644 index 000000000000..593d4bd48cfd --- /dev/null +++ b/tests/components/max44009/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_max44009 + scl: 5 + sda: 4 + +sensor: + - platform: max44009 + name: MAX44009 Brightness + internal: true + mode: low_power + address: 0x4A + update_interval: 30s diff --git a/tests/components/max6675/test.esp32-c3-idf.yaml b/tests/components/max6675/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2f05102ca1d1 --- /dev/null +++ b/tests/components/max6675/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 8 + update_interval: 15s diff --git a/tests/components/max6675/test.esp32-c3.yaml b/tests/components/max6675/test.esp32-c3.yaml new file mode 100644 index 000000000000..2f05102ca1d1 --- /dev/null +++ b/tests/components/max6675/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 8 + update_interval: 15s diff --git a/tests/components/max6675/test.esp32-idf.yaml b/tests/components/max6675/test.esp32-idf.yaml new file mode 100644 index 000000000000..9771bf9d5f79 --- /dev/null +++ b/tests/components/max6675/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 12 + update_interval: 15s diff --git a/tests/components/max6675/test.esp32.yaml b/tests/components/max6675/test.esp32.yaml new file mode 100644 index 000000000000..9771bf9d5f79 --- /dev/null +++ b/tests/components/max6675/test.esp32.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 12 + update_interval: 15s diff --git a/tests/components/max6675/test.esp8266.yaml b/tests/components/max6675/test.esp8266.yaml new file mode 100644 index 000000000000..f67e9e04a8c8 --- /dev/null +++ b/tests/components/max6675/test.esp8266.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 15 + update_interval: 15s diff --git a/tests/components/max6675/test.rp2040.yaml b/tests/components/max6675/test.rp2040.yaml new file mode 100644 index 000000000000..89c0932f949a --- /dev/null +++ b/tests/components/max6675/test.rp2040.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_max6675 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +sensor: + - platform: max6675 + name: Temperature + cs_pin: 6 + update_interval: 15s diff --git a/tests/components/max6956/test.esp32-c3-idf.yaml b/tests/components/max6956/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..690941784c57 --- /dev/null +++ b/tests/components/max6956/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 5 + sda: 4 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max6956/test.esp32-c3.yaml b/tests/components/max6956/test.esp32-c3.yaml new file mode 100644 index 000000000000..690941784c57 --- /dev/null +++ b/tests/components/max6956/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 5 + sda: 4 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max6956/test.esp32-idf.yaml b/tests/components/max6956/test.esp32-idf.yaml new file mode 100644 index 000000000000..abd140463480 --- /dev/null +++ b/tests/components/max6956/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 16 + sda: 17 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max6956/test.esp32.yaml b/tests/components/max6956/test.esp32.yaml new file mode 100644 index 000000000000..abd140463480 --- /dev/null +++ b/tests/components/max6956/test.esp32.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 16 + sda: 17 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max6956/test.esp8266.yaml b/tests/components/max6956/test.esp8266.yaml new file mode 100644 index 000000000000..690941784c57 --- /dev/null +++ b/tests/components/max6956/test.esp8266.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 5 + sda: 4 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max6956/test.rp2040.yaml b/tests/components/max6956/test.rp2040.yaml new file mode 100644 index 000000000000..690941784c57 --- /dev/null +++ b/tests/components/max6956/test.rp2040.yaml @@ -0,0 +1,19 @@ +i2c: + - id: i2c_max6956 + scl: 5 + sda: 4 + +max6956: + - id: max6956_1 + address: 0x40 + +binary_sensor: + - platform: gpio + name: Max Input Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false diff --git a/tests/components/max7219/test.esp32-c3-idf.yaml b/tests/components/max7219/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fa1ac15f331f --- /dev/null +++ b/tests/components/max7219/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max7219 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: max7219 + cs_pin: 8 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219/test.esp32-c3.yaml b/tests/components/max7219/test.esp32-c3.yaml new file mode 100644 index 000000000000..fa1ac15f331f --- /dev/null +++ b/tests/components/max7219/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max7219 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: max7219 + cs_pin: 8 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219/test.esp32-idf.yaml b/tests/components/max7219/test.esp32-idf.yaml new file mode 100644 index 000000000000..2985345a4872 --- /dev/null +++ b/tests/components/max7219/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max6675 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: max7219 + cs_pin: 12 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219/test.esp32.yaml b/tests/components/max7219/test.esp32.yaml new file mode 100644 index 000000000000..2985345a4872 --- /dev/null +++ b/tests/components/max7219/test.esp32.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max6675 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: max7219 + cs_pin: 12 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219/test.esp8266.yaml b/tests/components/max7219/test.esp8266.yaml new file mode 100644 index 000000000000..a8c280daff77 --- /dev/null +++ b/tests/components/max7219/test.esp8266.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max6675 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: max7219 + cs_pin: 15 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219/test.rp2040.yaml b/tests/components/max7219/test.rp2040.yaml new file mode 100644 index 000000000000..37b22206498a --- /dev/null +++ b/tests/components/max7219/test.rp2040.yaml @@ -0,0 +1,12 @@ +spi: + - id: spi_max6675 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: max7219 + cs_pin: 6 + num_chips: 1 + lambda: |- + it.print("01234567"); diff --git a/tests/components/max7219digit/test.esp32-c3-idf.yaml b/tests/components/max7219digit/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0c04784380c0 --- /dev/null +++ b/tests/components/max7219digit/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: max7219digit + cs_pin: 8 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max7219digit/test.esp32-c3.yaml b/tests/components/max7219digit/test.esp32-c3.yaml new file mode 100644 index 000000000000..0c04784380c0 --- /dev/null +++ b/tests/components/max7219digit/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: max7219digit + cs_pin: 8 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max7219digit/test.esp32-idf.yaml b/tests/components/max7219digit/test.esp32-idf.yaml new file mode 100644 index 000000000000..7f3aed964ad7 --- /dev/null +++ b/tests/components/max7219digit/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: max7219digit + cs_pin: 12 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max7219digit/test.esp32.yaml b/tests/components/max7219digit/test.esp32.yaml new file mode 100644 index 000000000000..7f3aed964ad7 --- /dev/null +++ b/tests/components/max7219digit/test.esp32.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: max7219digit + cs_pin: 12 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max7219digit/test.esp8266.yaml b/tests/components/max7219digit/test.esp8266.yaml new file mode 100644 index 000000000000..52587e8b0e59 --- /dev/null +++ b/tests/components/max7219digit/test.esp8266.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: max7219digit + cs_pin: 15 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max7219digit/test.rp2040.yaml b/tests/components/max7219digit/test.rp2040.yaml new file mode 100644 index 000000000000..f986483ec287 --- /dev/null +++ b/tests/components/max7219digit/test.rp2040.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_max7219digit + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: max7219digit + cs_pin: 6 + num_chips: 4 + rotate_chip: 0 + intensity: 10 + scroll_mode: STOP + id: my_matrix + lambda: |- + it.printdigit("hello"); diff --git a/tests/components/max9611/test.esp32-c3-idf.yaml b/tests/components/max9611/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..00f8330280d0 --- /dev/null +++ b/tests/components/max9611/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 5 + sda: 4 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/max9611/test.esp32-c3.yaml b/tests/components/max9611/test.esp32-c3.yaml new file mode 100644 index 000000000000..00f8330280d0 --- /dev/null +++ b/tests/components/max9611/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 5 + sda: 4 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/max9611/test.esp32-idf.yaml b/tests/components/max9611/test.esp32-idf.yaml new file mode 100644 index 000000000000..5c480cc81537 --- /dev/null +++ b/tests/components/max9611/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 16 + sda: 17 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/max9611/test.esp32.yaml b/tests/components/max9611/test.esp32.yaml new file mode 100644 index 000000000000..5c480cc81537 --- /dev/null +++ b/tests/components/max9611/test.esp32.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 16 + sda: 17 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/max9611/test.esp8266.yaml b/tests/components/max9611/test.esp8266.yaml new file mode 100644 index 000000000000..00f8330280d0 --- /dev/null +++ b/tests/components/max9611/test.esp8266.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 5 + sda: 4 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/max9611/test.rp2040.yaml b/tests/components/max9611/test.rp2040.yaml new file mode 100644 index 000000000000..00f8330280d0 --- /dev/null +++ b/tests/components/max9611/test.rp2040.yaml @@ -0,0 +1,18 @@ +i2c: + - id: i2c_max9611 + scl: 5 + sda: 4 + +sensor: + - platform: max9611 + shunt_resistance: 0.2 ohm + gain: 1X + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s diff --git a/tests/components/mcp23008/test.esp32-c3-idf.yaml b/tests/components/mcp23008/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..eabd5a731182 --- /dev/null +++ b/tests/components/mcp23008/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 5 + sda: 4 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23008/test.esp32-c3.yaml b/tests/components/mcp23008/test.esp32-c3.yaml new file mode 100644 index 000000000000..eabd5a731182 --- /dev/null +++ b/tests/components/mcp23008/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 5 + sda: 4 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23008/test.esp32-idf.yaml b/tests/components/mcp23008/test.esp32-idf.yaml new file mode 100644 index 000000000000..cbf03f371c63 --- /dev/null +++ b/tests/components/mcp23008/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 16 + sda: 17 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23008/test.esp32.yaml b/tests/components/mcp23008/test.esp32.yaml new file mode 100644 index 000000000000..cbf03f371c63 --- /dev/null +++ b/tests/components/mcp23008/test.esp32.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 16 + sda: 17 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23008/test.esp8266.yaml b/tests/components/mcp23008/test.esp8266.yaml new file mode 100644 index 000000000000..eabd5a731182 --- /dev/null +++ b/tests/components/mcp23008/test.esp8266.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 5 + sda: 4 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23008/test.rp2040.yaml b/tests/components/mcp23008/test.rp2040.yaml new file mode 100644 index 000000000000..eabd5a731182 --- /dev/null +++ b/tests/components/mcp23008/test.rp2040.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23008 + scl: 5 + sda: 4 + +mcp23008: + id: mcp23008_hub + +binary_sensor: + - platform: gpio + id: mcp23008_binary_sensor + pin: + mcp23xxx: mcp23008_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23008_switch + pin: + mcp23xxx: mcp23008_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.esp32-c3-idf.yaml b/tests/components/mcp23016/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2211931e3d0c --- /dev/null +++ b/tests/components/mcp23016/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 5 + sda: 4 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.esp32-c3.yaml b/tests/components/mcp23016/test.esp32-c3.yaml new file mode 100644 index 000000000000..2211931e3d0c --- /dev/null +++ b/tests/components/mcp23016/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 5 + sda: 4 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.esp32-idf.yaml b/tests/components/mcp23016/test.esp32-idf.yaml new file mode 100644 index 000000000000..48574a9b01ed --- /dev/null +++ b/tests/components/mcp23016/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 16 + sda: 17 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.esp32.yaml b/tests/components/mcp23016/test.esp32.yaml new file mode 100644 index 000000000000..48574a9b01ed --- /dev/null +++ b/tests/components/mcp23016/test.esp32.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 16 + sda: 17 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.esp8266.yaml b/tests/components/mcp23016/test.esp8266.yaml new file mode 100644 index 000000000000..2211931e3d0c --- /dev/null +++ b/tests/components/mcp23016/test.esp8266.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 5 + sda: 4 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23016/test.rp2040.yaml b/tests/components/mcp23016/test.rp2040.yaml new file mode 100644 index 000000000000..2211931e3d0c --- /dev/null +++ b/tests/components/mcp23016/test.rp2040.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23016 + scl: 5 + sda: 4 + +mcp23016: + id: mcp23016_hub + +binary_sensor: + - platform: gpio + id: mcp23016_binary_sensor + pin: + mcp23016: mcp23016_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23016_switch + pin: + mcp23016: mcp23016_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.esp32-c3-idf.yaml b/tests/components/mcp23017/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..863b2b8f0b07 --- /dev/null +++ b/tests/components/mcp23017/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 5 + sda: 4 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.esp32-c3.yaml b/tests/components/mcp23017/test.esp32-c3.yaml new file mode 100644 index 000000000000..863b2b8f0b07 --- /dev/null +++ b/tests/components/mcp23017/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 5 + sda: 4 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.esp32-idf.yaml b/tests/components/mcp23017/test.esp32-idf.yaml new file mode 100644 index 000000000000..9b7c45eb8a26 --- /dev/null +++ b/tests/components/mcp23017/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 16 + sda: 17 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.esp32.yaml b/tests/components/mcp23017/test.esp32.yaml new file mode 100644 index 000000000000..9b7c45eb8a26 --- /dev/null +++ b/tests/components/mcp23017/test.esp32.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 16 + sda: 17 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.esp8266.yaml b/tests/components/mcp23017/test.esp8266.yaml new file mode 100644 index 000000000000..863b2b8f0b07 --- /dev/null +++ b/tests/components/mcp23017/test.esp8266.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 5 + sda: 4 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23017/test.rp2040.yaml b/tests/components/mcp23017/test.rp2040.yaml new file mode 100644 index 000000000000..863b2b8f0b07 --- /dev/null +++ b/tests/components/mcp23017/test.rp2040.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_mcp23017 + scl: 5 + sda: 4 + +mcp23017: + id: mcp23017_hub + +binary_sensor: + - platform: gpio + id: mcp23017_binary_sensor + pin: + mcp23xxx: mcp23017_hub + number: 0 + mode: INPUT + +switch: + - platform: gpio + id: mcp23017_switch + pin: + mcp23xxx: mcp23017_hub + number: 1 + mode: OUTPUT diff --git a/tests/components/mcp23s08/test.esp32-c3-idf.yaml b/tests/components/mcp23s08/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f1af8a71a937 --- /dev/null +++ b/tests/components/mcp23s08/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 8 + deviceaddress: 0 diff --git a/tests/components/mcp23s08/test.esp32-c3.yaml b/tests/components/mcp23s08/test.esp32-c3.yaml new file mode 100644 index 000000000000..f1af8a71a937 --- /dev/null +++ b/tests/components/mcp23s08/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 8 + deviceaddress: 0 diff --git a/tests/components/mcp23s08/test.esp32-idf.yaml b/tests/components/mcp23s08/test.esp32-idf.yaml new file mode 100644 index 000000000000..0b26035c3e61 --- /dev/null +++ b/tests/components/mcp23s08/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 12 + deviceaddress: 0 diff --git a/tests/components/mcp23s08/test.esp32.yaml b/tests/components/mcp23s08/test.esp32.yaml new file mode 100644 index 000000000000..0b26035c3e61 --- /dev/null +++ b/tests/components/mcp23s08/test.esp32.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 12 + deviceaddress: 0 diff --git a/tests/components/mcp23s08/test.esp8266.yaml b/tests/components/mcp23s08/test.esp8266.yaml new file mode 100644 index 000000000000..eff856aca92f --- /dev/null +++ b/tests/components/mcp23s08/test.esp8266.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 15 + deviceaddress: 0 diff --git a/tests/components/mcp23s08/test.rp2040.yaml b/tests/components/mcp23s08/test.rp2040.yaml new file mode 100644 index 000000000000..1b23d2d3b502 --- /dev/null +++ b/tests/components/mcp23s08/test.rp2040.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s08 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +mcp23s08: + - id: mcp23s08_hub + cs_pin: 6 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.esp32-c3-idf.yaml b/tests/components/mcp23s17/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d83f66d3b109 --- /dev/null +++ b/tests/components/mcp23s17/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 8 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.esp32-c3.yaml b/tests/components/mcp23s17/test.esp32-c3.yaml new file mode 100644 index 000000000000..d83f66d3b109 --- /dev/null +++ b/tests/components/mcp23s17/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 8 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.esp32-idf.yaml b/tests/components/mcp23s17/test.esp32-idf.yaml new file mode 100644 index 000000000000..9a42c12e85a4 --- /dev/null +++ b/tests/components/mcp23s17/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 12 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.esp32.yaml b/tests/components/mcp23s17/test.esp32.yaml new file mode 100644 index 000000000000..9a42c12e85a4 --- /dev/null +++ b/tests/components/mcp23s17/test.esp32.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 12 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.esp8266.yaml b/tests/components/mcp23s17/test.esp8266.yaml new file mode 100644 index 000000000000..36dac63f6f6a --- /dev/null +++ b/tests/components/mcp23s17/test.esp8266.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 15 + deviceaddress: 0 diff --git a/tests/components/mcp23s17/test.rp2040.yaml b/tests/components/mcp23s17/test.rp2040.yaml new file mode 100644 index 000000000000..2730f6a9d6ae --- /dev/null +++ b/tests/components/mcp23s17/test.rp2040.yaml @@ -0,0 +1,10 @@ +spi: + - id: spi_mcp23s17 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +mcp23s17: + - id: mcp23s17_hub + cs_pin: 6 + deviceaddress: 0 diff --git a/tests/components/mcp2515/test.esp32-c3-idf.yaml b/tests/components/mcp2515/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3ceeea268f3c --- /dev/null +++ b/tests/components/mcp2515/test.esp32-c3-idf.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 8 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp2515/test.esp32-c3.yaml b/tests/components/mcp2515/test.esp32-c3.yaml new file mode 100644 index 000000000000..3ceeea268f3c --- /dev/null +++ b/tests/components/mcp2515/test.esp32-c3.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 8 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp2515/test.esp32-idf.yaml b/tests/components/mcp2515/test.esp32-idf.yaml new file mode 100644 index 000000000000..07fae36cc325 --- /dev/null +++ b/tests/components/mcp2515/test.esp32-idf.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 12 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp2515/test.esp32.yaml b/tests/components/mcp2515/test.esp32.yaml new file mode 100644 index 000000000000..07fae36cc325 --- /dev/null +++ b/tests/components/mcp2515/test.esp32.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 12 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp2515/test.esp8266.yaml b/tests/components/mcp2515/test.esp8266.yaml new file mode 100644 index 000000000000..1096a0e80916 --- /dev/null +++ b/tests/components/mcp2515/test.esp8266.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 15 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp2515/test.rp2040.yaml b/tests/components/mcp2515/test.rp2040.yaml new file mode 100644 index 000000000000..678c817d3db6 --- /dev/null +++ b/tests/components/mcp2515/test.rp2040.yaml @@ -0,0 +1,44 @@ +spi: + - id: spi_mcp2515 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +canbus: + - platform: mcp2515 + id: mcp2515_can + cs_pin: 6 + can_id: 4 + bit_rate: 50kbps + on_frame: + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("can_id 500", "%s", b.c_str()); + - can_id: 23 + then: + - if: + condition: + lambda: "return x[0] == 0x11;" + then: + logger.log: "x[0] == 0x11" + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } diff --git a/tests/components/mcp3008/test.esp32-c3-idf.yaml b/tests/components/mcp3008/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9e66372e4f3c --- /dev/null +++ b/tests/components/mcp3008/test.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp3008: + - id: mcp3008_hub + cs_pin: 8 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3008/test.esp32-c3.yaml b/tests/components/mcp3008/test.esp32-c3.yaml new file mode 100644 index 000000000000..9e66372e4f3c --- /dev/null +++ b/tests/components/mcp3008/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp3008: + - id: mcp3008_hub + cs_pin: 8 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3008/test.esp32-idf.yaml b/tests/components/mcp3008/test.esp32-idf.yaml new file mode 100644 index 000000000000..a66fbeb7a14b --- /dev/null +++ b/tests/components/mcp3008/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp3008: + - id: mcp3008_hub + cs_pin: 12 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3008/test.esp32.yaml b/tests/components/mcp3008/test.esp32.yaml new file mode 100644 index 000000000000..a66fbeb7a14b --- /dev/null +++ b/tests/components/mcp3008/test.esp32.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp3008: + - id: mcp3008_hub + cs_pin: 12 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3008/test.esp8266.yaml b/tests/components/mcp3008/test.esp8266.yaml new file mode 100644 index 000000000000..eaccca076577 --- /dev/null +++ b/tests/components/mcp3008/test.esp8266.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +mcp3008: + - id: mcp3008_hub + cs_pin: 15 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3008/test.rp2040.yaml b/tests/components/mcp3008/test.rp2040.yaml new file mode 100644 index 000000000000..8ab963055368 --- /dev/null +++ b/tests/components/mcp3008/test.rp2040.yaml @@ -0,0 +1,17 @@ +spi: + - id: spi_mcp3008 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +mcp3008: + - id: mcp3008_hub + cs_pin: 6 + +sensor: + - platform: mcp3008 + id: mcp3008_sensor + mcp3008_id: mcp3008_hub + number: 0 + reference_voltage: 3.19 + update_interval: 5s diff --git a/tests/components/mcp3204/test.esp32-c3-idf.yaml b/tests/components/mcp3204/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5bf5ba81e1d3 --- /dev/null +++ b/tests/components/mcp3204/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp3204: + - id: mcp3204_hub + cs_pin: 8 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp3204/test.esp32-c3.yaml b/tests/components/mcp3204/test.esp32-c3.yaml new file mode 100644 index 000000000000..5bf5ba81e1d3 --- /dev/null +++ b/tests/components/mcp3204/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +mcp3204: + - id: mcp3204_hub + cs_pin: 8 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp3204/test.esp32-idf.yaml b/tests/components/mcp3204/test.esp32-idf.yaml new file mode 100644 index 000000000000..c340797c8eaf --- /dev/null +++ b/tests/components/mcp3204/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp3204: + - id: mcp3204_hub + cs_pin: 12 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp3204/test.esp32.yaml b/tests/components/mcp3204/test.esp32.yaml new file mode 100644 index 000000000000..c340797c8eaf --- /dev/null +++ b/tests/components/mcp3204/test.esp32.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +mcp3204: + - id: mcp3204_hub + cs_pin: 12 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp3204/test.esp8266.yaml b/tests/components/mcp3204/test.esp8266.yaml new file mode 100644 index 000000000000..d208e3e06c0e --- /dev/null +++ b/tests/components/mcp3204/test.esp8266.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +mcp3204: + - id: mcp3204_hub + cs_pin: 15 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp3204/test.rp2040.yaml b/tests/components/mcp3204/test.rp2040.yaml new file mode 100644 index 000000000000..63f30e3621c9 --- /dev/null +++ b/tests/components/mcp3204/test.rp2040.yaml @@ -0,0 +1,16 @@ +spi: + - id: spi_mcp3204 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +mcp3204: + - id: mcp3204_hub + cs_pin: 6 + +sensor: + - platform: mcp3204 + id: mcp3204_sensor + mcp3204_id: mcp3204_hub + number: 0 + update_interval: 5s diff --git a/tests/components/mcp4725/test.esp32-c3-idf.yaml b/tests/components/mcp4725/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5fc799203d2d --- /dev/null +++ b/tests/components/mcp4725/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 5 + sda: 4 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4725/test.esp32-c3.yaml b/tests/components/mcp4725/test.esp32-c3.yaml new file mode 100644 index 000000000000..5fc799203d2d --- /dev/null +++ b/tests/components/mcp4725/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 5 + sda: 4 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4725/test.esp32-idf.yaml b/tests/components/mcp4725/test.esp32-idf.yaml new file mode 100644 index 000000000000..a523ad95e197 --- /dev/null +++ b/tests/components/mcp4725/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 16 + sda: 17 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4725/test.esp32.yaml b/tests/components/mcp4725/test.esp32.yaml new file mode 100644 index 000000000000..a523ad95e197 --- /dev/null +++ b/tests/components/mcp4725/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 16 + sda: 17 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4725/test.esp8266.yaml b/tests/components/mcp4725/test.esp8266.yaml new file mode 100644 index 000000000000..5fc799203d2d --- /dev/null +++ b/tests/components/mcp4725/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 5 + sda: 4 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4725/test.rp2040.yaml b/tests/components/mcp4725/test.rp2040.yaml new file mode 100644 index 000000000000..5fc799203d2d --- /dev/null +++ b/tests/components/mcp4725/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp4725 + scl: 5 + sda: 4 + +output: + - platform: mcp4725 + id: mcp4725_dac_output diff --git a/tests/components/mcp4728/test.esp32-c3-idf.yaml b/tests/components/mcp4728/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2f24dd0b8c06 --- /dev/null +++ b/tests/components/mcp4728/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 5 + sda: 4 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp4728/test.esp32-c3.yaml b/tests/components/mcp4728/test.esp32-c3.yaml new file mode 100644 index 000000000000..2f24dd0b8c06 --- /dev/null +++ b/tests/components/mcp4728/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 5 + sda: 4 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp4728/test.esp32-idf.yaml b/tests/components/mcp4728/test.esp32-idf.yaml new file mode 100644 index 000000000000..b29a6ee53cf9 --- /dev/null +++ b/tests/components/mcp4728/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 16 + sda: 17 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp4728/test.esp32.yaml b/tests/components/mcp4728/test.esp32.yaml new file mode 100644 index 000000000000..b29a6ee53cf9 --- /dev/null +++ b/tests/components/mcp4728/test.esp32.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 16 + sda: 17 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp4728/test.esp8266.yaml b/tests/components/mcp4728/test.esp8266.yaml new file mode 100644 index 000000000000..2f24dd0b8c06 --- /dev/null +++ b/tests/components/mcp4728/test.esp8266.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 5 + sda: 4 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp4728/test.rp2040.yaml b/tests/components/mcp4728/test.rp2040.yaml new file mode 100644 index 000000000000..2f24dd0b8c06 --- /dev/null +++ b/tests/components/mcp4728/test.rp2040.yaml @@ -0,0 +1,31 @@ +i2c: + - id: i2c_mcp4728 + scl: 5 + sda: 4 + +mcp4728: + - id: mcp4728_dac + +output: + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k diff --git a/tests/components/mcp47a1/test.esp32-c3-idf.yaml b/tests/components/mcp47a1/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..68273e00eb0b --- /dev/null +++ b/tests/components/mcp47a1/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 5 + sda: 4 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp47a1/test.esp32-c3.yaml b/tests/components/mcp47a1/test.esp32-c3.yaml new file mode 100644 index 000000000000..68273e00eb0b --- /dev/null +++ b/tests/components/mcp47a1/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 5 + sda: 4 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp47a1/test.esp32-idf.yaml b/tests/components/mcp47a1/test.esp32-idf.yaml new file mode 100644 index 000000000000..9e2133de668a --- /dev/null +++ b/tests/components/mcp47a1/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 16 + sda: 17 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp47a1/test.esp32.yaml b/tests/components/mcp47a1/test.esp32.yaml new file mode 100644 index 000000000000..9e2133de668a --- /dev/null +++ b/tests/components/mcp47a1/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 16 + sda: 17 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp47a1/test.esp8266.yaml b/tests/components/mcp47a1/test.esp8266.yaml new file mode 100644 index 000000000000..68273e00eb0b --- /dev/null +++ b/tests/components/mcp47a1/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 5 + sda: 4 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp47a1/test.rp2040.yaml b/tests/components/mcp47a1/test.rp2040.yaml new file mode 100644 index 000000000000..68273e00eb0b --- /dev/null +++ b/tests/components/mcp47a1/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp47a1 + scl: 5 + sda: 4 + +output: + - platform: mcp47a1 + id: output_mcp47a1 diff --git a/tests/components/mcp9600/test.esp32-c3-idf.yaml b/tests/components/mcp9600/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b07f4589ce18 --- /dev/null +++ b/tests/components/mcp9600/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9600/test.esp32-c3.yaml b/tests/components/mcp9600/test.esp32-c3.yaml new file mode 100644 index 000000000000..b07f4589ce18 --- /dev/null +++ b/tests/components/mcp9600/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9600/test.esp32-idf.yaml b/tests/components/mcp9600/test.esp32-idf.yaml new file mode 100644 index 000000000000..0c94f099ae4d --- /dev/null +++ b/tests/components/mcp9600/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 16 + sda: 17 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9600/test.esp32.yaml b/tests/components/mcp9600/test.esp32.yaml new file mode 100644 index 000000000000..0c94f099ae4d --- /dev/null +++ b/tests/components/mcp9600/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 16 + sda: 17 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9600/test.esp8266.yaml b/tests/components/mcp9600/test.esp8266.yaml new file mode 100644 index 000000000000..b07f4589ce18 --- /dev/null +++ b/tests/components/mcp9600/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9600/test.rp2040.yaml b/tests/components/mcp9600/test.rp2040.yaml new file mode 100644 index 000000000000..b07f4589ce18 --- /dev/null +++ b/tests/components/mcp9600/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mcp9600 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature diff --git a/tests/components/mcp9808/test.esp32-c3-idf.yaml b/tests/components/mcp9808/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..86b4d7f181e7 --- /dev/null +++ b/tests/components/mcp9808/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mcp9808/test.esp32-c3.yaml b/tests/components/mcp9808/test.esp32-c3.yaml new file mode 100644 index 000000000000..86b4d7f181e7 --- /dev/null +++ b/tests/components/mcp9808/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mcp9808/test.esp32-idf.yaml b/tests/components/mcp9808/test.esp32-idf.yaml new file mode 100644 index 000000000000..1e5affdac086 --- /dev/null +++ b/tests/components/mcp9808/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 16 + sda: 17 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mcp9808/test.esp32.yaml b/tests/components/mcp9808/test.esp32.yaml new file mode 100644 index 000000000000..1e5affdac086 --- /dev/null +++ b/tests/components/mcp9808/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 16 + sda: 17 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mcp9808/test.esp8266.yaml b/tests/components/mcp9808/test.esp8266.yaml new file mode 100644 index 000000000000..86b4d7f181e7 --- /dev/null +++ b/tests/components/mcp9808/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mcp9808/test.rp2040.yaml b/tests/components/mcp9808/test.rp2040.yaml new file mode 100644 index 000000000000..86b4d7f181e7 --- /dev/null +++ b/tests/components/mcp9808/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_mcp9808 + scl: 5 + sda: 4 + +sensor: + - platform: mcp9808 + name: MCP9808 Temperature diff --git a/tests/components/mdns/common.yaml b/tests/components/mdns/common.yaml new file mode 100644 index 000000000000..bc31e3278365 --- /dev/null +++ b/tests/components/mdns/common.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +mdns: + disabled: false diff --git a/tests/components/mdns/test.esp32-c3-idf.yaml b/tests/components/mdns/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mdns/test.esp32-c3.yaml b/tests/components/mdns/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mdns/test.esp32-idf.yaml b/tests/components/mdns/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mdns/test.esp32.yaml b/tests/components/mdns/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mdns/test.esp8266.yaml b/tests/components/mdns/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mdns/test.rp2040.yaml b/tests/components/mdns/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mdns/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/media_player/common.yaml b/tests/components/media_player/common.yaml new file mode 100644 index 000000000000..24b85cd474e0 --- /dev/null +++ b/tests/components/media_player/common.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +i2s_audio: + i2s_lrclk_pin: 13 + i2s_bclk_pin: 14 + i2s_mclk_pin: 15 + +media_player: + - platform: i2s_audio + name: None + dac_type: external + i2s_dout_pin: 18 + mute_pin: 19 + on_state: + - media_player.play: + - media_player.play_media: http://localhost/media.mp3 + - media_player.play_media: !lambda 'return "http://localhost/media.mp3";' + on_idle: + - media_player.pause: + on_play: + - media_player.stop: + on_pause: + - media_player.toggle: + - wait_until: + media_player.is_idle: + - wait_until: + media_player.is_playing: + - media_player.volume_up: + - media_player.volume_down: + - media_player.volume_set: 50% diff --git a/tests/components/media_player/test.esp32.yaml b/tests/components/media_player/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/media_player/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mhz19/test.esp32-c3-idf.yaml b/tests/components/mhz19/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..1edfa49c2335 --- /dev/null +++ b/tests/components/mhz19/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/mhz19/test.esp32-c3.yaml b/tests/components/mhz19/test.esp32-c3.yaml new file mode 100644 index 000000000000..1edfa49c2335 --- /dev/null +++ b/tests/components/mhz19/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/mhz19/test.esp32-idf.yaml b/tests/components/mhz19/test.esp32-idf.yaml new file mode 100644 index 000000000000..0e30713b541a --- /dev/null +++ b/tests/components/mhz19/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/mhz19/test.esp32.yaml b/tests/components/mhz19/test.esp32.yaml new file mode 100644 index 000000000000..0e30713b541a --- /dev/null +++ b/tests/components/mhz19/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/mhz19/test.esp8266.yaml b/tests/components/mhz19/test.esp8266.yaml new file mode 100644 index 000000000000..1edfa49c2335 --- /dev/null +++ b/tests/components/mhz19/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/mhz19/test.rp2040.yaml b/tests/components/mhz19/test.rp2040.yaml new file mode 100644 index 000000000000..1edfa49c2335 --- /dev/null +++ b/tests/components/mhz19/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_mhz19 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: mhz19 + co2: + name: MH-Z19 CO2 Value + temperature: + name: MH-Z19 Temperature + automatic_baseline_calibration: false + update_interval: 15s diff --git a/tests/components/micro_wake_word/common.yaml b/tests/components/micro_wake_word/common.yaml new file mode 100644 index 000000000000..c0f3593cc6a3 --- /dev/null +++ b/tests/components/micro_wake_word/common.yaml @@ -0,0 +1,15 @@ +i2s_audio: + i2s_lrclk_pin: GPIO18 + i2s_bclk_pin: GPIO19 + +microphone: + - platform: i2s_audio + id: echo_microphone + i2s_din_pin: GPIO17 + adc_type: external + pdm: true + +micro_wake_word: + model: hey_jarvis + on_wake_word_detected: + - logger.log: "Wake word detected" diff --git a/tests/components/micro_wake_word/test.esp32-s3-idf.yaml b/tests/components/micro_wake_word/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/micro_wake_word/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/micronova/test.esp32-c3-idf.yaml b/tests/components/micronova/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ec9699909e89 --- /dev/null +++ b/tests/components/micronova/test.esp32-c3-idf.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +micronova: + enable_rx_pin: 6 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/micronova/test.esp32-c3.yaml b/tests/components/micronova/test.esp32-c3.yaml new file mode 100644 index 000000000000..ec9699909e89 --- /dev/null +++ b/tests/components/micronova/test.esp32-c3.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +micronova: + enable_rx_pin: 6 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/micronova/test.esp32-idf.yaml b/tests/components/micronova/test.esp32-idf.yaml new file mode 100644 index 000000000000..9156f7d6a98e --- /dev/null +++ b/tests/components/micronova/test.esp32-idf.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +micronova: + enable_rx_pin: 18 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/micronova/test.esp32.yaml b/tests/components/micronova/test.esp32.yaml new file mode 100644 index 000000000000..9156f7d6a98e --- /dev/null +++ b/tests/components/micronova/test.esp32.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +micronova: + enable_rx_pin: 18 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/micronova/test.esp8266.yaml b/tests/components/micronova/test.esp8266.yaml new file mode 100644 index 000000000000..d10ab7ad7aaf --- /dev/null +++ b/tests/components/micronova/test.esp8266.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +micronova: + enable_rx_pin: 16 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/micronova/test.rp2040.yaml b/tests/components/micronova/test.rp2040.yaml new file mode 100644 index 000000000000..ec9699909e89 --- /dev/null +++ b/tests/components/micronova/test.rp2040.yaml @@ -0,0 +1,49 @@ +uart: + - id: uart_micronova + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +micronova: + enable_rx_pin: 6 + +button: + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F + +number: + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level + +sensor: + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + +switch: + - platform: micronova + stove: + name: Stove on/off diff --git a/tests/components/microphone/test.esp32-c3-idf.yaml b/tests/components/microphone/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..706a38f910b9 --- /dev/null +++ b/tests/components/microphone/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 8 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false diff --git a/tests/components/microphone/test.esp32-c3.yaml b/tests/components/microphone/test.esp32-c3.yaml new file mode 100644 index 000000000000..706a38f910b9 --- /dev/null +++ b/tests/components/microphone/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 8 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false diff --git a/tests/components/microphone/test.esp32-idf.yaml b/tests/components/microphone/test.esp32-idf.yaml new file mode 100644 index 000000000000..166eedb54da7 --- /dev/null +++ b/tests/components/microphone/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2s_audio: + i2s_lrclk_pin: 13 + i2s_bclk_pin: 14 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_adc + adc_pin: 32 + adc_type: internal + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 33 + adc_type: external + pdm: false diff --git a/tests/components/microphone/test.esp32.yaml b/tests/components/microphone/test.esp32.yaml new file mode 100644 index 000000000000..166eedb54da7 --- /dev/null +++ b/tests/components/microphone/test.esp32.yaml @@ -0,0 +1,15 @@ +i2s_audio: + i2s_lrclk_pin: 13 + i2s_bclk_pin: 14 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_adc + adc_pin: 32 + adc_type: internal + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 33 + adc_type: external + pdm: false diff --git a/tests/components/mics_4514/test.esp32-c3-idf.yaml b/tests/components/mics_4514/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..72369bec0160 --- /dev/null +++ b/tests/components/mics_4514/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 5 + sda: 4 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/mics_4514/test.esp32-c3.yaml b/tests/components/mics_4514/test.esp32-c3.yaml new file mode 100644 index 000000000000..72369bec0160 --- /dev/null +++ b/tests/components/mics_4514/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 5 + sda: 4 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/mics_4514/test.esp32-idf.yaml b/tests/components/mics_4514/test.esp32-idf.yaml new file mode 100644 index 000000000000..234839c91c93 --- /dev/null +++ b/tests/components/mics_4514/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 16 + sda: 17 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/mics_4514/test.esp32.yaml b/tests/components/mics_4514/test.esp32.yaml new file mode 100644 index 000000000000..234839c91c93 --- /dev/null +++ b/tests/components/mics_4514/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 16 + sda: 17 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/mics_4514/test.esp8266.yaml b/tests/components/mics_4514/test.esp8266.yaml new file mode 100644 index 000000000000..72369bec0160 --- /dev/null +++ b/tests/components/mics_4514/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 5 + sda: 4 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/mics_4514/test.rp2040.yaml b/tests/components/mics_4514/test.rp2040.yaml new file mode 100644 index 000000000000..72369bec0160 --- /dev/null +++ b/tests/components/mics_4514/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mics_4514 + scl: 5 + sda: 4 + +sensor: + - platform: mics_4514 + update_interval: 60s + nitrogen_dioxide: + name: MICS-4514 NO2 + carbon_monoxide: + name: MICS-4514 CO + methane: + name: MICS-4514 CH4 + hydrogen: + name: MICS-4514 H2 + ethanol: + name: MICS-4514 C2H5OH + ammonia: + name: MICS-4514 NH3 diff --git a/tests/components/midea/test.esp32-c3.yaml b/tests/components/midea/test.esp32-c3.yaml new file mode 100644 index 000000000000..bcb8635eafa8 --- /dev/null +++ b/tests/components/midea/test.esp32-c3.yaml @@ -0,0 +1,59 @@ +wifi: + ssid: MySSID + password: password1 + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +uart: + - id: uart_midea + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: midea + id: midea_unit + name: Midea Climate + on_control: + - logger.log: Control message received! + - lambda: |- + x.set_mode(CLIMATE_MODE_FAN_ONLY); + on_state: + - logger.log: State changed! + transmitter_id: + period: 1s + num_attempts: 5 + timeout: 2s + beeper: false + autoconf: true + visual: + min_temperature: 17 °C + max_temperature: 30 °C + temperature_step: 0.5 °C + supported_modes: + - FAN_ONLY + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: + - ECO + - BOOST + - SLEEP + custom_presets: + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH + outdoor_temperature: + name: Temp + power_usage: + name: Power + humidity_setpoint: + name: Humidity diff --git a/tests/components/midea/test.esp32.yaml b/tests/components/midea/test.esp32.yaml new file mode 100644 index 000000000000..5c638b96132f --- /dev/null +++ b/tests/components/midea/test.esp32.yaml @@ -0,0 +1,59 @@ +wifi: + ssid: MySSID + password: password1 + +remote_transmitter: + pin: 18 + carrier_duty_percent: 50% + +uart: + - id: uart_midea + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +climate: + - platform: midea + id: midea_unit + name: Midea Climate + on_control: + - logger.log: Control message received! + - lambda: |- + x.set_mode(CLIMATE_MODE_FAN_ONLY); + on_state: + - logger.log: State changed! + transmitter_id: + period: 1s + num_attempts: 5 + timeout: 2s + beeper: false + autoconf: true + visual: + min_temperature: 17 °C + max_temperature: 30 °C + temperature_step: 0.5 °C + supported_modes: + - FAN_ONLY + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: + - ECO + - BOOST + - SLEEP + custom_presets: + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH + outdoor_temperature: + name: Temp + power_usage: + name: Power + humidity_setpoint: + name: Humidity diff --git a/tests/components/midea/test.esp8266.yaml b/tests/components/midea/test.esp8266.yaml new file mode 100644 index 000000000000..b0ed7eb472cf --- /dev/null +++ b/tests/components/midea/test.esp8266.yaml @@ -0,0 +1,59 @@ +wifi: + ssid: MySSID + password: password1 + +remote_transmitter: + pin: 12 + carrier_duty_percent: 50% + +uart: + - id: uart_midea + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +climate: + - platform: midea + id: midea_unit + name: Midea Climate + on_control: + - logger.log: Control message received! + - lambda: |- + x.set_mode(CLIMATE_MODE_FAN_ONLY); + on_state: + - logger.log: State changed! + transmitter_id: + period: 1s + num_attempts: 5 + timeout: 2s + beeper: false + autoconf: true + visual: + min_temperature: 17 °C + max_temperature: 30 °C + temperature_step: 0.5 °C + supported_modes: + - FAN_ONLY + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: + - ECO + - BOOST + - SLEEP + custom_presets: + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH + outdoor_temperature: + name: Temp + power_usage: + name: Power + humidity_setpoint: + name: Humidity diff --git a/tests/components/midea_ir/common.yaml b/tests/components/midea_ir/common.yaml new file mode 100644 index 000000000000..e8d89cecc29b --- /dev/null +++ b/tests/components/midea_ir/common.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + pin: 4 + carrier_duty_percent: 50% + +climate: + - platform: midea_ir + name: Midea IR + use_fahrenheit: true diff --git a/tests/components/midea_ir/test.esp32-c3-idf.yaml b/tests/components/midea_ir/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/midea_ir/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/midea_ir/test.esp32-c3.yaml b/tests/components/midea_ir/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/midea_ir/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/midea_ir/test.esp32-idf.yaml b/tests/components/midea_ir/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/midea_ir/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/midea_ir/test.esp32.yaml b/tests/components/midea_ir/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/midea_ir/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/midea_ir/test.esp8266.yaml b/tests/components/midea_ir/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/midea_ir/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mitsubishi/common.yaml b/tests/components/mitsubishi/common.yaml new file mode 100644 index 000000000000..c0fc959c5bf4 --- /dev/null +++ b/tests/components/mitsubishi/common.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 4 + carrier_duty_percent: 50% + +climate: + - platform: mitsubishi + name: Mitsubishi diff --git a/tests/components/mitsubishi/test.esp32-c3-idf.yaml b/tests/components/mitsubishi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mitsubishi/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mitsubishi/test.esp32-c3.yaml b/tests/components/mitsubishi/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mitsubishi/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mitsubishi/test.esp32-idf.yaml b/tests/components/mitsubishi/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mitsubishi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mitsubishi/test.esp32.yaml b/tests/components/mitsubishi/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mitsubishi/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mitsubishi/test.esp8266.yaml b/tests/components/mitsubishi/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mitsubishi/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mlx90393/test.esp32-c3-idf.yaml b/tests/components/mlx90393/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..549eea803291 --- /dev/null +++ b/tests/components/mlx90393/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90393/test.esp32-c3.yaml b/tests/components/mlx90393/test.esp32-c3.yaml new file mode 100644 index 000000000000..549eea803291 --- /dev/null +++ b/tests/components/mlx90393/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90393/test.esp32-idf.yaml b/tests/components/mlx90393/test.esp32-idf.yaml new file mode 100644 index 000000000000..089fd136f41e --- /dev/null +++ b/tests/components/mlx90393/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 16 + sda: 17 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90393/test.esp32.yaml b/tests/components/mlx90393/test.esp32.yaml new file mode 100644 index 000000000000..089fd136f41e --- /dev/null +++ b/tests/components/mlx90393/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 16 + sda: 17 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90393/test.esp8266.yaml b/tests/components/mlx90393/test.esp8266.yaml new file mode 100644 index 000000000000..549eea803291 --- /dev/null +++ b/tests/components/mlx90393/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90393/test.rp2040.yaml b/tests/components/mlx90393/test.rp2040.yaml new file mode 100644 index 000000000000..549eea803291 --- /dev/null +++ b/tests/components/mlx90393/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_mlx90393 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: 3X + x_axis: + name: mlxxaxis + y_axis: + name: mlxyaxis + z_axis: + name: mlxzaxis + resolution: 17BIT + temperature: + name: mlxtemp + oversampling: 2 diff --git a/tests/components/mlx90614/test.esp32-c3-idf.yaml b/tests/components/mlx90614/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a863e0ee2e03 --- /dev/null +++ b/tests/components/mlx90614/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mlx90614/test.esp32-c3.yaml b/tests/components/mlx90614/test.esp32-c3.yaml new file mode 100644 index 000000000000..a863e0ee2e03 --- /dev/null +++ b/tests/components/mlx90614/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mlx90614/test.esp32-idf.yaml b/tests/components/mlx90614/test.esp32-idf.yaml new file mode 100644 index 000000000000..8c1ee68f42ec --- /dev/null +++ b/tests/components/mlx90614/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 16 + sda: 17 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mlx90614/test.esp32.yaml b/tests/components/mlx90614/test.esp32.yaml new file mode 100644 index 000000000000..8c1ee68f42ec --- /dev/null +++ b/tests/components/mlx90614/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 16 + sda: 17 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mlx90614/test.esp8266.yaml b/tests/components/mlx90614/test.esp8266.yaml new file mode 100644 index 000000000000..a863e0ee2e03 --- /dev/null +++ b/tests/components/mlx90614/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mlx90614/test.rp2040.yaml b/tests/components/mlx90614/test.rp2040.yaml new file mode 100644 index 000000000000..a863e0ee2e03 --- /dev/null +++ b/tests/components/mlx90614/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mlx90614 + scl: 5 + sda: 4 + +sensor: + - platform: mlx90614 + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 diff --git a/tests/components/mmc5603/test.esp32-c3-idf.yaml b/tests/components/mmc5603/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..834591bb396f --- /dev/null +++ b/tests/components/mmc5603/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5603/test.esp32-c3.yaml b/tests/components/mmc5603/test.esp32-c3.yaml new file mode 100644 index 000000000000..834591bb396f --- /dev/null +++ b/tests/components/mmc5603/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5603/test.esp32-idf.yaml b/tests/components/mmc5603/test.esp32-idf.yaml new file mode 100644 index 000000000000..fbb83cd9f8a5 --- /dev/null +++ b/tests/components/mmc5603/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 16 + sda: 17 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5603/test.esp32.yaml b/tests/components/mmc5603/test.esp32.yaml new file mode 100644 index 000000000000..fbb83cd9f8a5 --- /dev/null +++ b/tests/components/mmc5603/test.esp32.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 16 + sda: 17 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5603/test.esp8266.yaml b/tests/components/mmc5603/test.esp8266.yaml new file mode 100644 index 000000000000..834591bb396f --- /dev/null +++ b/tests/components/mmc5603/test.esp8266.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5603/test.rp2040.yaml b/tests/components/mmc5603/test.rp2040.yaml new file mode 100644 index 000000000000..834591bb396f --- /dev/null +++ b/tests/components/mmc5603/test.rp2040.yaml @@ -0,0 +1,14 @@ +i2c: + - id: i2c_mmc5603 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5603 + address: 0x30 + field_strength_x: + name: HMC5883L Field Strength X + field_strength_y: + name: HMC5883L Field Strength Y + field_strength_z: + name: HMC5883L Field Strength Z diff --git a/tests/components/mmc5983/test.esp32-c3-idf.yaml b/tests/components/mmc5983/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..68d821e9a551 --- /dev/null +++ b/tests/components/mmc5983/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/mmc5983/test.esp32-c3.yaml b/tests/components/mmc5983/test.esp32-c3.yaml new file mode 100644 index 000000000000..68d821e9a551 --- /dev/null +++ b/tests/components/mmc5983/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/mmc5983/test.esp32-idf.yaml b/tests/components/mmc5983/test.esp32-idf.yaml new file mode 100644 index 000000000000..6104be9b8394 --- /dev/null +++ b/tests/components/mmc5983/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 16 + sda: 17 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/mmc5983/test.esp32.yaml b/tests/components/mmc5983/test.esp32.yaml new file mode 100644 index 000000000000..6104be9b8394 --- /dev/null +++ b/tests/components/mmc5983/test.esp32.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 16 + sda: 17 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/mmc5983/test.esp8266.yaml b/tests/components/mmc5983/test.esp8266.yaml new file mode 100644 index 000000000000..68d821e9a551 --- /dev/null +++ b/tests/components/mmc5983/test.esp8266.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/mmc5983/test.rp2040.yaml b/tests/components/mmc5983/test.rp2040.yaml new file mode 100644 index 000000000000..68d821e9a551 --- /dev/null +++ b/tests/components/mmc5983/test.rp2040.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_mmc5983 + scl: 5 + sda: 4 + +sensor: + - platform: mmc5983 + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z diff --git a/tests/components/modbus/test.esp32-c3-idf.yaml b/tests/components/modbus/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d22b507be02b --- /dev/null +++ b/tests/components/modbus/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 diff --git a/tests/components/modbus/test.esp32-c3.yaml b/tests/components/modbus/test.esp32-c3.yaml new file mode 100644 index 000000000000..d22b507be02b --- /dev/null +++ b/tests/components/modbus/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 diff --git a/tests/components/modbus/test.esp32-idf.yaml b/tests/components/modbus/test.esp32-idf.yaml new file mode 100644 index 000000000000..20cf238b1bca --- /dev/null +++ b/tests/components/modbus/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 15 diff --git a/tests/components/modbus/test.esp32.yaml b/tests/components/modbus/test.esp32.yaml new file mode 100644 index 000000000000..20cf238b1bca --- /dev/null +++ b/tests/components/modbus/test.esp32.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 15 diff --git a/tests/components/modbus/test.esp8266.yaml b/tests/components/modbus/test.esp8266.yaml new file mode 100644 index 000000000000..560c044766db --- /dev/null +++ b/tests/components/modbus/test.esp8266.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 12 diff --git a/tests/components/modbus/test.rp2040.yaml b/tests/components/modbus/test.rp2040.yaml new file mode 100644 index 000000000000..d22b507be02b --- /dev/null +++ b/tests/components/modbus/test.rp2040.yaml @@ -0,0 +1,9 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 diff --git a/tests/components/modbus_controller/test.esp32-c3-idf.yaml b/tests/components/modbus_controller/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..476e65ecb0d6 --- /dev/null +++ b/tests/components/modbus_controller/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/modbus_controller/test.esp32-c3.yaml b/tests/components/modbus_controller/test.esp32-c3.yaml new file mode 100644 index 000000000000..476e65ecb0d6 --- /dev/null +++ b/tests/components/modbus_controller/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/modbus_controller/test.esp32-idf.yaml b/tests/components/modbus_controller/test.esp32-idf.yaml new file mode 100644 index 000000000000..c5fe3fd057a9 --- /dev/null +++ b/tests/components/modbus_controller/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 15 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/modbus_controller/test.esp32.yaml b/tests/components/modbus_controller/test.esp32.yaml new file mode 100644 index 000000000000..c5fe3fd057a9 --- /dev/null +++ b/tests/components/modbus_controller/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 15 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/modbus_controller/test.esp8266.yaml b/tests/components/modbus_controller/test.esp8266.yaml new file mode 100644 index 000000000000..67cac65d1b06 --- /dev/null +++ b/tests/components/modbus_controller/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 12 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/modbus_controller/test.rp2040.yaml b/tests/components/modbus_controller/test.rp2040.yaml new file mode 100644 index 000000000000..476e65ecb0d6 --- /dev/null +++ b/tests/components/modbus_controller/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_modbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + id: mod_bus1 + flow_control_pin: 6 + +modbus_controller: + - id: modbus_controller1 + address: 0x2 + modbus_id: mod_bus1 diff --git a/tests/components/monochromatic/test.esp32-c3-idf.yaml b/tests/components/monochromatic/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9524efcb2d5b --- /dev/null +++ b/tests/components/monochromatic/test.esp32-c3-idf.yaml @@ -0,0 +1,40 @@ +output: + - platform: ledc + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/monochromatic/test.esp32-c3.yaml b/tests/components/monochromatic/test.esp32-c3.yaml new file mode 100644 index 000000000000..9524efcb2d5b --- /dev/null +++ b/tests/components/monochromatic/test.esp32-c3.yaml @@ -0,0 +1,40 @@ +output: + - platform: ledc + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/monochromatic/test.esp32-idf.yaml b/tests/components/monochromatic/test.esp32-idf.yaml new file mode 100644 index 000000000000..9524efcb2d5b --- /dev/null +++ b/tests/components/monochromatic/test.esp32-idf.yaml @@ -0,0 +1,40 @@ +output: + - platform: ledc + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/monochromatic/test.esp32.yaml b/tests/components/monochromatic/test.esp32.yaml new file mode 100644 index 000000000000..9524efcb2d5b --- /dev/null +++ b/tests/components/monochromatic/test.esp32.yaml @@ -0,0 +1,40 @@ +output: + - platform: ledc + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/monochromatic/test.esp8266.yaml b/tests/components/monochromatic/test.esp8266.yaml new file mode 100644 index 000000000000..94d849581d29 --- /dev/null +++ b/tests/components/monochromatic/test.esp8266.yaml @@ -0,0 +1,40 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/monochromatic/test.rp2040.yaml b/tests/components/monochromatic/test.rp2040.yaml new file mode 100644 index 000000000000..093577e256a6 --- /dev/null +++ b/tests/components/monochromatic/test.rp2040.yaml @@ -0,0 +1,40 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 4 + +light: + - platform: monochromatic + name: Monochromatic Light + id: monochromatic_light + output: light_output_1 + gamma_correct: 2.8 + default_transition_length: 2s + effects: + - strobe: + - flicker: + - flicker: + name: My Flicker + alpha: 98% + intensity: 1.5% + - lambda: + name: My Custom Effect + update_interval: 1s + lambda: |- + static int state = 0; + state += 1; + if (state == 4) + state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% diff --git a/tests/components/mopeka_ble/common.yaml b/tests/components/mopeka_ble/common.yaml new file mode 100644 index 000000000000..a115404f1c5c --- /dev/null +++ b/tests/components/mopeka_ble/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +mopeka_ble: diff --git a/tests/components/mopeka_ble/test.esp32-c3-idf.yaml b/tests/components/mopeka_ble/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_ble/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_ble/test.esp32-c3.yaml b/tests/components/mopeka_ble/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_ble/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_ble/test.esp32-idf.yaml b/tests/components/mopeka_ble/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_ble/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_ble/test.esp32.yaml b/tests/components/mopeka_ble/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_ble/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_pro_check/common.yaml b/tests/components/mopeka_pro_check/common.yaml new file mode 100644 index 000000000000..147cbcb9dea5 --- /dev/null +++ b/tests/components/mopeka_pro_check/common.yaml @@ -0,0 +1,16 @@ +esp32_ble_tracker: + +sensor: + - platform: mopeka_pro_check + mac_address: D3:75:F2:DC:16:91 + tank_type: CUSTOM + custom_distance_full: 40cm + custom_distance_empty: 10mm + temperature: + name: Propane test temp + level: + name: Propane test level + distance: + name: Propane test distance + battery_level: + name: Propane test battery level diff --git a/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml b/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_pro_check/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_pro_check/test.esp32-c3.yaml b/tests/components/mopeka_pro_check/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_pro_check/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_pro_check/test.esp32-idf.yaml b/tests/components/mopeka_pro_check/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_pro_check/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_pro_check/test.esp32.yaml b/tests/components/mopeka_pro_check/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_pro_check/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_std_check/common.yaml b/tests/components/mopeka_std_check/common.yaml new file mode 100644 index 000000000000..383e2e2a19b2 --- /dev/null +++ b/tests/components/mopeka_std_check/common.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + # Example using 11kg 100% propane tank. + - platform: mopeka_std_check + mac_address: D3:75:F2:DC:16:91 + tank_type: Europe_11kg + temperature: + name: "Propane test temp" + level: + name: "Propane test level" + distance: + name: "Propane test distance" + battery_level: + name: "Propane test battery level" diff --git a/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml b/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_std_check/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_std_check/test.esp32-c3.yaml b/tests/components/mopeka_std_check/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_std_check/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_std_check/test.esp32-idf.yaml b/tests/components/mopeka_std_check/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_std_check/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mopeka_std_check/test.esp32.yaml b/tests/components/mopeka_std_check/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/mopeka_std_check/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/mpl3115a2/test.esp32-c3-idf.yaml b/tests/components/mpl3115a2/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9cbe08d9201f --- /dev/null +++ b/tests/components/mpl3115a2/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 5 + sda: 4 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpl3115a2/test.esp32-c3.yaml b/tests/components/mpl3115a2/test.esp32-c3.yaml new file mode 100644 index 000000000000..9cbe08d9201f --- /dev/null +++ b/tests/components/mpl3115a2/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 5 + sda: 4 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpl3115a2/test.esp32-idf.yaml b/tests/components/mpl3115a2/test.esp32-idf.yaml new file mode 100644 index 000000000000..5e9d6d190d75 --- /dev/null +++ b/tests/components/mpl3115a2/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 16 + sda: 17 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpl3115a2/test.esp32.yaml b/tests/components/mpl3115a2/test.esp32.yaml new file mode 100644 index 000000000000..5e9d6d190d75 --- /dev/null +++ b/tests/components/mpl3115a2/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 16 + sda: 17 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpl3115a2/test.esp8266.yaml b/tests/components/mpl3115a2/test.esp8266.yaml new file mode 100644 index 000000000000..9cbe08d9201f --- /dev/null +++ b/tests/components/mpl3115a2/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 5 + sda: 4 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpl3115a2/test.rp2040.yaml b/tests/components/mpl3115a2/test.rp2040.yaml new file mode 100644 index 000000000000..9cbe08d9201f --- /dev/null +++ b/tests/components/mpl3115a2/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_mpl3115a2 + scl: 5 + sda: 4 + +sensor: + - platform: mpl3115a2 + temperature: + name: MPL3115A2 Temperature + pressure: + name: MPL3115A2 Pressure + update_interval: 10s diff --git a/tests/components/mpr121/test.esp32-c3-idf.yaml b/tests/components/mpr121/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..517e092560b7 --- /dev/null +++ b/tests/components/mpr121/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 5 + sda: 4 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpr121/test.esp32-c3.yaml b/tests/components/mpr121/test.esp32-c3.yaml new file mode 100644 index 000000000000..517e092560b7 --- /dev/null +++ b/tests/components/mpr121/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 5 + sda: 4 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpr121/test.esp32-idf.yaml b/tests/components/mpr121/test.esp32-idf.yaml new file mode 100644 index 000000000000..96996fd8ee31 --- /dev/null +++ b/tests/components/mpr121/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 16 + sda: 17 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpr121/test.esp32.yaml b/tests/components/mpr121/test.esp32.yaml new file mode 100644 index 000000000000..96996fd8ee31 --- /dev/null +++ b/tests/components/mpr121/test.esp32.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 16 + sda: 17 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpr121/test.esp8266.yaml b/tests/components/mpr121/test.esp8266.yaml new file mode 100644 index 000000000000..517e092560b7 --- /dev/null +++ b/tests/components/mpr121/test.esp8266.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 5 + sda: 4 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpr121/test.rp2040.yaml b/tests/components/mpr121/test.rp2040.yaml new file mode 100644 index 000000000000..517e092560b7 --- /dev/null +++ b/tests/components/mpr121/test.rp2040.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_mpr121 + scl: 5 + sda: 4 + +mpr121: + id: mpr121_first + address: 0x5A + +binary_sensor: + - platform: mpr121 + id: touchkey0 + name: touchkey0 + channel: 0 + - platform: mpr121 + id: bin1 + name: touchkey1 + channel: 1 + - platform: mpr121 + id: bin2 + name: touchkey2 + channel: 2 + - platform: mpr121 + id: bin3 + name: touchkey3 + channel: 3 diff --git a/tests/components/mpu6050/test.esp32-c3-idf.yaml b/tests/components/mpu6050/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..39c8506d2b53 --- /dev/null +++ b/tests/components/mpu6050/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6050/test.esp32-c3.yaml b/tests/components/mpu6050/test.esp32-c3.yaml new file mode 100644 index 000000000000..39c8506d2b53 --- /dev/null +++ b/tests/components/mpu6050/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6050/test.esp32-idf.yaml b/tests/components/mpu6050/test.esp32-idf.yaml new file mode 100644 index 000000000000..45bca55deab7 --- /dev/null +++ b/tests/components/mpu6050/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 16 + sda: 17 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6050/test.esp32.yaml b/tests/components/mpu6050/test.esp32.yaml new file mode 100644 index 000000000000..45bca55deab7 --- /dev/null +++ b/tests/components/mpu6050/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 16 + sda: 17 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6050/test.esp8266.yaml b/tests/components/mpu6050/test.esp8266.yaml new file mode 100644 index 000000000000..39c8506d2b53 --- /dev/null +++ b/tests/components/mpu6050/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6050/test.rp2040.yaml b/tests/components/mpu6050/test.rp2040.yaml new file mode 100644 index 000000000000..39c8506d2b53 --- /dev/null +++ b/tests/components/mpu6050/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6050 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6050 + address: 0x68 + accel_x: + name: MPU6050 Accel X + accel_y: + name: MPU6050 Accel Y + accel_z: + name: MPU6050 Accel z + gyro_x: + name: MPU6050 Gyro X + gyro_y: + name: MPU6050 Gyro Y + gyro_z: + name: MPU6050 Gyro z + temperature: + name: MPU6050 Temperature diff --git a/tests/components/mpu6886/test.esp32-c3-idf.yaml b/tests/components/mpu6886/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fad51a80b4b6 --- /dev/null +++ b/tests/components/mpu6886/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mpu6886/test.esp32-c3.yaml b/tests/components/mpu6886/test.esp32-c3.yaml new file mode 100644 index 000000000000..fad51a80b4b6 --- /dev/null +++ b/tests/components/mpu6886/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mpu6886/test.esp32-idf.yaml b/tests/components/mpu6886/test.esp32-idf.yaml new file mode 100644 index 000000000000..84e4d567398b --- /dev/null +++ b/tests/components/mpu6886/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 16 + sda: 17 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mpu6886/test.esp32.yaml b/tests/components/mpu6886/test.esp32.yaml new file mode 100644 index 000000000000..84e4d567398b --- /dev/null +++ b/tests/components/mpu6886/test.esp32.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 16 + sda: 17 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mpu6886/test.esp8266.yaml b/tests/components/mpu6886/test.esp8266.yaml new file mode 100644 index 000000000000..fad51a80b4b6 --- /dev/null +++ b/tests/components/mpu6886/test.esp8266.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mpu6886/test.rp2040.yaml b/tests/components/mpu6886/test.rp2040.yaml new file mode 100644 index 000000000000..fad51a80b4b6 --- /dev/null +++ b/tests/components/mpu6886/test.rp2040.yaml @@ -0,0 +1,22 @@ +i2c: + - id: i2c_mpu6886 + scl: 5 + sda: 4 + +sensor: + - platform: mpu6886 + address: 0x68 + accel_x: + name: MPU6886 Accel X + accel_y: + name: MPU6886 Accel Y + accel_z: + name: MPU6886 Accel z + gyro_x: + name: MPU6886 Gyro X + gyro_y: + name: MPU6886 Gyro Y + gyro_z: + name: MPU6886 Gyro z + temperature: + name: MPU6886 Temperature diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml new file mode 100644 index 000000000000..a2a751df63f2 --- /dev/null +++ b/tests/components/mqtt/common.yaml @@ -0,0 +1,428 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + +mqtt: + broker: "192.168.178.84" + port: 1883 + username: debug + password: debug + client_id: someclient + use_abbreviations: false + discovery: true + discovery_retain: false + discovery_prefix: discovery + discovery_unique_id_generator: legacy + topic_prefix: helloworld + log_topic: + topic: helloworld/hi + level: INFO + birth_message: + will_message: + shutdown_message: + topic: topic/to/send/to + payload: hi + qos: 2 + retain: true + keepalive: 60s + reboot_timeout: 60s + on_message: + - topic: my/custom/topic + qos: 0 + then: + - lambda: >- + ESP_LOGD("main", "Got message %s", x.c_str()); + - topic: bedroom/ota_mode + then: + - logger.log: Got bedroom/ota_mode + - topic: livingroom/ota_mode + then: + - logger.log: Got livingroom/ota_mode + on_json_message: + topic: the/topic + then: + - if: + condition: + - wifi.connected: + - mqtt.connected: + then: + - logger.log: on_json_message + on_connect: + - mqtt.publish: + topic: some/topic + payload: Hello + on_disconnect: + - mqtt.publish: + topic: some/topic + payload: Good-bye + +binary_sensor: + - platform: template + id: some_binary_sensor + name: Garage Door Open + state_topic: some/topic/binary_sensor + qos: 2 + lambda: |- + if (id(template_sens).state > 30) { + // Garage Door is open. + return true; + } else { + // Garage Door is closed. + return false; + } + on_state: + - mqtt.publish: + topic: some/topic/binary_sensor + payload: Hello + qos: 2 + retain: true + +button: + - platform: template + name: "Template Button" + state_topic: some/topic/button + qos: 2 + on_press: + - mqtt.publish: + topic: some/topic/button + payload: Hello + qos: 2 + retain: true + +climate: + - platform: thermostat + name: Test Thermostat + sensor: template_sens + humidity_sensor: template_sens + action_state_topic: some/topicaction_state + current_temperature_state_topic: some/topiccurrent_temperature_state + current_humidity_state_topic: some/topiccurrent_humidity_state + fan_mode_state_topic: some/topicfan_mode_state + fan_mode_command_topic: some/topicfan_mode_command + mode_state_topic: some/topicmode_state + mode_command_topic: some/topicmode_command + preset_state_topic: some/topicpreset_state + preset_command_topic: some/topicpreset_command + swing_mode_state_topic: some/topicswing_mode_state + swing_mode_command_topic: some/topicswing_mode_command + target_temperature_state_topic: some/topictarget_temperature_state + target_temperature_command_topic: some/topictarget_temperature_command + target_temperature_high_state_topic: some/topictarget_temperature_high_state + target_temperature_high_command_topic: some/topictarget_temperature_high_command + target_temperature_low_state_topic: some/topictarget_temperature_low_state + target_temperature_low_command_topic: some/topictarget_temperature_low_command + target_humidity_state_topic: some/topictarget_humidity_state + target_humidity_command_topic: some/topictarget_humidity_command + preset: + - name: Default Preset + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + - name: Away + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C + idle_action: + - logger.log: idle_action + cool_action: + - logger.log: cool_action + supplemental_cooling_action: + - logger.log: supplemental_cooling_action + heat_action: + - logger.log: heat_action + supplemental_heating_action: + - logger.log: supplemental_heating_action + dry_action: + - logger.log: dry_action + fan_only_action: + - logger.log: fan_only_action + auto_mode: + - logger.log: auto_mode + off_mode: + - logger.log: off_mode + heat_mode: + - logger.log: heat_mode + cool_mode: + - logger.log: cool_mode + dry_mode: + - logger.log: dry_mode + fan_only_mode: + - logger.log: fan_only_mode + fan_mode_auto_action: + - logger.log: fan_mode_auto_action + fan_mode_on_action: + - logger.log: fan_mode_on_action + fan_mode_off_action: + - logger.log: fan_mode_off_action + fan_mode_low_action: + - logger.log: fan_mode_low_action + fan_mode_medium_action: + - logger.log: fan_mode_medium_action + fan_mode_high_action: + - logger.log: fan_mode_high_action + fan_mode_middle_action: + - logger.log: fan_mode_middle_action + fan_mode_focus_action: + - logger.log: fan_mode_focus_action + fan_mode_diffuse_action: + - logger.log: fan_mode_diffuse_action + fan_mode_quiet_action: + - logger.log: fan_mode_quiet_action + swing_off_action: + - logger.log: swing_off_action + swing_horizontal_action: + - logger.log: swing_horizontal_action + swing_vertical_action: + - logger.log: swing_vertical_action + swing_both_action: + - logger.log: swing_both_action + startup_delay: true + supplemental_cooling_delta: 2.0 + cool_deadband: 0.5 + cool_overrun: 0.5 + min_cooling_off_time: 300s + min_cooling_run_time: 300s + max_cooling_run_time: 600s + supplemental_heating_delta: 2.0 + heat_deadband: 0.5 + heat_overrun: 0.5 + min_heating_off_time: 300s + min_heating_run_time: 300s + max_heating_run_time: 600s + min_fanning_off_time: 30s + min_fanning_run_time: 30s + min_fan_mode_switching_time: 15s + min_idle_time: 30s + set_point_minimum_differential: 0.5 + fan_only_action_uses_fan_mode_timer: true + fan_only_cooling: true + fan_with_cooling: true + fan_with_heating: true + +cover: + - platform: template + name: Template Cover + state_topic: some/topic/cover + qos: 2 + lambda: |- + if (id(some_binary_sensor).state) { + return COVER_OPEN; + } else { + return COVER_CLOSED; + } + open_action: + - logger.log: open_action + close_action: + - logger.log: close_action + stop_action: + - logger.log: stop_action + optimistic: true + +datetime: + - platform: template + name: Date + id: test_date + type: date + state_topic: some/topic/date + qos: 2 + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "Date: %04d-%02d-%02d" + args: + - x.year + - x.month + - x.day_of_month + - platform: template + name: Time + id: test_time + type: time + state_topic: some/topic/time + qos: 2 + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "Time: %02d:%02d:%02d" + args: + - x.hour + - x.minute + - x.second + - platform: template + name: DateTime + id: test_datetime + type: datetime + state_topic: some/topic/datetime + qos: 2 + set_action: + - logger.log: set_value + on_value: + - logger.log: + format: "DateTime: %04d-%02d-%02d %02d:%02d:%02d" + args: + - x.year + - x.month + - x.day_of_month + - x.hour + - x.minute + - x.second + +event: + - platform: template + name: Template Event + state_topic: some/topic/event + qos: 2 + event_types: + - "custom_event_1" + - "custom_event_2" + +fan: + - platform: template + name: Template Fan + state_topic: some/topic/fan + qos: 2 + on_state: + - logger.log: on_state + on_speed_set: + - logger.log: on_speed_set + +light: + - platform: binary + name: Desk Lamp + output: light_output + state_topic: some/topic/light + qos: 2 + +output: + - id: light_output + platform: gpio + pin: 0 + +lock: + - platform: template + name: "Template Lock" + state_topic: some/topic/lock + qos: 2 + lambda: |- + if (id(some_binary_sensor).state) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + lock_action: + - logger.log: lock_action + unlock_action: + - logger.log: unlock_action + open_action: + - logger.log: open_action + +number: + - platform: template + name: "Template number" + state_topic: some/topic/number + qos: 2 + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + +select: + - platform: template + name: "Template select" + state_topic: some/topic/select + qos: 2 + optimistic: true + options: + - one + - two + - three + initial_option: two + +sensor: + - platform: template + name: Template Sensor + id: template_sens + lambda: |- + if (id(some_binary_sensor).state) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + on_value: + - mqtt.publish: + topic: some/topic/sensor + payload: Hello + qos: 2 + retain: true + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(template_sens).state; + root["greeting"] = "Hello World"; + +switch: + - platform: template + name: Template Switch + state_topic: some/topic/switch + qos: 2 + lambda: |- + if (id(some_binary_sensor).state) { + return true; + } else { + return false; + } + turn_on_action: + - logger.log: turn_on_action + turn_off_action: + - logger.log: turn_off_action + +text_sensor: + - platform: template + name: Template Text Sensor + id: tts_text + state_topic: some/topic/text_sensor + qos: 2 + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: some/topic/text_sensor + qos: 2 + on_value: + - text_sensor.template.publish: + id: tts_text + state: Hello World + - text_sensor.template.publish: + id: tts_text + state: |- + return "Hello World2"; + +text: + - platform: template + name: Template Text + optimistic: true + min_length: 0 + max_length: 100 + mode: text + state_topic: some/topic/text + qos: 2 + +valve: + - platform: template + name: Template Valve + state_topic: some/topic/valve + qos: 2 + optimistic: true + lambda: |- + if (id(some_binary_sensor).state) { + return VALVE_OPEN; + } else { + return VALVE_CLOSED; + } diff --git a/tests/components/mqtt/test.bk72xx.yaml b/tests/components/mqtt/test.bk72xx.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.bk72xx.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt/test.esp32-c3-idf.yaml b/tests/components/mqtt/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt/test.esp32-c3.yaml b/tests/components/mqtt/test.esp32-c3.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.esp32-c3.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt/test.esp32-idf.yaml b/tests/components/mqtt/test.esp32-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt/test.esp32.yaml b/tests/components/mqtt/test.esp32.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.esp32.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt/test.esp8266.yaml b/tests/components/mqtt/test.esp8266.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/mqtt/test.esp8266.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml b/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..070672f15c90 --- /dev/null +++ b/tests/components/mqtt_subscribe/test.esp32-c3-idf.yaml @@ -0,0 +1,37 @@ +wifi: + ssid: MySSID + password: password1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + idf_send_async: false + log_topic: + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - logger.log: Mqtt Test + +sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(the_sensor).state; + root["greeting"] = "Hello World"; + +text_sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: "the/topic" + qos: 2 + on_value: + - logger.log: "Text sensor got value" diff --git a/tests/components/mqtt_subscribe/test.esp32-c3.yaml b/tests/components/mqtt_subscribe/test.esp32-c3.yaml new file mode 100644 index 000000000000..13ed311b1766 --- /dev/null +++ b/tests/components/mqtt_subscribe/test.esp32-c3.yaml @@ -0,0 +1,36 @@ +wifi: + ssid: MySSID + password: password1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + log_topic: + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - logger.log: Mqtt Test + +sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(the_sensor).state; + root["greeting"] = "Hello World"; + +text_sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: "the/topic" + qos: 2 + on_value: + - logger.log: "Text sensor got value" diff --git a/tests/components/mqtt_subscribe/test.esp32-idf.yaml b/tests/components/mqtt_subscribe/test.esp32-idf.yaml new file mode 100644 index 000000000000..070672f15c90 --- /dev/null +++ b/tests/components/mqtt_subscribe/test.esp32-idf.yaml @@ -0,0 +1,37 @@ +wifi: + ssid: MySSID + password: password1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + idf_send_async: false + log_topic: + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - logger.log: Mqtt Test + +sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(the_sensor).state; + root["greeting"] = "Hello World"; + +text_sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: "the/topic" + qos: 2 + on_value: + - logger.log: "Text sensor got value" diff --git a/tests/components/mqtt_subscribe/test.esp32.yaml b/tests/components/mqtt_subscribe/test.esp32.yaml new file mode 100644 index 000000000000..13ed311b1766 --- /dev/null +++ b/tests/components/mqtt_subscribe/test.esp32.yaml @@ -0,0 +1,36 @@ +wifi: + ssid: MySSID + password: password1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + log_topic: + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - logger.log: Mqtt Test + +sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(the_sensor).state; + root["greeting"] = "Hello World"; + +text_sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: "the/topic" + qos: 2 + on_value: + - logger.log: "Text sensor got value" diff --git a/tests/components/mqtt_subscribe/test.esp8266.yaml b/tests/components/mqtt_subscribe/test.esp8266.yaml new file mode 100644 index 000000000000..13ed311b1766 --- /dev/null +++ b/tests/components/mqtt_subscribe/test.esp8266.yaml @@ -0,0 +1,36 @@ +wifi: + ssid: MySSID + password: password1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + log_topic: + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + - logger.log: Mqtt Test + +sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Sensor + topic: mqtt/topic + id: the_sensor + qos: 2 + on_value: + - mqtt.publish_json: + topic: the/topic + payload: |- + root["key"] = id(the_sensor).state; + root["greeting"] = "Hello World"; + +text_sensor: + - platform: mqtt_subscribe + name: MQTT Subscribe Text + topic: "the/topic" + qos: 2 + on_value: + - logger.log: "Text sensor got value" diff --git a/tests/components/ms5611/test.esp32-c3-idf.yaml b/tests/components/ms5611/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8f18501eefa0 --- /dev/null +++ b/tests/components/ms5611/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 5 + sda: 4 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/ms5611/test.esp32-c3.yaml b/tests/components/ms5611/test.esp32-c3.yaml new file mode 100644 index 000000000000..8f18501eefa0 --- /dev/null +++ b/tests/components/ms5611/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 5 + sda: 4 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/ms5611/test.esp32-idf.yaml b/tests/components/ms5611/test.esp32-idf.yaml new file mode 100644 index 000000000000..b090eeaa93bb --- /dev/null +++ b/tests/components/ms5611/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 16 + sda: 17 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/ms5611/test.esp32.yaml b/tests/components/ms5611/test.esp32.yaml new file mode 100644 index 000000000000..b090eeaa93bb --- /dev/null +++ b/tests/components/ms5611/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 16 + sda: 17 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/ms5611/test.esp8266.yaml b/tests/components/ms5611/test.esp8266.yaml new file mode 100644 index 000000000000..8f18501eefa0 --- /dev/null +++ b/tests/components/ms5611/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 5 + sda: 4 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/ms5611/test.rp2040.yaml b/tests/components/ms5611/test.rp2040.yaml new file mode 100644 index 000000000000..8f18501eefa0 --- /dev/null +++ b/tests/components/ms5611/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_ms5611 + scl: 5 + sda: 4 + +sensor: + - platform: ms5611 + temperature: + name: Outside Temperature + pressure: + name: Outside Pressure + address: 0x77 + update_interval: 15s diff --git a/tests/components/my9231/common.yaml b/tests/components/my9231/common.yaml new file mode 100644 index 000000000000..3f2e81ef9822 --- /dev/null +++ b/tests/components/my9231/common.yaml @@ -0,0 +1,26 @@ +my9231: + clock_pin: 5 + data_pin: 4 + num_channels: 6 + num_chips: 2 + bit_depth: 16 + +output: + - platform: my9231 + id: my_0 + channel: 0 + - platform: my9231 + id: my_1 + channel: 1 + - platform: my9231 + id: my_2 + channel: 2 + - platform: my9231 + id: my_3 + channel: 3 + - platform: my9231 + id: my_4 + channel: 4 + - platform: my9231 + id: my_5 + channel: 5 diff --git a/tests/components/my9231/test.esp32-c3-idf.yaml b/tests/components/my9231/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/my9231/test.esp32-c3.yaml b/tests/components/my9231/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/my9231/test.esp32-idf.yaml b/tests/components/my9231/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/my9231/test.esp32.yaml b/tests/components/my9231/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/my9231/test.esp8266.yaml b/tests/components/my9231/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/my9231/test.rp2040.yaml b/tests/components/my9231/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/my9231/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/neopixelbus/test.esp32-c3.yaml b/tests/components/neopixelbus/test.esp32-c3.yaml new file mode 100644 index 000000000000..f2b53ab1e90d --- /dev/null +++ b/tests/components/neopixelbus/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: + type: esp32_rmt + channel: 0 + num_leds: 5 + pin: 4 diff --git a/tests/components/neopixelbus/test.esp32.yaml b/tests/components/neopixelbus/test.esp32.yaml new file mode 100644 index 000000000000..fd468586e0c6 --- /dev/null +++ b/tests/components/neopixelbus/test.esp32.yaml @@ -0,0 +1,17 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: ESP32_I2S_0 + num_leds: 5 + pin: 4 diff --git a/tests/components/neopixelbus/test.esp8266.yaml b/tests/components/neopixelbus/test.esp8266.yaml new file mode 100644 index 000000000000..2c1f16a38c38 --- /dev/null +++ b/tests/components/neopixelbus/test.esp8266.yaml @@ -0,0 +1,17 @@ +light: + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: esp8266_uart + num_leds: 5 + pin: 2 diff --git a/tests/components/network/common.yaml b/tests/components/network/common.yaml new file mode 100644 index 000000000000..147afd1e8179 --- /dev/null +++ b/tests/components/network/common.yaml @@ -0,0 +1,6 @@ +wifi: + ssid: MySSID + password: password1 + +network: + enable_ipv6: true diff --git a/tests/components/network/test.esp32-c3-idf.yaml b/tests/components/network/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/network/test.esp32-c3.yaml b/tests/components/network/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/network/test.esp32-idf.yaml b/tests/components/network/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/network/test.esp32.yaml b/tests/components/network/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/network/test.esp8266.yaml b/tests/components/network/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/network/test.rp2040.yaml b/tests/components/network/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/network/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..5881d6e1659e --- /dev/null +++ b/tests/components/nextion/test.esp32-c3-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-c3.yaml b/tests/components/nextion/test.esp32-c3.yaml new file mode 100644 index 000000000000..5881d6e1659e --- /dev/null +++ b/tests/components/nextion/test.esp32-c3.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-idf.yaml b/tests/components/nextion/test.esp32-idf.yaml new file mode 100644 index 000000000000..27568ebc2a4f --- /dev/null +++ b/tests/components/nextion/test.esp32-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32.yaml b/tests/components/nextion/test.esp32.yaml new file mode 100644 index 000000000000..27568ebc2a4f --- /dev/null +++ b/tests/components/nextion/test.esp32.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp8266.yaml b/tests/components/nextion/test.esp8266.yaml new file mode 100644 index 000000000000..5881d6e1659e --- /dev/null +++ b/tests/components/nextion/test.esp8266.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + tft_url: http://esphome.io/default35.tft + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.rp2040.yaml b/tests/components/nextion/test.rp2040.yaml new file mode 100644 index 000000000000..a1c5848ce6ef --- /dev/null +++ b/tests/components/nextion/test.rp2040.yaml @@ -0,0 +1,55 @@ +uart: + - id: uart_nextion + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/noblex/common.yaml b/tests/components/noblex/common.yaml new file mode 100644 index 000000000000..f5e471a9a7bb --- /dev/null +++ b/tests/components/noblex/common.yaml @@ -0,0 +1,20 @@ +remote_receiver: + id: rcvr + pin: 4 + dump: all + +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: noblex_ac_sensor + lambda: "return 21;" + +climate: + - platform: noblex + name: AC Living + id: noblex_ac + sensor: noblex_ac_sensor + receiver_id: rcvr diff --git a/tests/components/noblex/test.esp32-c3-idf.yaml b/tests/components/noblex/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/noblex/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/noblex/test.esp32-c3.yaml b/tests/components/noblex/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/noblex/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/noblex/test.esp32-idf.yaml b/tests/components/noblex/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/noblex/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/noblex/test.esp32.yaml b/tests/components/noblex/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/noblex/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/noblex/test.esp8266.yaml b/tests/components/noblex/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/noblex/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ntc/test.esp32-c3.yaml b/tests/components/ntc/test.esp32-c3.yaml new file mode 100644 index 000000000000..c0edb83d9d32 --- /dev/null +++ b/tests/components/ntc/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-idf.yaml b/tests/components/ntc/test.esp32-idf.yaml new file mode 100644 index 000000000000..3e0e901b6e08 --- /dev/null +++ b/tests/components/ntc/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-s2.yaml b/tests/components/ntc/test.esp32-s2.yaml new file mode 100644 index 000000000000..c0edb83d9d32 --- /dev/null +++ b/tests/components/ntc/test.esp32-s2.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32-s3.yaml b/tests/components/ntc/test.esp32-s3.yaml new file mode 100644 index 000000000000..c0edb83d9d32 --- /dev/null +++ b/tests/components/ntc/test.esp32-s3.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp32.yaml b/tests/components/ntc/test.esp32.yaml new file mode 100644 index 000000000000..3e0e901b6e08 --- /dev/null +++ b/tests/components/ntc/test.esp32.yaml @@ -0,0 +1,26 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.esp8266.yaml b/tests/components/ntc/test.esp8266.yaml new file mode 100644 index 000000000000..370d16d3f736 --- /dev/null +++ b/tests/components/ntc/test.esp8266.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: adc + id: my_sensor + pin: A0 + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ntc/test.rp2040.yaml b/tests/components/ntc/test.rp2040.yaml new file mode 100644 index 000000000000..9c7ba7b5390a --- /dev/null +++ b/tests/components/ntc/test.rp2040.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: adc + id: my_sensor + pin: 26 + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist + - platform: ntc + sensor: resist + name: NTC Sensor + calibration: + b_constant: 3950 + reference_resistance: 10k + reference_temperature: 25°C + - platform: ntc + sensor: resist + name: NTC Sensor2 + calibration: + - 10.0kOhm -> 25°C + - 27.219kOhm -> 0°C + - 14.674kOhm -> 15°C diff --git a/tests/components/ota/common.yaml b/tests/components/ota/common.yaml new file mode 100644 index 000000000000..367454995f14 --- /dev/null +++ b/tests/components/ota/common.yaml @@ -0,0 +1,30 @@ +wifi: + ssid: MySSID + password: password1 + +ota: + safe_mode: true + password: "superlongpasswordthatnoonewillknow" + port: 3286 + reboot_timeout: 2min + num_attempts: 5 + on_begin: + then: + - logger.log: "OTA start" + on_progress: + then: + - logger.log: + format: "OTA progress %0.1f%%" + args: ["x"] + on_end: + then: + - logger.log: "OTA end" + on_error: + then: + - logger.log: + format: "OTA update error %d" + args: ["x"] + on_state_change: + then: + lambda: >- + ESP_LOGD("ota", "State %d", state); diff --git a/tests/components/ota/test.esp32-c3-idf.yaml b/tests/components/ota/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ota/test.esp32-c3.yaml b/tests/components/ota/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ota/test.esp32-idf.yaml b/tests/components/ota/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ota/test.esp32.yaml b/tests/components/ota/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ota/test.esp8266.yaml b/tests/components/ota/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ota/test.rp2040.yaml b/tests/components/ota/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ota/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/output/test.esp32-c3-idf.yaml b/tests/components/output/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c56d85c29668 --- /dev/null +++ b/tests/components/output/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: ledc + id: light_output_1 + pin: 1 diff --git a/tests/components/output/test.esp32-c3.yaml b/tests/components/output/test.esp32-c3.yaml new file mode 100644 index 000000000000..c56d85c29668 --- /dev/null +++ b/tests/components/output/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: ledc + id: light_output_1 + pin: 1 diff --git a/tests/components/output/test.esp32-idf.yaml b/tests/components/output/test.esp32-idf.yaml new file mode 100644 index 000000000000..480f9dfe1f54 --- /dev/null +++ b/tests/components/output/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: ledc + id: light_output_1 + pin: 12 diff --git a/tests/components/output/test.esp32.yaml b/tests/components/output/test.esp32.yaml new file mode 100644 index 000000000000..480f9dfe1f54 --- /dev/null +++ b/tests/components/output/test.esp32.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: ledc + id: light_output_1 + pin: 12 diff --git a/tests/components/output/test.esp8266.yaml b/tests/components/output/test.esp8266.yaml new file mode 100644 index 000000000000..d9cb353636a1 --- /dev/null +++ b/tests/components/output/test.esp8266.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 diff --git a/tests/components/output/test.rp2040.yaml b/tests/components/output/test.rp2040.yaml new file mode 100644 index 000000000000..399259fdd9fc --- /dev/null +++ b/tests/components/output/test.rp2040.yaml @@ -0,0 +1,13 @@ +esphome: + on_boot: + then: + - output.turn_off: light_output_1 + - output.turn_on: light_output_1 + - output.set_level: + id: light_output_1 + level: 50% + +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 diff --git a/tests/components/partition/test.esp32-c3-idf.yaml b/tests/components/partition/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..77cfc5ad4461 --- /dev/null +++ b/tests/components/partition/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +light: + - platform: esp32_rmt_led_strip + id: part_leds + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + - platform: partition + name: Partition Light + segments: + - id: part_leds + from: 0 + to: 0 + - id: part_leds + from: 1 + to: 10 + - id: part_leds + from: 20 + to: 25 + - single_light_id: part_leds diff --git a/tests/components/partition/test.esp32-c3.yaml b/tests/components/partition/test.esp32-c3.yaml new file mode 100644 index 000000000000..77cfc5ad4461 --- /dev/null +++ b/tests/components/partition/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +light: + - platform: esp32_rmt_led_strip + id: part_leds + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + - platform: partition + name: Partition Light + segments: + - id: part_leds + from: 0 + to: 0 + - id: part_leds + from: 1 + to: 10 + - id: part_leds + from: 20 + to: 25 + - single_light_id: part_leds diff --git a/tests/components/partition/test.esp32-idf.yaml b/tests/components/partition/test.esp32-idf.yaml new file mode 100644 index 000000000000..77cfc5ad4461 --- /dev/null +++ b/tests/components/partition/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +light: + - platform: esp32_rmt_led_strip + id: part_leds + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + - platform: partition + name: Partition Light + segments: + - id: part_leds + from: 0 + to: 0 + - id: part_leds + from: 1 + to: 10 + - id: part_leds + from: 20 + to: 25 + - single_light_id: part_leds diff --git a/tests/components/partition/test.esp32.yaml b/tests/components/partition/test.esp32.yaml new file mode 100644 index 000000000000..c8eae67d405d --- /dev/null +++ b/tests/components/partition/test.esp32.yaml @@ -0,0 +1,22 @@ +light: + - platform: fastled_clockless + id: part_leds + chipset: WS2812B + pin: 2 + num_leds: 256 + rgb_order: GRB + default_transition_length: 0s + color_correct: [50%, 50%, 50%] + - platform: partition + name: Partition Light + segments: + - id: part_leds + from: 0 + to: 0 + - id: part_leds + from: 1 + to: 10 + - id: part_leds + from: 20 + to: 25 + - single_light_id: part_leds diff --git a/tests/components/pca6416a/test.esp32-c3-idf.yaml b/tests/components/pca6416a/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fe940c44cca8 --- /dev/null +++ b/tests/components/pca6416a/test.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 5 + sda: 4 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca6416a/test.esp32-c3.yaml b/tests/components/pca6416a/test.esp32-c3.yaml new file mode 100644 index 000000000000..fe940c44cca8 --- /dev/null +++ b/tests/components/pca6416a/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 5 + sda: 4 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca6416a/test.esp32-idf.yaml b/tests/components/pca6416a/test.esp32-idf.yaml new file mode 100644 index 000000000000..669e9416e445 --- /dev/null +++ b/tests/components/pca6416a/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 16 + sda: 17 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca6416a/test.esp32.yaml b/tests/components/pca6416a/test.esp32.yaml new file mode 100644 index 000000000000..669e9416e445 --- /dev/null +++ b/tests/components/pca6416a/test.esp32.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 16 + sda: 17 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca6416a/test.esp8266.yaml b/tests/components/pca6416a/test.esp8266.yaml new file mode 100644 index 000000000000..fe940c44cca8 --- /dev/null +++ b/tests/components/pca6416a/test.esp8266.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 5 + sda: 4 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca6416a/test.rp2040.yaml b/tests/components/pca6416a/test.rp2040.yaml new file mode 100644 index 000000000000..fe940c44cca8 --- /dev/null +++ b/tests/components/pca6416a/test.rp2040.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_pca6416a + scl: 5 + sda: 4 + +pca6416a: + - id: pca6416a_hub + address: 0x21 + +binary_sensor: + - platform: gpio + name: PCA6416A Binary Sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true diff --git a/tests/components/pca9554/test.esp32-c3-idf.yaml b/tests/components/pca9554/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0ff453e64f56 --- /dev/null +++ b/tests/components/pca9554/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 5 + sda: 4 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9554/test.esp32-c3.yaml b/tests/components/pca9554/test.esp32-c3.yaml new file mode 100644 index 000000000000..0ff453e64f56 --- /dev/null +++ b/tests/components/pca9554/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 5 + sda: 4 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9554/test.esp32-idf.yaml b/tests/components/pca9554/test.esp32-idf.yaml new file mode 100644 index 000000000000..8fe9686303aa --- /dev/null +++ b/tests/components/pca9554/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 16 + sda: 17 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9554/test.esp32.yaml b/tests/components/pca9554/test.esp32.yaml new file mode 100644 index 000000000000..8fe9686303aa --- /dev/null +++ b/tests/components/pca9554/test.esp32.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 16 + sda: 17 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9554/test.esp8266.yaml b/tests/components/pca9554/test.esp8266.yaml new file mode 100644 index 000000000000..0ff453e64f56 --- /dev/null +++ b/tests/components/pca9554/test.esp8266.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 5 + sda: 4 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9554/test.rp2040.yaml b/tests/components/pca9554/test.rp2040.yaml new file mode 100644 index 000000000000..0ff453e64f56 --- /dev/null +++ b/tests/components/pca9554/test.rp2040.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 5 + sda: 4 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pca9685/test.esp32-c3-idf.yaml b/tests/components/pca9685/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e532f323bef8 --- /dev/null +++ b/tests/components/pca9685/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_pca9685 + scl: 5 + sda: 4 + +pca9685: + frequency: 500 + address: 0x0 + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + - platform: pca9685 + id: pca_3 + channel: 3 + - platform: pca9685 + id: pca_4 + channel: 4 + - platform: pca9685 + id: pca_5 + channel: 5 + - platform: pca9685 + id: pca_6 + channel: 6 + - platform: pca9685 + id: pca_7 + channel: 7 diff --git a/tests/components/pca9685/test.esp32-c3.yaml b/tests/components/pca9685/test.esp32-c3.yaml new file mode 100644 index 000000000000..e532f323bef8 --- /dev/null +++ b/tests/components/pca9685/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_pca9685 + scl: 5 + sda: 4 + +pca9685: + frequency: 500 + address: 0x0 + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + - platform: pca9685 + id: pca_3 + channel: 3 + - platform: pca9685 + id: pca_4 + channel: 4 + - platform: pca9685 + id: pca_5 + channel: 5 + - platform: pca9685 + id: pca_6 + channel: 6 + - platform: pca9685 + id: pca_7 + channel: 7 diff --git a/tests/components/pca9685/test.esp32-idf.yaml b/tests/components/pca9685/test.esp32-idf.yaml new file mode 100644 index 000000000000..d02a16bcd148 --- /dev/null +++ b/tests/components/pca9685/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_pca9685 + scl: 16 + sda: 17 + +pca9685: + frequency: 500 + address: 0x0 + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + - platform: pca9685 + id: pca_3 + channel: 3 + - platform: pca9685 + id: pca_4 + channel: 4 + - platform: pca9685 + id: pca_5 + channel: 5 + - platform: pca9685 + id: pca_6 + channel: 6 + - platform: pca9685 + id: pca_7 + channel: 7 diff --git a/tests/components/pca9685/test.esp32.yaml b/tests/components/pca9685/test.esp32.yaml new file mode 100644 index 000000000000..d02a16bcd148 --- /dev/null +++ b/tests/components/pca9685/test.esp32.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_pca9685 + scl: 16 + sda: 17 + +pca9685: + frequency: 500 + address: 0x0 + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + - platform: pca9685 + id: pca_3 + channel: 3 + - platform: pca9685 + id: pca_4 + channel: 4 + - platform: pca9685 + id: pca_5 + channel: 5 + - platform: pca9685 + id: pca_6 + channel: 6 + - platform: pca9685 + id: pca_7 + channel: 7 diff --git a/tests/components/pca9685/test.esp8266.yaml b/tests/components/pca9685/test.esp8266.yaml new file mode 100644 index 000000000000..e532f323bef8 --- /dev/null +++ b/tests/components/pca9685/test.esp8266.yaml @@ -0,0 +1,34 @@ +i2c: + - id: i2c_pca9685 + scl: 5 + sda: 4 + +pca9685: + frequency: 500 + address: 0x0 + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + - platform: pca9685 + id: pca_3 + channel: 3 + - platform: pca9685 + id: pca_4 + channel: 4 + - platform: pca9685 + id: pca_5 + channel: 5 + - platform: pca9685 + id: pca_6 + channel: 6 + - platform: pca9685 + id: pca_7 + channel: 7 diff --git a/tests/components/pca9685/test.rp2040.yaml b/tests/components/pca9685/test.rp2040.yaml new file mode 100644 index 000000000000..0ff453e64f56 --- /dev/null +++ b/tests/components/pca9685/test.rp2040.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_pca9554 + scl: 5 + sda: 4 + +pca9554: + - id: pca9554_hub + pin_count: 8 + address: 0x3F + +binary_sensor: + - platform: gpio + id: pca9554_input + name: PCA9554 Binary Sensor + pin: + pca9554: pca9554_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: pca9554_output + pin: + pca9554: pca9554_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcd8544/test.esp32-c3-idf.yaml b/tests/components/pcd8544/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..57771d2d7309 --- /dev/null +++ b/tests/components/pcd8544/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: pcd8544 + cs_pin: 2 + dc_pin: 3 + reset_pin: 1 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcd8544/test.esp32-c3.yaml b/tests/components/pcd8544/test.esp32-c3.yaml new file mode 100644 index 000000000000..57771d2d7309 --- /dev/null +++ b/tests/components/pcd8544/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: pcd8544 + cs_pin: 2 + dc_pin: 3 + reset_pin: 1 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcd8544/test.esp32-idf.yaml b/tests/components/pcd8544/test.esp32-idf.yaml new file mode 100644 index 000000000000..20c05c407f40 --- /dev/null +++ b/tests/components/pcd8544/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: pcd8544 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcd8544/test.esp32.yaml b/tests/components/pcd8544/test.esp32.yaml new file mode 100644 index 000000000000..20c05c407f40 --- /dev/null +++ b/tests/components/pcd8544/test.esp32.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: pcd8544 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcd8544/test.esp8266.yaml b/tests/components/pcd8544/test.esp8266.yaml new file mode 100644 index 000000000000..6e6022c6d219 --- /dev/null +++ b/tests/components/pcd8544/test.esp8266.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: pcd8544 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcd8544/test.rp2040.yaml b/tests/components/pcd8544/test.rp2040.yaml new file mode 100644 index 000000000000..7181f99fb1b6 --- /dev/null +++ b/tests/components/pcd8544/test.rp2040.yaml @@ -0,0 +1,14 @@ +spi: + - id: spi_pcd8544 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: pcd8544 + cs_pin: 6 + dc_pin: 5 + reset_pin: 7 + contrast: 60 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/pcf85063/test.esp32-c3-idf.yaml b/tests/components/pcf85063/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9e1a3da81e8e --- /dev/null +++ b/tests/components/pcf85063/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 5 + sda: 4 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf85063/test.esp32-c3.yaml b/tests/components/pcf85063/test.esp32-c3.yaml new file mode 100644 index 000000000000..9e1a3da81e8e --- /dev/null +++ b/tests/components/pcf85063/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 5 + sda: 4 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf85063/test.esp32-idf.yaml b/tests/components/pcf85063/test.esp32-idf.yaml new file mode 100644 index 000000000000..9cce41510319 --- /dev/null +++ b/tests/components/pcf85063/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 16 + sda: 17 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf85063/test.esp32.yaml b/tests/components/pcf85063/test.esp32.yaml new file mode 100644 index 000000000000..9cce41510319 --- /dev/null +++ b/tests/components/pcf85063/test.esp32.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 16 + sda: 17 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf85063/test.esp8266.yaml b/tests/components/pcf85063/test.esp8266.yaml new file mode 100644 index 000000000000..9e1a3da81e8e --- /dev/null +++ b/tests/components/pcf85063/test.esp8266.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 5 + sda: 4 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf85063/test.rp2040.yaml b/tests/components/pcf85063/test.rp2040.yaml new file mode 100644 index 000000000000..9e1a3da81e8e --- /dev/null +++ b/tests/components/pcf85063/test.rp2040.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf85063.read_time + - pcf85063.write_time + +i2c: + - id: i2c_pcf85063 + scl: 5 + sda: 4 + +time: + - platform: pcf85063 diff --git a/tests/components/pcf8563/test.esp32-c3-idf.yaml b/tests/components/pcf8563/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f91a465e0f47 --- /dev/null +++ b/tests/components/pcf8563/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8563/test.esp32-c3.yaml b/tests/components/pcf8563/test.esp32-c3.yaml new file mode 100644 index 000000000000..f91a465e0f47 --- /dev/null +++ b/tests/components/pcf8563/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8563/test.esp32-idf.yaml b/tests/components/pcf8563/test.esp32-idf.yaml new file mode 100644 index 000000000000..e95b487b1944 --- /dev/null +++ b/tests/components/pcf8563/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 16 + sda: 17 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8563/test.esp32.yaml b/tests/components/pcf8563/test.esp32.yaml new file mode 100644 index 000000000000..e95b487b1944 --- /dev/null +++ b/tests/components/pcf8563/test.esp32.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 16 + sda: 17 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8563/test.esp8266.yaml b/tests/components/pcf8563/test.esp8266.yaml new file mode 100644 index 000000000000..f91a465e0f47 --- /dev/null +++ b/tests/components/pcf8563/test.esp8266.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8563/test.rp2040.yaml b/tests/components/pcf8563/test.rp2040.yaml new file mode 100644 index 000000000000..f91a465e0f47 --- /dev/null +++ b/tests/components/pcf8563/test.rp2040.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + - pcf8563.read_time + - pcf8563.write_time + +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +time: + - platform: pcf8563 diff --git a/tests/components/pcf8574/test.esp32-c3-idf.yaml b/tests/components/pcf8574/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..551e4258920d --- /dev/null +++ b/tests/components/pcf8574/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcf8574/test.esp32-c3.yaml b/tests/components/pcf8574/test.esp32-c3.yaml new file mode 100644 index 000000000000..551e4258920d --- /dev/null +++ b/tests/components/pcf8574/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcf8574/test.esp32-idf.yaml b/tests/components/pcf8574/test.esp32-idf.yaml new file mode 100644 index 000000000000..aeed55f4fe6a --- /dev/null +++ b/tests/components/pcf8574/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 16 + sda: 17 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcf8574/test.esp32.yaml b/tests/components/pcf8574/test.esp32.yaml new file mode 100644 index 000000000000..aeed55f4fe6a --- /dev/null +++ b/tests/components/pcf8574/test.esp32.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 16 + sda: 17 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcf8574/test.esp8266.yaml b/tests/components/pcf8574/test.esp8266.yaml new file mode 100644 index 000000000000..551e4258920d --- /dev/null +++ b/tests/components/pcf8574/test.esp8266.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pcf8574/test.rp2040.yaml b/tests/components/pcf8574/test.rp2040.yaml new file mode 100644 index 000000000000..551e4258920d --- /dev/null +++ b/tests/components/pcf8574/test.rp2040.yaml @@ -0,0 +1,28 @@ +i2c: + - id: i2c_pcf8563 + scl: 5 + sda: 4 + +pcf8574: + - id: pcf8574_hub + address: 0x21 + pcf8575: false + +binary_sensor: + - platform: gpio + id: pcf8574_binary_sensor + name: PCF Binary Sensor + pin: + pcf8574: pcf8574_hub + number: 1 + mode: INPUT + inverted: true + +output: + - platform: gpio + id: pcf8574_output + pin: + pcf8574: pcf8574_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/pid/common.yaml b/tests/components/pid/common.yaml new file mode 100644 index 000000000000..5f7762872f4d --- /dev/null +++ b/tests/components/pid/common.yaml @@ -0,0 +1,56 @@ +esphome: + on_boot: + then: + - climate.pid.autotune: pid_climate + - climate.pid.autotune: + id: pid_climate + noiseband: 0.25 + positive_output: 25% + negative_output: -25% + - climate.pid.set_control_parameters: + id: pid_climate + kp: 0.0 + ki: 0.0 + kd: 0.0 + - climate.pid.reset_integral_term: pid_climate + +output: + - platform: slow_pwm + pin: 4 + id: pid_slow_pwm + period: 15s + restart_cycle_on_state_change: false + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +climate: + - platform: pid + id: pid_climate + name: PID Climate Controller + sensor: template_sensor1 + humidity_sensor: template_sensor1 + default_target_temperature: 21°C + heat_output: pid_slow_pwm + control_parameters: + kp: 0.0 + ki: 0.0 + kd: 0.0 + max_integral: 0.0 + output_averaging_samples: 1 + derivative_averaging_samples: 1 + deadband_parameters: + threshold_high: 0.4 + threshold_low: -2.0 + kp_multiplier: 0.0 + ki_multiplier: 0.0 + kd_multiplier: 0.0 + deadband_output_averaging_samples: 1 diff --git a/tests/components/pid/test.esp32-c3-idf.yaml b/tests/components/pid/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pid/test.esp32-c3.yaml b/tests/components/pid/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pid/test.esp32-idf.yaml b/tests/components/pid/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pid/test.esp32.yaml b/tests/components/pid/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pid/test.esp8266.yaml b/tests/components/pid/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pid/test.rp2040.yaml b/tests/components/pid/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pid/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pipsolar/test.esp32-c3-idf.yaml b/tests/components/pipsolar/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..12e92663430c --- /dev/null +++ b/tests/components/pipsolar/test.esp32-c3-idf.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pipsolar/test.esp32-c3.yaml b/tests/components/pipsolar/test.esp32-c3.yaml new file mode 100644 index 000000000000..12e92663430c --- /dev/null +++ b/tests/components/pipsolar/test.esp32-c3.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pipsolar/test.esp32-idf.yaml b/tests/components/pipsolar/test.esp32-idf.yaml new file mode 100644 index 000000000000..fcd45757395a --- /dev/null +++ b/tests/components/pipsolar/test.esp32-idf.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pipsolar/test.esp32.yaml b/tests/components/pipsolar/test.esp32.yaml new file mode 100644 index 000000000000..fcd45757395a --- /dev/null +++ b/tests/components/pipsolar/test.esp32.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pipsolar/test.esp8266.yaml b/tests/components/pipsolar/test.esp8266.yaml new file mode 100644 index 000000000000..12e92663430c --- /dev/null +++ b/tests/components/pipsolar/test.esp8266.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pipsolar/test.rp2040.yaml b/tests/components/pipsolar/test.rp2040.yaml new file mode 100644 index 000000000000..12e92663430c --- /dev/null +++ b/tests/components/pipsolar/test.rp2040.yaml @@ -0,0 +1,247 @@ +esphome: + on_boot: + then: + - output.pipsolar.set_level: + id: inverter0_battery_recharge_voltage_out + value: 48.0 + +uart: + - id: uart_pipsolar + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pipsolar: + id: inverter0 + +binary_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out + +sensor: + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power + +switch: + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance + +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag diff --git a/tests/components/pm1006/test.esp32-c3-idf.yaml b/tests/components/pm1006/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..15ee077f3e94 --- /dev/null +++ b/tests/components/pm1006/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pm1006/test.esp32-c3.yaml b/tests/components/pm1006/test.esp32-c3.yaml new file mode 100644 index 000000000000..15ee077f3e94 --- /dev/null +++ b/tests/components/pm1006/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pm1006/test.esp32-idf.yaml b/tests/components/pm1006/test.esp32-idf.yaml new file mode 100644 index 000000000000..635af37b2512 --- /dev/null +++ b/tests/components/pm1006/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pm1006/test.esp32.yaml b/tests/components/pm1006/test.esp32.yaml new file mode 100644 index 000000000000..635af37b2512 --- /dev/null +++ b/tests/components/pm1006/test.esp32.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pm1006/test.esp8266.yaml b/tests/components/pm1006/test.esp8266.yaml new file mode 100644 index 000000000000..15ee077f3e94 --- /dev/null +++ b/tests/components/pm1006/test.esp8266.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pm1006/test.rp2040.yaml b/tests/components/pm1006/test.rp2040.yaml new file mode 100644 index 000000000000..15ee077f3e94 --- /dev/null +++ b/tests/components/pm1006/test.rp2040.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_pm1006 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pm1006 + pm_2_5: + name: Particulate Matter 2.5µm Concentration diff --git a/tests/components/pmsa003i/test.esp32-c3-idf.yaml b/tests/components/pmsa003i/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..70e28303a2c9 --- /dev/null +++ b/tests/components/pmsa003i/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 5 + sda: 4 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsa003i/test.esp32-c3.yaml b/tests/components/pmsa003i/test.esp32-c3.yaml new file mode 100644 index 000000000000..70e28303a2c9 --- /dev/null +++ b/tests/components/pmsa003i/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 5 + sda: 4 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsa003i/test.esp32-idf.yaml b/tests/components/pmsa003i/test.esp32-idf.yaml new file mode 100644 index 000000000000..d8d96400f6e3 --- /dev/null +++ b/tests/components/pmsa003i/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 16 + sda: 17 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsa003i/test.esp32.yaml b/tests/components/pmsa003i/test.esp32.yaml new file mode 100644 index 000000000000..d8d96400f6e3 --- /dev/null +++ b/tests/components/pmsa003i/test.esp32.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 16 + sda: 17 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsa003i/test.esp8266.yaml b/tests/components/pmsa003i/test.esp8266.yaml new file mode 100644 index 000000000000..70e28303a2c9 --- /dev/null +++ b/tests/components/pmsa003i/test.esp8266.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 5 + sda: 4 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsa003i/test.rp2040.yaml b/tests/components/pmsa003i/test.rp2040.yaml new file mode 100644 index 000000000000..70e28303a2c9 --- /dev/null +++ b/tests/components/pmsa003i/test.rp2040.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_pmsa003i + scl: 5 + sda: 4 + +sensor: + - platform: pmsa003i + pm_1_0: + name: PMSA003i PM1.0 + pm_2_5: + name: PMSA003i PM2.5 + pm_10_0: + name: PMSA003i PM10.0 + pmc_0_3: + name: PMSA003i PMC <0.3µm + pmc_0_5: + name: PMSA003i PMC <0.5µm + pmc_1_0: + name: PMSA003i PMC <1µm + pmc_2_5: + name: PMSA003i PMC <2.5µm + pmc_5_0: + name: PMSA003i PMC <5µm + pmc_10_0: + name: PMSA003i PMC <10µm + address: 0x12 + standard_units: true diff --git a/tests/components/pmsx003/test.esp32-c3-idf.yaml b/tests/components/pmsx003/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..58adc9390aad --- /dev/null +++ b/tests/components/pmsx003/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmsx003/test.esp32-c3.yaml b/tests/components/pmsx003/test.esp32-c3.yaml new file mode 100644 index 000000000000..58adc9390aad --- /dev/null +++ b/tests/components/pmsx003/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmsx003/test.esp32-idf.yaml b/tests/components/pmsx003/test.esp32-idf.yaml new file mode 100644 index 000000000000..5e7ebbbb2e84 --- /dev/null +++ b/tests/components/pmsx003/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmsx003/test.esp32.yaml b/tests/components/pmsx003/test.esp32.yaml new file mode 100644 index 000000000000..5e7ebbbb2e84 --- /dev/null +++ b/tests/components/pmsx003/test.esp32.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmsx003/test.esp8266.yaml b/tests/components/pmsx003/test.esp8266.yaml new file mode 100644 index 000000000000..58adc9390aad --- /dev/null +++ b/tests/components/pmsx003/test.esp8266.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmsx003/test.rp2040.yaml b/tests/components/pmsx003/test.rp2040.yaml new file mode 100644 index 000000000000..58adc9390aad --- /dev/null +++ b/tests/components/pmsx003/test.rp2040.yaml @@ -0,0 +1,34 @@ +uart: + - id: uart_pmsx003 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: pmsx003 + type: PMSX003 + pm_1_0: + name: PM 1.0 Concentration + pm_2_5: + name: PM 2.5 Concentration + pm_10_0: + name: PM 10.0 Concentration + pm_1_0_std: + name: PM 1.0 Standard Atmospher Concentration + pm_2_5_std: + name: PM 2.5 Standard Atmospher Concentration + pm_10_0_std: + name: PM 10.0 Standard Atmospher Concentration + pm_0_3um: + name: Particulate Count >0.3um + pm_0_5um: + name: Particulate Count >0.5um + pm_1_0um: + name: Particulate Count >1.0um + pm_2_5um: + name: Particulate Count >2.5um + pm_5_0um: + name: Particulate Count >5.0um + pm_10_0um: + name: Particulate Count >10.0um + update_interval: 30s diff --git a/tests/components/pmwcs3/test.esp32-c3-idf.yaml b/tests/components/pmwcs3/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7e7e72692d46 --- /dev/null +++ b/tests/components/pmwcs3/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 5 + sda: 4 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pmwcs3/test.esp32-c3.yaml b/tests/components/pmwcs3/test.esp32-c3.yaml new file mode 100644 index 000000000000..7e7e72692d46 --- /dev/null +++ b/tests/components/pmwcs3/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 5 + sda: 4 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pmwcs3/test.esp32-idf.yaml b/tests/components/pmwcs3/test.esp32-idf.yaml new file mode 100644 index 000000000000..787eaca650ef --- /dev/null +++ b/tests/components/pmwcs3/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 16 + sda: 17 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pmwcs3/test.esp32.yaml b/tests/components/pmwcs3/test.esp32.yaml new file mode 100644 index 000000000000..787eaca650ef --- /dev/null +++ b/tests/components/pmwcs3/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 16 + sda: 17 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pmwcs3/test.esp8266.yaml b/tests/components/pmwcs3/test.esp8266.yaml new file mode 100644 index 000000000000..7e7e72692d46 --- /dev/null +++ b/tests/components/pmwcs3/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 5 + sda: 4 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pmwcs3/test.rp2040.yaml b/tests/components/pmwcs3/test.rp2040.yaml new file mode 100644 index 000000000000..7e7e72692d46 --- /dev/null +++ b/tests/components/pmwcs3/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_pmwcs3 + scl: 5 + sda: 4 + +sensor: + - platform: pmwcs3 + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc diff --git a/tests/components/pn532_i2c/test.esp32-c3-idf.yaml b/tests/components/pn532_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..62816d2acec6 --- /dev/null +++ b/tests/components/pn532_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 5 + sda: 4 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_i2c/test.esp32-c3.yaml b/tests/components/pn532_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..62816d2acec6 --- /dev/null +++ b/tests/components/pn532_i2c/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 5 + sda: 4 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_i2c/test.esp32-idf.yaml b/tests/components/pn532_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..a50533b1d0f8 --- /dev/null +++ b/tests/components/pn532_i2c/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 16 + sda: 17 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_i2c/test.esp32.yaml b/tests/components/pn532_i2c/test.esp32.yaml new file mode 100644 index 000000000000..a50533b1d0f8 --- /dev/null +++ b/tests/components/pn532_i2c/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 16 + sda: 17 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_i2c/test.esp8266.yaml b/tests/components/pn532_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..62816d2acec6 --- /dev/null +++ b/tests/components/pn532_i2c/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 5 + sda: 4 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_i2c/test.rp2040.yaml b/tests/components/pn532_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..62816d2acec6 --- /dev/null +++ b/tests/components/pn532_i2c/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_pn532 + scl: 5 + sda: 4 + +pn532_i2c: + id: pn532_nfcc + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.esp32-c3-idf.yaml b/tests/components/pn532_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d21d50aa5c92 --- /dev/null +++ b/tests/components/pn532_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +pn532_spi: + id: pn532_nfcc + cs_pin: 4 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.esp32-c3.yaml b/tests/components/pn532_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..d21d50aa5c92 --- /dev/null +++ b/tests/components/pn532_spi/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +pn532_spi: + id: pn532_nfcc + cs_pin: 4 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.esp32-idf.yaml b/tests/components/pn532_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..18a382a007e1 --- /dev/null +++ b/tests/components/pn532_spi/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +pn532_spi: + id: pn532_nfcc + cs_pin: 12 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.esp32.yaml b/tests/components/pn532_spi/test.esp32.yaml new file mode 100644 index 000000000000..18a382a007e1 --- /dev/null +++ b/tests/components/pn532_spi/test.esp32.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +pn532_spi: + id: pn532_nfcc + cs_pin: 12 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.esp8266.yaml b/tests/components/pn532_spi/test.esp8266.yaml new file mode 100644 index 000000000000..1dba38e63ea9 --- /dev/null +++ b/tests/components/pn532_spi/test.esp8266.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +pn532_spi: + id: pn532_nfcc + cs_pin: 15 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn532_spi/test.rp2040.yaml b/tests/components/pn532_spi/test.rp2040.yaml new file mode 100644 index 000000000000..ab02b2cc47ea --- /dev/null +++ b/tests/components/pn532_spi/test.rp2040.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_pn532 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +pn532_spi: + id: pn532_nfcc + cs_pin: 6 + +binary_sensor: + - platform: pn532 + pn532_id: pn532_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml b/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..aee1886cd460 --- /dev/null +++ b/tests/components/pn7150_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7150_i2c/test.esp32-c3.yaml b/tests/components/pn7150_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..aee1886cd460 --- /dev/null +++ b/tests/components/pn7150_i2c/test.esp32-c3.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7150_i2c/test.esp32-idf.yaml b/tests/components/pn7150_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..23d1061608ea --- /dev/null +++ b/tests/components/pn7150_i2c/test.esp32-idf.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 16 + sda: 17 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7150_i2c/test.esp32.yaml b/tests/components/pn7150_i2c/test.esp32.yaml new file mode 100644 index 000000000000..23d1061608ea --- /dev/null +++ b/tests/components/pn7150_i2c/test.esp32.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 16 + sda: 17 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7150_i2c/test.esp8266.yaml b/tests/components/pn7150_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..6017d548cac9 --- /dev/null +++ b/tests/components/pn7150_i2c/test.esp8266.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7150_i2c/test.rp2040.yaml b/tests/components/pn7150_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..aee1886cd460 --- /dev/null +++ b/tests/components/pn7150_i2c/test.rp2040.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7150 + - tag.set_format_mode: nfcc_pn7150 + - tag.set_read_mode: nfcc_pn7150 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7150 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7150 + - tag.emulation_on: nfcc_pn7150 + - tag.polling_off: nfcc_pn7150 + - tag.polling_on: nfcc_pn7150 + +i2c: + - id: i2c_pn7150 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7150 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml b/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d1d7947352cd --- /dev/null +++ b/tests/components/pn7160_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 5 + sda: 4 + +pn7160_i2c: + id: nfcc_pn7160 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.esp32-c3.yaml b/tests/components/pn7160_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..d1d7947352cd --- /dev/null +++ b/tests/components/pn7160_i2c/test.esp32-c3.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 5 + sda: 4 + +pn7160_i2c: + id: nfcc_pn7160 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.esp32-idf.yaml b/tests/components/pn7160_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..d1a3cf5c7744 --- /dev/null +++ b/tests/components/pn7160_i2c/test.esp32-idf.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 16 + sda: 17 + +pn7150_i2c: + id: nfcc_pn7160 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.esp32.yaml b/tests/components/pn7160_i2c/test.esp32.yaml new file mode 100644 index 000000000000..d1a3cf5c7744 --- /dev/null +++ b/tests/components/pn7160_i2c/test.esp32.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 16 + sda: 17 + +pn7150_i2c: + id: nfcc_pn7160 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.esp8266.yaml b/tests/components/pn7160_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..57bd965fc94b --- /dev/null +++ b/tests/components/pn7160_i2c/test.esp8266.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7160 + irq_pin: 12 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_i2c/test.rp2040.yaml b/tests/components/pn7160_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..5224b465edf4 --- /dev/null +++ b/tests/components/pn7160_i2c/test.rp2040.yaml @@ -0,0 +1,35 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +i2c: + - id: i2c_pn7160 + scl: 5 + sda: 4 + +pn7150_i2c: + id: nfcc_pn7160 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.esp32-c3-idf.yaml b/tests/components/pn7160_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fd19a53b2b5e --- /dev/null +++ b/tests/components/pn7160_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 4 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.esp32-c3.yaml b/tests/components/pn7160_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..fd19a53b2b5e --- /dev/null +++ b/tests/components/pn7160_spi/test.esp32-c3.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 4 + irq_pin: 2 + ven_pin: 3 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.esp32-idf.yaml b/tests/components/pn7160_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..0319648f13b6 --- /dev/null +++ b/tests/components/pn7160_spi/test.esp32-idf.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 12 + irq_pin: 14 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.esp32.yaml b/tests/components/pn7160_spi/test.esp32.yaml new file mode 100644 index 000000000000..0319648f13b6 --- /dev/null +++ b/tests/components/pn7160_spi/test.esp32.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 12 + irq_pin: 14 + ven_pin: 13 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.esp8266.yaml b/tests/components/pn7160_spi/test.esp8266.yaml new file mode 100644 index 000000000000..fa356d561013 --- /dev/null +++ b/tests/components/pn7160_spi/test.esp8266.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 15 + irq_pin: 4 + ven_pin: 5 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/pn7160_spi/test.rp2040.yaml b/tests/components/pn7160_spi/test.rp2040.yaml new file mode 100644 index 000000000000..b36650032f11 --- /dev/null +++ b/tests/components/pn7160_spi/test.rp2040.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - tag.set_clean_mode: nfcc_pn7160 + - tag.set_format_mode: nfcc_pn7160 + - tag.set_read_mode: nfcc_pn7160 + - tag.set_write_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.set_write_mode: nfcc_pn7160 + - tag.set_emulation_message: + message: https://www.home-assistant.io/tag/pulse + include_android_app_record: false + - tag.emulation_off: nfcc_pn7160 + - tag.emulation_on: nfcc_pn7160 + - tag.polling_off: nfcc_pn7160 + - tag.polling_on: nfcc_pn7160 + +spi: + - id: spi_pn7160 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +pn7160_spi: + id: nfcc_pn7160 + cs_pin: 6 + irq_pin: 7 + ven_pin: 5 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + on_tag: + - logger.log: "Tag" + on_tag_removed: + - logger.log: "Tag removed" + on_emulated_tag_scan: + - logger.log: "Tag emulated" diff --git a/tests/components/power_supply/common.yaml b/tests/components/power_supply/common.yaml new file mode 100644 index 000000000000..3fefc4d42587 --- /dev/null +++ b/tests/components/power_supply/common.yaml @@ -0,0 +1,6 @@ +power_supply: + - id: atx_power_supply + enable_time: 20ms + keep_on_time: 10s + enable_on_boot: true + pin: 4 diff --git a/tests/components/power_supply/test.esp32-c3-idf.yaml b/tests/components/power_supply/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/power_supply/test.esp32-c3.yaml b/tests/components/power_supply/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/power_supply/test.esp32-idf.yaml b/tests/components/power_supply/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/power_supply/test.esp32.yaml b/tests/components/power_supply/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/power_supply/test.esp8266.yaml b/tests/components/power_supply/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/power_supply/test.rp2040.yaml b/tests/components/power_supply/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/power_supply/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/prometheus/common.yaml b/tests/components/prometheus/common.yaml new file mode 100644 index 000000000000..c8ce17da88cc --- /dev/null +++ b/tests/components/prometheus/common.yaml @@ -0,0 +1,21 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: template + id: template_sensor1 + lambda: |- + if (millis() > 10000) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +prometheus: + include_internal: true + relabel: + template_sensor1: + id: hellow_world + name: Hello World diff --git a/tests/components/prometheus/test.esp32-c3-idf.yaml b/tests/components/prometheus/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/prometheus/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/prometheus/test.esp32-c3.yaml b/tests/components/prometheus/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/prometheus/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/prometheus/test.esp32-idf.yaml b/tests/components/prometheus/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/prometheus/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/prometheus/test.esp32.yaml b/tests/components/prometheus/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/prometheus/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/prometheus/test.esp8266.yaml b/tests/components/prometheus/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/prometheus/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/psram/common.yaml b/tests/components/psram/common.yaml new file mode 100644 index 000000000000..cfd39f77fed4 --- /dev/null +++ b/tests/components/psram/common.yaml @@ -0,0 +1,3 @@ +psram: + mode: octal + speed: 80MHz diff --git a/tests/components/psram/test.esp32-c3-idf.yaml b/tests/components/psram/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/psram/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/psram/test.esp32-c3.yaml b/tests/components/psram/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/psram/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/psram/test.esp32-idf.yaml b/tests/components/psram/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/psram/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/psram/test.esp32.yaml b/tests/components/psram/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/psram/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/common.yaml b/tests/components/pulse_counter/common.yaml new file mode 100644 index 000000000000..556b43ee6fa4 --- /dev/null +++ b/tests/components/pulse_counter/common.yaml @@ -0,0 +1,9 @@ +sensor: + - platform: pulse_counter + name: Pulse Counter + pin: 4 + count_mode: + rising_edge: INCREMENT + falling_edge: DECREMENT + internal_filter: 13us + update_interval: 15s diff --git a/tests/components/pulse_counter/test.esp32-c3-idf.yaml b/tests/components/pulse_counter/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/test.esp32-c3.yaml b/tests/components/pulse_counter/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/test.esp32-idf.yaml b/tests/components/pulse_counter/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/test.esp32.yaml b/tests/components/pulse_counter/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/test.esp8266.yaml b/tests/components/pulse_counter/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_counter/test.rp2040.yaml b/tests/components/pulse_counter/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_counter/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/common.yaml b/tests/components/pulse_meter/common.yaml new file mode 100644 index 000000000000..a83ec478bb1b --- /dev/null +++ b/tests/components/pulse_meter/common.yaml @@ -0,0 +1,13 @@ +sensor: + - platform: pulse_meter + id: pulse_meter_sensor + name: Pulse Meter + pin: 4 + internal_filter: 100ms + timeout: 2 min + on_value: + - pulse_meter.set_total_pulses: + id: pulse_meter_sensor + value: 12345 + total: + name: Pulse Meter Total diff --git a/tests/components/pulse_meter/test.esp32-c3-idf.yaml b/tests/components/pulse_meter/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.esp32-c3.yaml b/tests/components/pulse_meter/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.esp32-idf.yaml b/tests/components/pulse_meter/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.esp32.yaml b/tests/components/pulse_meter/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.esp8266.yaml b/tests/components/pulse_meter/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_meter/test.rp2040.yaml b/tests/components/pulse_meter/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_meter/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/common.yaml b/tests/components/pulse_width/common.yaml new file mode 100644 index 000000000000..fbda7cda28a9 --- /dev/null +++ b/tests/components/pulse_width/common.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: pulse_width + name: Pulse Width + pin: 4 diff --git a/tests/components/pulse_width/test.esp32-c3-idf.yaml b/tests/components/pulse_width/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/test.esp32-c3.yaml b/tests/components/pulse_width/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/test.esp32-idf.yaml b/tests/components/pulse_width/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/test.esp32.yaml b/tests/components/pulse_width/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/test.esp8266.yaml b/tests/components/pulse_width/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pulse_width/test.rp2040.yaml b/tests/components/pulse_width/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pulse_width/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pvvx_mithermometer/common.yaml b/tests/components/pvvx_mithermometer/common.yaml new file mode 100644 index 000000000000..972f23122c7e --- /dev/null +++ b/tests/components/pvvx_mithermometer/common.yaml @@ -0,0 +1,44 @@ +wifi: + ssid: MySSID + password: password1 + +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: pvvx_ble_display + +display: + - platform: pvvx_mithermometer + ble_client_id: pvvx_ble_display + time_id: sntp_time + disconnect_delay: 3s + update_interval: 10min + validity_period: 20min + lambda: |- + it.print_bignum(188.8); + it.print_unit(pvvx_mithermometer::UNIT_DEG_E); + it.print_smallnum(88); + it.print_percent(true); + it.print_happy(true); + it.print_sad(true); + it.print_bracket(true); + it.print_battery(true); + +sensor: + - platform: pvvx_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: PVVX Temperature + humidity: + name: PVVX Humidity + battery_level: + name: PVVX Battery-Level + battery_voltage: + name: PVVX Battery-Voltage + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org diff --git a/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml b/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pvvx_mithermometer/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pvvx_mithermometer/test.esp32-c3.yaml b/tests/components/pvvx_mithermometer/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pvvx_mithermometer/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pvvx_mithermometer/test.esp32-idf.yaml b/tests/components/pvvx_mithermometer/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pvvx_mithermometer/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pvvx_mithermometer/test.esp32.yaml b/tests/components/pvvx_mithermometer/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/pvvx_mithermometer/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/pylontech/test.esp32-c3-idf.yaml b/tests/components/pylontech/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f7ec493422b5 --- /dev/null +++ b/tests/components/pylontech/test.esp32-c3-idf.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pylontech/test.esp32-c3.yaml b/tests/components/pylontech/test.esp32-c3.yaml new file mode 100644 index 000000000000..f7ec493422b5 --- /dev/null +++ b/tests/components/pylontech/test.esp32-c3.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pylontech/test.esp32-idf.yaml b/tests/components/pylontech/test.esp32-idf.yaml new file mode 100644 index 000000000000..a4c168fb4766 --- /dev/null +++ b/tests/components/pylontech/test.esp32-idf.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pylontech/test.esp32.yaml b/tests/components/pylontech/test.esp32.yaml new file mode 100644 index 000000000000..a4c168fb4766 --- /dev/null +++ b/tests/components/pylontech/test.esp32.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pylontech/test.esp8266.yaml b/tests/components/pylontech/test.esp8266.yaml new file mode 100644 index 000000000000..f7ec493422b5 --- /dev/null +++ b/tests/components/pylontech/test.esp8266.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pylontech/test.rp2040.yaml b/tests/components/pylontech/test.rp2040.yaml new file mode 100644 index 000000000000..f7ec493422b5 --- /dev/null +++ b/tests/components/pylontech/test.rp2040.yaml @@ -0,0 +1,48 @@ +uart: + - id: uart_pylontech0 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +pylontech: + - id: pylontech0 + - id: pylontech1 + +sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high + +text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state diff --git a/tests/components/pzem004t/test.esp32-c3-idf.yaml b/tests/components/pzem004t/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b9c93f876126 --- /dev/null +++ b/tests/components/pzem004t/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzem004t/test.esp32-c3.yaml b/tests/components/pzem004t/test.esp32-c3.yaml new file mode 100644 index 000000000000..b9c93f876126 --- /dev/null +++ b/tests/components/pzem004t/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzem004t/test.esp32-idf.yaml b/tests/components/pzem004t/test.esp32-idf.yaml new file mode 100644 index 000000000000..23f2bd0eca28 --- /dev/null +++ b/tests/components/pzem004t/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzem004t/test.esp32.yaml b/tests/components/pzem004t/test.esp32.yaml new file mode 100644 index 000000000000..23f2bd0eca28 --- /dev/null +++ b/tests/components/pzem004t/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzem004t/test.esp8266.yaml b/tests/components/pzem004t/test.esp8266.yaml new file mode 100644 index 000000000000..b9c93f876126 --- /dev/null +++ b/tests/components/pzem004t/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzem004t/test.rp2040.yaml b/tests/components/pzem004t/test.rp2040.yaml new file mode 100644 index 000000000000..b9c93f876126 --- /dev/null +++ b/tests/components/pzem004t/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_pzem004t + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: pzem004t + voltage: + name: PZEM004T Voltage + current: + name: PZEM004T Current + power: + name: PZEM004T Power diff --git a/tests/components/pzemac/test.esp32-c3-idf.yaml b/tests/components/pzemac/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..6d9abbebe9f6 --- /dev/null +++ b/tests/components/pzemac/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemac/test.esp32-c3.yaml b/tests/components/pzemac/test.esp32-c3.yaml new file mode 100644 index 000000000000..6d9abbebe9f6 --- /dev/null +++ b/tests/components/pzemac/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemac/test.esp32-idf.yaml b/tests/components/pzemac/test.esp32-idf.yaml new file mode 100644 index 000000000000..ce431a610084 --- /dev/null +++ b/tests/components/pzemac/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemac/test.esp32.yaml b/tests/components/pzemac/test.esp32.yaml new file mode 100644 index 000000000000..ce431a610084 --- /dev/null +++ b/tests/components/pzemac/test.esp32.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemac/test.esp8266.yaml b/tests/components/pzemac/test.esp8266.yaml new file mode 100644 index 000000000000..6d9abbebe9f6 --- /dev/null +++ b/tests/components/pzemac/test.esp8266.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemac/test.rp2040.yaml b/tests/components/pzemac/test.rp2040.yaml new file mode 100644 index 000000000000..6d9abbebe9f6 --- /dev/null +++ b/tests/components/pzemac/test.rp2040.yaml @@ -0,0 +1,28 @@ +esphome: + on_boot: + then: + - pzemac.reset_energy: pzemac1 + +uart: + - id: uart_pzemac + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +modbus: + +sensor: + - platform: pzemac + id: pzemac1 + voltage: + name: PZEMAC Voltage + current: + name: PZEMAC Current + power: + name: PZEMAC Power + energy: + name: PZEMAC Energy + frequency: + name: PZEMAC Frequency + power_factor: + name: PZEMAC Power Factor diff --git a/tests/components/pzemdc/test.esp32-c3-idf.yaml b/tests/components/pzemdc/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..02114b781dca --- /dev/null +++ b/tests/components/pzemdc/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/pzemdc/test.esp32-c3.yaml b/tests/components/pzemdc/test.esp32-c3.yaml new file mode 100644 index 000000000000..02114b781dca --- /dev/null +++ b/tests/components/pzemdc/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/pzemdc/test.esp32-idf.yaml b/tests/components/pzemdc/test.esp32-idf.yaml new file mode 100644 index 000000000000..9cc61137deb5 --- /dev/null +++ b/tests/components/pzemdc/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/pzemdc/test.esp32.yaml b/tests/components/pzemdc/test.esp32.yaml new file mode 100644 index 000000000000..9cc61137deb5 --- /dev/null +++ b/tests/components/pzemdc/test.esp32.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/pzemdc/test.esp8266.yaml b/tests/components/pzemdc/test.esp8266.yaml new file mode 100644 index 000000000000..02114b781dca --- /dev/null +++ b/tests/components/pzemdc/test.esp8266.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/pzemdc/test.rp2040.yaml b/tests/components/pzemdc/test.rp2040.yaml new file mode 100644 index 000000000000..02114b781dca --- /dev/null +++ b/tests/components/pzemdc/test.rp2040.yaml @@ -0,0 +1,23 @@ +esphome: + on_boot: + then: + - pzemdc.reset_energy: pzemdc1 + +uart: + - id: uart_pzemdc + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + stop_bits: 2 + +sensor: + - platform: pzemdc + id: pzemdc1 + voltage: + name: PZEMDC Voltage + current: + name: PZEMDC Current + power: + name: PZEMDC Power + energy: + name: PZEMDC Energy diff --git a/tests/components/qmc5883l/test.esp32-c3-idf.yaml b/tests/components/qmc5883l/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..841bbd5d1eb5 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32-c3.yaml b/tests/components/qmc5883l/test.esp32-c3.yaml new file mode 100644 index 000000000000..841bbd5d1eb5 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32-idf.yaml b/tests/components/qmc5883l/test.esp32-idf.yaml new file mode 100644 index 000000000000..9acd391497a9 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp32.yaml b/tests/components/qmc5883l/test.esp32.yaml new file mode 100644 index 000000000000..9acd391497a9 --- /dev/null +++ b/tests/components/qmc5883l/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 16 + sda: 17 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.esp8266.yaml b/tests/components/qmc5883l/test.esp8266.yaml new file mode 100644 index 000000000000..841bbd5d1eb5 --- /dev/null +++ b/tests/components/qmc5883l/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmc5883l/test.rp2040.yaml b/tests/components/qmc5883l/test.rp2040.yaml new file mode 100644 index 000000000000..841bbd5d1eb5 --- /dev/null +++ b/tests/components/qmc5883l/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_qmc5883l + scl: 5 + sda: 4 + +sensor: + - platform: qmc5883l + address: 0x0D + field_strength_x: + name: QMC5883L Field Strength X + field_strength_y: + name: QMC5883L Field Strength Y + field_strength_z: + name: QMC5883L Field Strength Z + heading: + name: QMC5883L Heading + temperature: + name: QMC5883L Temperature + range: 800uT + oversampling: 256x + update_interval: 15s diff --git a/tests/components/qmp6988/test.esp32-c3-idf.yaml b/tests/components/qmp6988/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..bcd87ae6b86c --- /dev/null +++ b/tests/components/qmp6988/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32-c3.yaml b/tests/components/qmp6988/test.esp32-c3.yaml new file mode 100644 index 000000000000..bcd87ae6b86c --- /dev/null +++ b/tests/components/qmp6988/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32-idf.yaml b/tests/components/qmp6988/test.esp32-idf.yaml new file mode 100644 index 000000000000..f3fbf75bbe36 --- /dev/null +++ b/tests/components/qmp6988/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 16 + sda: 17 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp32.yaml b/tests/components/qmp6988/test.esp32.yaml new file mode 100644 index 000000000000..f3fbf75bbe36 --- /dev/null +++ b/tests/components/qmp6988/test.esp32.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 16 + sda: 17 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.esp8266.yaml b/tests/components/qmp6988/test.esp8266.yaml new file mode 100644 index 000000000000..bcd87ae6b86c --- /dev/null +++ b/tests/components/qmp6988/test.esp8266.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qmp6988/test.rp2040.yaml b/tests/components/qmp6988/test.rp2040.yaml new file mode 100644 index 000000000000..bcd87ae6b86c --- /dev/null +++ b/tests/components/qmp6988/test.rp2040.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_qmp6988 + scl: 5 + sda: 4 + +sensor: + - platform: qmp6988 + temperature: + name: QMP6988 Temperature + oversampling: 32x + pressure: + name: QMP6988 Pressure + oversampling: 2x + address: 0x70 + update_interval: 30s + iir_filter: 16x diff --git a/tests/components/qr_code/test.esp32-c3-idf.yaml b/tests/components/qr_code/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..63973b1aa25b --- /dev/null +++ b/tests/components/qr_code/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32-c3.yaml b/tests/components/qr_code/test.esp32-c3.yaml new file mode 100644 index 000000000000..63973b1aa25b --- /dev/null +++ b/tests/components/qr_code/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32-idf.yaml b/tests/components/qr_code/test.esp32-idf.yaml new file mode 100644 index 000000000000..3e70d3258f46 --- /dev/null +++ b/tests/components/qr_code/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp32.yaml b/tests/components/qr_code/test.esp32.yaml new file mode 100644 index 000000000000..3e70d3258f46 --- /dev/null +++ b/tests/components/qr_code/test.esp32.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 12 + dc_pin: 13 + reset_pin: 21 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.esp8266.yaml b/tests/components/qr_code/test.esp8266.yaml new file mode 100644 index 000000000000..3c304d7575aa --- /dev/null +++ b/tests/components/qr_code/test.esp8266.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 5 + dc_pin: 15 + reset_pin: 16 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qr_code/test.rp2040.yaml b/tests/components/qr_code/test.rp2040.yaml new file mode 100644 index 000000000000..94cb772ba381 --- /dev/null +++ b/tests/components/qr_code/test.rp2040.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_main_lcd + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 21 + reset_pin: 22 + lambda: |- + // Draw a QR code in the center of the screen + auto scale = 2; + auto size = id(homepage_qr).get_size() * scale; + auto x = (it.get_width() / 2) - (size / 2); + auto y = (it.get_height() / 2) - (size / 2); + it.qr_code(x, y, id(homepage_qr), Color(255,255,255), scale); + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html diff --git a/tests/components/qspi_amoled/common.yaml b/tests/components/qspi_amoled/common.yaml new file mode 100644 index 000000000000..01d1a63bcb34 --- /dev/null +++ b/tests/components/qspi_amoled/common.yaml @@ -0,0 +1,36 @@ +spi: + id: quad_spi + clk_pin: 15 + type: quad + data_pins: [14, 10, 16, 12] + +display: + - platform: qspi_amoled + model: RM690B0 + data_rate: 80MHz + spi_mode: mode0 + dimensions: + width: 450 + height: 600 + offset_width: 16 + color_order: rgb + invert_colors: false + brightness: 255 + cs_pin: 11 + reset_pin: 13 + enable_pin: 9 + + - platform: qspi_amoled + model: RM67162 + id: main_lcd + dimensions: + height: 240 + width: 536 + transform: + mirror_x: true + swap_xy: true + color_order: rgb + brightness: 255 + cs_pin: 6 + reset_pin: 17 + enable_pin: 38 diff --git a/tests/components/qspi_amoled/test.esp32-s3-idf.yaml b/tests/components/qspi_amoled/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/qspi_amoled/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..ad52ac91c550 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32-c3.yaml b/tests/components/qwiic_pir/test.esp32-c3.yaml new file mode 100644 index 000000000000..ad52ac91c550 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32-idf.yaml b/tests/components/qwiic_pir/test.esp32-idf.yaml new file mode 100644 index 000000000000..da2e275cf30f --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 16 + sda: 17 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp32.yaml b/tests/components/qwiic_pir/test.esp32.yaml new file mode 100644 index 000000000000..da2e275cf30f --- /dev/null +++ b/tests/components/qwiic_pir/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 16 + sda: 17 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.esp8266.yaml b/tests/components/qwiic_pir/test.esp8266.yaml new file mode 100644 index 000000000000..ad52ac91c550 --- /dev/null +++ b/tests/components/qwiic_pir/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/qwiic_pir/test.rp2040.yaml b/tests/components/qwiic_pir/test.rp2040.yaml new file mode 100644 index 000000000000..ad52ac91c550 --- /dev/null +++ b/tests/components/qwiic_pir/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_qwiic_pir + scl: 5 + sda: 4 + +binary_sensor: + - platform: qwiic_pir + name: Qwiic PIR Motion Sensor diff --git a/tests/components/radon_eye_ble/common.yaml b/tests/components/radon_eye_ble/common.yaml new file mode 100644 index 000000000000..85638d5c0ebd --- /dev/null +++ b/tests/components/radon_eye_ble/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +radon_eye_ble: diff --git a/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml b/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_ble/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_ble/test.esp32-c3.yaml b/tests/components/radon_eye_ble/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_ble/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_ble/test.esp32-idf.yaml b/tests/components/radon_eye_ble/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_ble/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_ble/test.esp32.yaml b/tests/components/radon_eye_ble/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_ble/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_rd200/common.yaml b/tests/components/radon_eye_rd200/common.yaml new file mode 100644 index 000000000000..d06979be6ff2 --- /dev/null +++ b/tests/components/radon_eye_rd200/common.yaml @@ -0,0 +1,14 @@ +esp32_ble_tracker: + +ble_client: + - mac_address: 01:02:03:04:05:06 + id: radon_eye_blec + +sensor: + - platform: radon_eye_rd200 + ble_client_id: radon_eye_blec + radon: + name: RD200 Radon + radon_long_term: + name: RD200 Radon Long Term + update_interval: 10min diff --git a/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml b/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_rd200/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_rd200/test.esp32-c3.yaml b/tests/components/radon_eye_rd200/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_rd200/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_rd200/test.esp32-idf.yaml b/tests/components/radon_eye_rd200/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_rd200/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/radon_eye_rd200/test.esp32.yaml b/tests/components/radon_eye_rd200/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/radon_eye_rd200/test.esp32.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..8c8819e25708 --- /dev/null +++ b/tests/components/rc522_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 5 + sda: 4 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_i2c/test.esp32-c3.yaml b/tests/components/rc522_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..8c8819e25708 --- /dev/null +++ b/tests/components/rc522_i2c/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 5 + sda: 4 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_i2c/test.esp32-idf.yaml b/tests/components/rc522_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..69b7d892a442 --- /dev/null +++ b/tests/components/rc522_i2c/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 16 + sda: 17 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_i2c/test.esp32.yaml b/tests/components/rc522_i2c/test.esp32.yaml new file mode 100644 index 000000000000..69b7d892a442 --- /dev/null +++ b/tests/components/rc522_i2c/test.esp32.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 16 + sda: 17 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_i2c/test.esp8266.yaml b/tests/components/rc522_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..8c8819e25708 --- /dev/null +++ b/tests/components/rc522_i2c/test.esp8266.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 5 + sda: 4 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_i2c/test.rp2040.yaml b/tests/components/rc522_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..8c8819e25708 --- /dev/null +++ b/tests/components/rc522_i2c/test.rp2040.yaml @@ -0,0 +1,17 @@ +i2c: + - id: i2c_rc522 + scl: 5 + sda: 4 + +rc522_i2c: + - id: rc522_nfcc + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: RC522 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.esp32-c3-idf.yaml b/tests/components/rc522_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..8bcab8470086 --- /dev/null +++ b/tests/components/rc522_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +rc522_spi: + id: rc522_nfcc + cs_pin: 4 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.esp32-c3.yaml b/tests/components/rc522_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..8bcab8470086 --- /dev/null +++ b/tests/components/rc522_spi/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +rc522_spi: + id: rc522_nfcc + cs_pin: 4 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.esp32-idf.yaml b/tests/components/rc522_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..5c0b698a08dc --- /dev/null +++ b/tests/components/rc522_spi/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +rc522_spi: + id: rc522_nfcc + cs_pin: 12 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.esp32.yaml b/tests/components/rc522_spi/test.esp32.yaml new file mode 100644 index 000000000000..5c0b698a08dc --- /dev/null +++ b/tests/components/rc522_spi/test.esp32.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +rc522_spi: + id: rc522_nfcc + cs_pin: 12 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.esp8266.yaml b/tests/components/rc522_spi/test.esp8266.yaml new file mode 100644 index 000000000000..3c3331126689 --- /dev/null +++ b/tests/components/rc522_spi/test.esp8266.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +rc522_spi: + id: rc522_nfcc + cs_pin: 15 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rc522_spi/test.rp2040.yaml b/tests/components/rc522_spi/test.rp2040.yaml new file mode 100644 index 000000000000..ed2827dbb995 --- /dev/null +++ b/tests/components/rc522_spi/test.rp2040.yaml @@ -0,0 +1,15 @@ +spi: + - id: spi_rc522 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +rc522_spi: + id: rc522_nfcc + cs_pin: 6 + +binary_sensor: + - platform: rc522 + rc522_id: rc522_nfcc + name: PN532 NFC Tag + uid: 74-10-37-94 diff --git a/tests/components/rdm6300/test.esp32-c3-idf.yaml b/tests/components/rdm6300/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b92fce06e2dc --- /dev/null +++ b/tests/components/rdm6300/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/rdm6300/test.esp32-c3.yaml b/tests/components/rdm6300/test.esp32-c3.yaml new file mode 100644 index 000000000000..b92fce06e2dc --- /dev/null +++ b/tests/components/rdm6300/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/rdm6300/test.esp32-idf.yaml b/tests/components/rdm6300/test.esp32-idf.yaml new file mode 100644 index 000000000000..4159248124e1 --- /dev/null +++ b/tests/components/rdm6300/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/rdm6300/test.esp32.yaml b/tests/components/rdm6300/test.esp32.yaml new file mode 100644 index 000000000000..4159248124e1 --- /dev/null +++ b/tests/components/rdm6300/test.esp32.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/rdm6300/test.esp8266.yaml b/tests/components/rdm6300/test.esp8266.yaml new file mode 100644 index 000000000000..b92fce06e2dc --- /dev/null +++ b/tests/components/rdm6300/test.esp8266.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/rdm6300/test.rp2040.yaml b/tests/components/rdm6300/test.rp2040.yaml new file mode 100644 index 000000000000..b92fce06e2dc --- /dev/null +++ b/tests/components/rdm6300/test.rp2040.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_rdm6300 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rdm6300: + +binary_sensor: + - platform: rdm6300 + uid: 7616525 + name: RDM6300 NFC Tag diff --git a/tests/components/remote_receiver/esp32-common.yaml b/tests/components/remote_receiver/esp32-common.yaml new file mode 100644 index 000000000000..0e71143fc32d --- /dev/null +++ b/tests/components/remote_receiver/esp32-common.yaml @@ -0,0 +1,152 @@ +remote_receiver: + id: rcvr + pin: ${pin} + rmt_channel: ${rmt_channel} + dump: all + on_abbwelcome: + then: + - logger.log: + format: "on_abbwelcome: %u" + args: ["x.data()[0]"] + on_aeha: + then: + - logger.log: + format: "on_aeha: %u %u" + args: ["x.address", "x.data.front()"] + on_byronsx: + then: + - logger.log: + format: "on_byronsx: %u %u" + args: ["x.address", "x.command"] + on_canalsat: + then: + - logger.log: + format: "on_canalsat: %u %u" + args: ["x.address", "x.command"] + # on_canalsatld: + # then: + # - logger.log: + # format: "on_canalsatld: %u %u" + # args: ["x.address", "x.command"] + on_coolix: + then: + - logger.log: + format: "on_coolix: %u %u" + args: ["x.first", "x.second"] + on_dish: + then: + - logger.log: + format: "on_dish: %u %u" + args: ["x.address", "x.command"] + on_dooya: + then: + - logger.log: + format: "on_dooya: %u %u %u" + args: ["x.channel", "x.button", "x.check"] + on_drayton: + then: + - logger.log: + format: "on_drayton: %u %u %u" + args: ["x.address", "x.channel", "x.command"] + on_jvc: + then: + - logger.log: + format: "on_jvc: %u" + args: ["x.data"] + on_keeloq: + then: + - logger.log: + format: "on_keeloq: %u %u %u" + args: ["x.encrypted", "x.address", "x.command"] + on_haier: + then: + - logger.log: + format: "on_haier: %u" + args: ["x.data.front()"] + on_lg: + then: + - logger.log: + format: "on_lg: %u %u" + args: ["x.data", "x.nbits"] + on_magiquest: + then: + - logger.log: + format: "on_magiquest: %u %u" + args: ["x.magnitude", "x.wand_id"] + on_midea: + then: + - logger.log: + format: "on_midea: %u %u" + args: ["x.size()", "x.data()[0]"] + on_nec: + then: + - logger.log: + format: "on_nec: %u %u" + args: ["x.address", "x.command"] + on_nexa: + then: + - logger.log: + format: "on_nexa: %u %u %u %u %u" + args: ["x.device", "x.group", "x.state", "x.channel", "x.level"] + on_panasonic: + then: + - logger.log: + format: "on_panasonic: %u %u" + args: ["x.address", "x.command"] + on_pioneer: + then: + - logger.log: + format: "on_pioneer: %u %u" + args: ["x.rc_code_1", "x.rc_code_2"] + on_pronto: + then: + - logger.log: + format: "on_pronto: %s" + args: ["x.data.c_str()"] + on_raw: + then: + - logger.log: + format: "on_raw: %u" + args: ["x.front()"] + on_rc5: + then: + - logger.log: + format: "on_rc5: %u %u" + args: ["x.address", "x.command"] + on_rc6: + then: + - logger.log: + format: "on_rc6: %u %u" + args: ["x.address", "x.command"] + on_rc_switch: + then: + - logger.log: + format: "on_rc_switch: %llu %u" + args: ["x.code", "x.protocol"] + on_samsung: + then: + - logger.log: + format: "on_samsung: %llu %u" + args: ["x.data", "x.nbits"] + on_samsung36: + then: + - logger.log: + format: "on_samsung36: %u %u" + args: ["x.address", "x.command"] + on_sony: + then: + - logger.log: + format: "on_sony: %u %u" + args: ["x.data", "x.nbits"] + on_toshiba_ac: + then: + - logger.log: + format: "on_toshiba_ac: %llu %llu" + args: ["x.rc_code_1", "x.rc_code_2"] + +binary_sensor: + - platform: remote_receiver + name: Panasonic Remote Input + panasonic: + address: 0x4004 + command: 0x100BCBD diff --git a/tests/components/remote_receiver/test.esp32-c3-idf.yaml b/tests/components/remote_receiver/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_receiver/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-c3.yaml b/tests/components/remote_receiver/test.esp32-c3.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_receiver/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-idf.yaml b/tests/components/remote_receiver/test.esp32-idf.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_receiver/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32-s3-idf.yaml b/tests/components/remote_receiver/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..265ecda771fa --- /dev/null +++ b/tests/components/remote_receiver/test.esp32-s3-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO38 + rmt_channel: "5" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp32.yaml b/tests/components/remote_receiver/test.esp32.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_receiver/test.esp32.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_receiver/test.esp8266.yaml b/tests/components/remote_receiver/test.esp8266.yaml new file mode 100644 index 000000000000..e96f031e90ca --- /dev/null +++ b/tests/components/remote_receiver/test.esp8266.yaml @@ -0,0 +1,151 @@ +remote_receiver: + id: rcvr + pin: GPIO5 + dump: all + on_abbwelcome: + then: + - logger.log: + format: "on_abbwelcome: %u" + args: ["x.data()[0]"] + on_aeha: + then: + - logger.log: + format: "on_aeha: %u %u" + args: ["x.address", "x.data.front()"] + on_byronsx: + then: + - logger.log: + format: "on_byronsx: %u %u" + args: ["x.address", "x.command"] + on_canalsat: + then: + - logger.log: + format: "on_canalsat: %u %u" + args: ["x.address", "x.command"] + # on_canalsatld: + # then: + # - logger.log: + # format: "on_canalsatld: %u %u" + # args: ["x.address", "x.command"] + on_coolix: + then: + - logger.log: + format: "on_coolix: %u %u" + args: ["x.first", "x.second"] + on_dish: + then: + - logger.log: + format: "on_dish: %u %u" + args: ["x.address", "x.command"] + on_dooya: + then: + - logger.log: + format: "on_dooya: %u %u %u" + args: ["x.channel", "x.button", "x.check"] + on_drayton: + then: + - logger.log: + format: "on_drayton: %u %u %u" + args: ["x.address", "x.channel", "x.command"] + on_jvc: + then: + - logger.log: + format: "on_jvc: %u" + args: ["x.data"] + on_keeloq: + then: + - logger.log: + format: "on_keeloq: %u %u %u" + args: ["x.encrypted", "x.address", "x.command"] + on_haier: + then: + - logger.log: + format: "on_haier: %u" + args: ["x.data.front()"] + on_lg: + then: + - logger.log: + format: "on_lg: %u %u" + args: ["x.data", "x.nbits"] + on_magiquest: + then: + - logger.log: + format: "on_magiquest: %u %u" + args: ["x.magnitude", "x.wand_id"] + on_midea: + then: + - logger.log: + format: "on_midea: %u %u" + args: ["x.size()", "x.data()[0]"] + on_nec: + then: + - logger.log: + format: "on_nec: %u %u" + args: ["x.address", "x.command"] + on_nexa: + then: + - logger.log: + format: "on_nexa: %u %u %u %u %u" + args: ["x.device", "x.group", "x.state", "x.channel", "x.level"] + on_panasonic: + then: + - logger.log: + format: "on_panasonic: %u %u" + args: ["x.address", "x.command"] + on_pioneer: + then: + - logger.log: + format: "on_pioneer: %u %u" + args: ["x.rc_code_1", "x.rc_code_2"] + on_pronto: + then: + - logger.log: + format: "on_pronto: %s" + args: ["x.data.c_str()"] + on_raw: + then: + - logger.log: + format: "on_raw: %u" + args: ["x.front()"] + on_rc5: + then: + - logger.log: + format: "on_rc5: %u %u" + args: ["x.address", "x.command"] + on_rc6: + then: + - logger.log: + format: "on_rc6: %u %u" + args: ["x.address", "x.command"] + on_rc_switch: + then: + - logger.log: + format: "on_rc_switch: %llu %u" + args: ["x.code", "x.protocol"] + on_samsung: + then: + - logger.log: + format: "on_samsung: %llu %u" + args: ["x.data", "x.nbits"] + on_samsung36: + then: + - logger.log: + format: "on_samsung36: %u %u" + args: ["x.address", "x.command"] + on_sony: + then: + - logger.log: + format: "on_sony: %u %u" + args: ["x.data", "x.nbits"] + on_toshiba_ac: + then: + - logger.log: + format: "on_toshiba_ac: %llu %llu" + args: ["x.rc_code_1", "x.rc_code_2"] + +binary_sensor: + - platform: remote_receiver + name: Panasonic Remote Input + panasonic: + address: 0x4004 + command: 0x100BCBD diff --git a/tests/components/remote_transmitter/common-buttons.yaml b/tests/components/remote_transmitter/common-buttons.yaml new file mode 100644 index 000000000000..27683b387fd5 --- /dev/null +++ b/tests/components/remote_transmitter/common-buttons.yaml @@ -0,0 +1,186 @@ +button: + - platform: template + name: JVC Off + id: living_room_lights_on + on_press: + remote_transmitter.transmit_jvc: + data: 0x10EF + - platform: template + name: MagiQuest + on_press: + remote_transmitter.transmit_magiquest: + wand_id: 0x01234567 + - platform: template + name: NEC + id: living_room_lights_off + on_press: + remote_transmitter.transmit_nec: + address: 0x4242 + command: 0x8484 + - platform: template + name: LG + on_press: + remote_transmitter.transmit_lg: + data: 4294967295 + nbits: 28 + - platform: template + name: Samsung + on_press: + remote_transmitter.transmit_samsung: + data: 0xABCDEF + - platform: template + name: Samsung36 + on_press: + remote_transmitter.transmit_samsung36: + address: 0x0400 + command: 0x000E00FF + - platform: template + name: ToshibaAC + on_press: + - remote_transmitter.transmit_toshiba_ac: + rc_code_1: 0xB24DBF4050AF + rc_code_2: 0xD5660001003C + - platform: template + name: Sony + on_press: + remote_transmitter.transmit_sony: + data: 0xABCDEF + nbits: 12 + - platform: template + name: Panasonic + on_press: + remote_transmitter.transmit_panasonic: + address: 0x4004 + command: 0x1000BCD + - platform: template + name: Pioneer + on_press: + - remote_transmitter.transmit_pioneer: + rc_code_1: 0xA556 + rc_code_2: 0xA506 + repeat: + times: 2 + - platform: template + name: RC Switch Raw + on_press: + remote_transmitter.transmit_rc_switch_raw: + code: "00101001100111110101xxxx" + protocol: 1 + - platform: template + name: RC Switch Type A + on_press: + remote_transmitter.transmit_rc_switch_type_a: + group: "11001" + device: "01000" + state: true + protocol: + pulse_length: 175 + sync: [1, 31] + zero: [1, 3] + one: [3, 1] + inverted: false + - platform: template + name: RC Switch Type B + on_press: + remote_transmitter.transmit_rc_switch_type_b: + address: 4 + channel: 2 + state: true + - platform: template + name: RC Switch Type C + on_press: + remote_transmitter.transmit_rc_switch_type_c: + family: "a" + group: 1 + device: 2 + state: true + - platform: template + name: RC Switch Type D + on_press: + remote_transmitter.transmit_rc_switch_type_d: + group: "a" + device: 2 + state: true + - platform: template + name: RC5 + on_press: + remote_transmitter.transmit_rc5: + address: 0x00 + command: 0x0B + - platform: template + name: RC5 + on_press: + remote_transmitter.transmit_raw: + code: [1000, -1000] + - platform: template + name: AEHA + id: eaha_hitachi_climate_power_on + on_press: + remote_transmitter.transmit_aeha: + address: 0x8008 + data: + [ + 0x00, + 0x02, + 0xFD, + 0xFF, + 0x00, + 0x33, + 0xCC, + 0x49, + 0xB6, + 0xC8, + 0x37, + 0x16, + 0xE9, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0xCA, + 0x35, + 0x8F, + 0x70, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0xFF, + ] + - platform: template + name: Haier + on_press: + remote_transmitter.transmit_haier: + code: + [ + 0xA6, + 0xDA, + 0x00, + 0x00, + 0x40, + 0x40, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + ] + - platform: template + name: Dooya + on_press: + remote_transmitter.transmit_dooya: + id: 0x123456 + channel: 1 + button: 1 + check: 1 diff --git a/tests/components/remote_transmitter/esp32-common.yaml b/tests/components/remote_transmitter/esp32-common.yaml new file mode 100644 index 000000000000..3f3cd3f8c728 --- /dev/null +++ b/tests/components/remote_transmitter/esp32-common.yaml @@ -0,0 +1,8 @@ +remote_transmitter: + id: rcvr + pin: ${pin} + rmt_channel: ${rmt_channel} + carrier_duty_percent: 50% + +packages: + buttons: !include common-buttons.yaml diff --git a/tests/components/remote_transmitter/test.esp32-c3-idf.yaml b/tests/components/remote_transmitter/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3e2dc88e5a4b --- /dev/null +++ b/tests/components/remote_transmitter/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "1" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-c3.yaml b/tests/components/remote_transmitter/test.esp32-c3.yaml new file mode 100644 index 000000000000..3e2dc88e5a4b --- /dev/null +++ b/tests/components/remote_transmitter/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "1" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-idf.yaml b/tests/components/remote_transmitter/test.esp32-idf.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_transmitter/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32-s3-idf.yaml b/tests/components/remote_transmitter/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..31851dc54c9f --- /dev/null +++ b/tests/components/remote_transmitter/test.esp32-s3-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO38 + rmt_channel: "3" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp32.yaml b/tests/components/remote_transmitter/test.esp32.yaml new file mode 100644 index 000000000000..16d276958ae8 --- /dev/null +++ b/tests/components/remote_transmitter/test.esp32.yaml @@ -0,0 +1,6 @@ +substitutions: + pin: GPIO2 + rmt_channel: "2" + +packages: + common: !include esp32-common.yaml diff --git a/tests/components/remote_transmitter/test.esp8266.yaml b/tests/components/remote_transmitter/test.esp8266.yaml new file mode 100644 index 000000000000..de494485f4a4 --- /dev/null +++ b/tests/components/remote_transmitter/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + id: trns + pin: GPIO5 + carrier_duty_percent: 50% + +packages: + buttons: !include common-buttons.yaml diff --git a/tests/components/resistance/test.esp32-c3.yaml b/tests/components/resistance/test.esp32-c3.yaml new file mode 100644 index 000000000000..84e23d5115a4 --- /dev/null +++ b/tests/components/resistance/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + pin: 4 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.esp32-idf.yaml b/tests/components/resistance/test.esp32-idf.yaml new file mode 100644 index 000000000000..b1ffc6497264 --- /dev/null +++ b/tests/components/resistance/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.esp32-s2.yaml b/tests/components/resistance/test.esp32-s2.yaml new file mode 100644 index 000000000000..4ebd6b5c49b9 --- /dev/null +++ b/tests/components/resistance/test.esp32-s2.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.esp32-s3.yaml b/tests/components/resistance/test.esp32-s3.yaml new file mode 100644 index 000000000000..4ebd6b5c49b9 --- /dev/null +++ b/tests/components/resistance/test.esp32-s3.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + pin: 1 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.esp32.yaml b/tests/components/resistance/test.esp32.yaml new file mode 100644 index 000000000000..b1ffc6497264 --- /dev/null +++ b/tests/components/resistance/test.esp32.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + pin: 32 + attenuation: 11db + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.esp8266.yaml b/tests/components/resistance/test.esp8266.yaml new file mode 100644 index 000000000000..f723f7c7c780 --- /dev/null +++ b/tests/components/resistance/test.esp8266.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: adc + id: my_sensor + pin: VCC + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/resistance/test.rp2040.yaml b/tests/components/resistance/test.rp2040.yaml new file mode 100644 index 000000000000..5cc643926a04 --- /dev/null +++ b/tests/components/resistance/test.rp2040.yaml @@ -0,0 +1,12 @@ +sensor: + - platform: adc + id: my_sensor + name: VSYS + pin: VCC + - platform: resistance + sensor: my_sensor + configuration: DOWNSTREAM + resistor: 10kΩ + reference_voltage: 3.3V + name: Resistance + id: resist diff --git a/tests/components/restart/common.yaml b/tests/components/restart/common.yaml new file mode 100644 index 000000000000..f0d25809ac3f --- /dev/null +++ b/tests/components/restart/common.yaml @@ -0,0 +1,7 @@ +button: + - platform: restart + name: Restart Button + +switch: + - platform: restart + name: Restart Switch diff --git a/tests/components/restart/test.esp32-c3-idf.yaml b/tests/components/restart/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/restart/test.esp32-c3.yaml b/tests/components/restart/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/restart/test.esp32-idf.yaml b/tests/components/restart/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/restart/test.esp32.yaml b/tests/components/restart/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/restart/test.esp8266.yaml b/tests/components/restart/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/restart/test.rp2040.yaml b/tests/components/restart/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/restart/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..95a7aa861a96 --- /dev/null +++ b/tests/components/rf_bridge/test.esp32-c3-idf.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rf_bridge/test.esp32-c3.yaml b/tests/components/rf_bridge/test.esp32-c3.yaml new file mode 100644 index 000000000000..95a7aa861a96 --- /dev/null +++ b/tests/components/rf_bridge/test.esp32-c3.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rf_bridge/test.esp32-idf.yaml b/tests/components/rf_bridge/test.esp32-idf.yaml new file mode 100644 index 000000000000..9ade7f0ac03e --- /dev/null +++ b/tests/components/rf_bridge/test.esp32-idf.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rf_bridge/test.esp32.yaml b/tests/components/rf_bridge/test.esp32.yaml new file mode 100644 index 000000000000..9ade7f0ac03e --- /dev/null +++ b/tests/components/rf_bridge/test.esp32.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rf_bridge/test.esp8266.yaml b/tests/components/rf_bridge/test.esp8266.yaml new file mode 100644 index 000000000000..95a7aa861a96 --- /dev/null +++ b/tests/components/rf_bridge/test.esp8266.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rf_bridge/test.rp2040.yaml b/tests/components/rf_bridge/test.rp2040.yaml new file mode 100644 index 000000000000..95a7aa861a96 --- /dev/null +++ b/tests/components/rf_bridge/test.rp2040.yaml @@ -0,0 +1,35 @@ +uart: + - id: uart_rf_bridge + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +rf_bridge: + on_code_received: + - lambda: |- + uint32_t test; + test = data.sync; + test = data.low; + test = data.high; + test = data.code; + - rf_bridge.send_code: + sync: 0x1234 + low: 0x1234 + high: 0x1234 + code: 0x123456 + - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: "ABC123" + - rf_bridge.send_raw: + raw: "AAA5070008001000ABC12355" diff --git a/tests/components/rgb/test.esp32-c3-idf.yaml b/tests/components/rgb/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..30ff1527b474 --- /dev/null +++ b/tests/components/rgb/test.esp32-c3-idf.yaml @@ -0,0 +1,18 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgb/test.esp32-c3.yaml b/tests/components/rgb/test.esp32-c3.yaml new file mode 100644 index 000000000000..30ff1527b474 --- /dev/null +++ b/tests/components/rgb/test.esp32-c3.yaml @@ -0,0 +1,18 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgb/test.esp32-idf.yaml b/tests/components/rgb/test.esp32-idf.yaml new file mode 100644 index 000000000000..2173e718bee9 --- /dev/null +++ b/tests/components/rgb/test.esp32-idf.yaml @@ -0,0 +1,18 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgb/test.esp32.yaml b/tests/components/rgb/test.esp32.yaml new file mode 100644 index 000000000000..2173e718bee9 --- /dev/null +++ b/tests/components/rgb/test.esp32.yaml @@ -0,0 +1,18 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgb/test.esp8266.yaml b/tests/components/rgb/test.esp8266.yaml new file mode 100644 index 000000000000..60c5a7e04fff --- /dev/null +++ b/tests/components/rgb/test.esp8266.yaml @@ -0,0 +1,18 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + - platform: esp8266_pwm + id: light_output_3 + pin: 14 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgb/test.rp2040.yaml b/tests/components/rgb/test.rp2040.yaml new file mode 100644 index 000000000000..fd6519707b23 --- /dev/null +++ b/tests/components/rgb/test.rp2040.yaml @@ -0,0 +1,18 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + - platform: rp2040_pwm + id: light_output_3 + pin: 14 + +light: + - platform: rgb + name: RGB Light + id: rgb_light + red: light_output_1 + green: light_output_2 + blue: light_output_3 diff --git a/tests/components/rgbct/test.esp32-c3-idf.yaml b/tests/components/rgbct/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..426c4b8937a9 --- /dev/null +++ b/tests/components/rgbct/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + - platform: ledc + id: light_output_5 + pin: 5 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbct/test.esp32-c3.yaml b/tests/components/rgbct/test.esp32-c3.yaml new file mode 100644 index 000000000000..426c4b8937a9 --- /dev/null +++ b/tests/components/rgbct/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + - platform: ledc + id: light_output_5 + pin: 5 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbct/test.esp32-idf.yaml b/tests/components/rgbct/test.esp32-idf.yaml new file mode 100644 index 000000000000..d9758c9ec747 --- /dev/null +++ b/tests/components/rgbct/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + - platform: ledc + id: light_output_5 + pin: 16 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbct/test.esp32.yaml b/tests/components/rgbct/test.esp32.yaml new file mode 100644 index 000000000000..d9758c9ec747 --- /dev/null +++ b/tests/components/rgbct/test.esp32.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + - platform: ledc + id: light_output_5 + pin: 16 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbct/test.esp8266.yaml b/tests/components/rgbct/test.esp8266.yaml new file mode 100644 index 000000000000..b7008c9ae3bd --- /dev/null +++ b/tests/components/rgbct/test.esp8266.yaml @@ -0,0 +1,28 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + - platform: esp8266_pwm + id: light_output_3 + pin: 14 + - platform: esp8266_pwm + id: light_output_4 + pin: 15 + - platform: esp8266_pwm + id: light_output_5 + pin: 16 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbct/test.rp2040.yaml b/tests/components/rgbct/test.rp2040.yaml new file mode 100644 index 000000000000..e7e959b2a4e1 --- /dev/null +++ b/tests/components/rgbct/test.rp2040.yaml @@ -0,0 +1,28 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + - platform: rp2040_pwm + id: light_output_3 + pin: 14 + - platform: rp2040_pwm + id: light_output_4 + pin: 15 + - platform: rp2040_pwm + id: light_output_5 + pin: 16 + +light: + - platform: rgbct + name: RGBCT Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + color_temperature: light_output_4 + white_brightness: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbw/test.esp32-c3-idf.yaml b/tests/components/rgbw/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c5d4fceb9d2c --- /dev/null +++ b/tests/components/rgbw/test.esp32-c3-idf.yaml @@ -0,0 +1,22 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbw/test.esp32-c3.yaml b/tests/components/rgbw/test.esp32-c3.yaml new file mode 100644 index 000000000000..c5d4fceb9d2c --- /dev/null +++ b/tests/components/rgbw/test.esp32-c3.yaml @@ -0,0 +1,22 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbw/test.esp32-idf.yaml b/tests/components/rgbw/test.esp32-idf.yaml new file mode 100644 index 000000000000..6e9e92a03c90 --- /dev/null +++ b/tests/components/rgbw/test.esp32-idf.yaml @@ -0,0 +1,22 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbw/test.esp32.yaml b/tests/components/rgbw/test.esp32.yaml new file mode 100644 index 000000000000..6e9e92a03c90 --- /dev/null +++ b/tests/components/rgbw/test.esp32.yaml @@ -0,0 +1,22 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbw/test.esp8266.yaml b/tests/components/rgbw/test.esp8266.yaml new file mode 100644 index 000000000000..54098613e428 --- /dev/null +++ b/tests/components/rgbw/test.esp8266.yaml @@ -0,0 +1,22 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + - platform: esp8266_pwm + id: light_output_3 + pin: 14 + - platform: esp8266_pwm + id: light_output_4 + pin: 15 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbw/test.rp2040.yaml b/tests/components/rgbw/test.rp2040.yaml new file mode 100644 index 000000000000..6a4437b89862 --- /dev/null +++ b/tests/components/rgbw/test.rp2040.yaml @@ -0,0 +1,22 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + - platform: rp2040_pwm + id: light_output_3 + pin: 14 + - platform: rp2040_pwm + id: light_output_4 + pin: 15 + +light: + - platform: rgbw + name: RGBW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + white: light_output_4 + color_interlock: true diff --git a/tests/components/rgbww/test.esp32-c3-idf.yaml b/tests/components/rgbww/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..49e9c7f331a2 --- /dev/null +++ b/tests/components/rgbww/test.esp32-c3-idf.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + - platform: ledc + id: light_output_5 + pin: 5 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbww/test.esp32-c3.yaml b/tests/components/rgbww/test.esp32-c3.yaml new file mode 100644 index 000000000000..49e9c7f331a2 --- /dev/null +++ b/tests/components/rgbww/test.esp32-c3.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 1 + - platform: ledc + id: light_output_2 + pin: 2 + - platform: ledc + id: light_output_3 + pin: 3 + - platform: ledc + id: light_output_4 + pin: 4 + - platform: ledc + id: light_output_5 + pin: 5 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbww/test.esp32-idf.yaml b/tests/components/rgbww/test.esp32-idf.yaml new file mode 100644 index 000000000000..c24b6b77468e --- /dev/null +++ b/tests/components/rgbww/test.esp32-idf.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + - platform: ledc + id: light_output_5 + pin: 16 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbww/test.esp32.yaml b/tests/components/rgbww/test.esp32.yaml new file mode 100644 index 000000000000..c24b6b77468e --- /dev/null +++ b/tests/components/rgbww/test.esp32.yaml @@ -0,0 +1,28 @@ +output: + - platform: ledc + id: light_output_1 + pin: 12 + - platform: ledc + id: light_output_2 + pin: 13 + - platform: ledc + id: light_output_3 + pin: 14 + - platform: ledc + id: light_output_4 + pin: 15 + - platform: ledc + id: light_output_5 + pin: 16 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbww/test.esp8266.yaml b/tests/components/rgbww/test.esp8266.yaml new file mode 100644 index 000000000000..4ea26e6526ca --- /dev/null +++ b/tests/components/rgbww/test.esp8266.yaml @@ -0,0 +1,28 @@ +output: + - platform: esp8266_pwm + id: light_output_1 + pin: 12 + - platform: esp8266_pwm + id: light_output_2 + pin: 13 + - platform: esp8266_pwm + id: light_output_3 + pin: 14 + - platform: esp8266_pwm + id: light_output_4 + pin: 15 + - platform: esp8266_pwm + id: light_output_5 + pin: 16 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rgbww/test.rp2040.yaml b/tests/components/rgbww/test.rp2040.yaml new file mode 100644 index 000000000000..0986f06e7871 --- /dev/null +++ b/tests/components/rgbww/test.rp2040.yaml @@ -0,0 +1,28 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 12 + - platform: rp2040_pwm + id: light_output_2 + pin: 13 + - platform: rp2040_pwm + id: light_output_3 + pin: 14 + - platform: rp2040_pwm + id: light_output_4 + pin: 15 + - platform: rp2040_pwm + id: light_output_5 + pin: 16 + +light: + - platform: rgbww + name: RGBWW Light + red: light_output_1 + green: light_output_2 + blue: light_output_3 + cold_white: light_output_4 + warm_white: light_output_5 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true diff --git a/tests/components/rotary_encoder/test.esp32-c3-idf.yaml b/tests/components/rotary_encoder/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..59f8b56abfc7 --- /dev/null +++ b/tests/components/rotary_encoder/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 3 + pin_b: 4 + pin_reset: 5 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rotary_encoder/test.esp32-c3.yaml b/tests/components/rotary_encoder/test.esp32-c3.yaml new file mode 100644 index 000000000000..59f8b56abfc7 --- /dev/null +++ b/tests/components/rotary_encoder/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 3 + pin_b: 4 + pin_reset: 5 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rotary_encoder/test.esp32-idf.yaml b/tests/components/rotary_encoder/test.esp32-idf.yaml new file mode 100644 index 000000000000..da3843f82d1a --- /dev/null +++ b/tests/components/rotary_encoder/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 13 + pin_b: 14 + pin_reset: 15 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rotary_encoder/test.esp32.yaml b/tests/components/rotary_encoder/test.esp32.yaml new file mode 100644 index 000000000000..da3843f82d1a --- /dev/null +++ b/tests/components/rotary_encoder/test.esp32.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 13 + pin_b: 14 + pin_reset: 15 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rotary_encoder/test.esp8266.yaml b/tests/components/rotary_encoder/test.esp8266.yaml new file mode 100644 index 000000000000..da3843f82d1a --- /dev/null +++ b/tests/components/rotary_encoder/test.esp8266.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 13 + pin_b: 14 + pin_reset: 15 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rotary_encoder/test.rp2040.yaml b/tests/components/rotary_encoder/test.rp2040.yaml new file mode 100644 index 000000000000..da3843f82d1a --- /dev/null +++ b/tests/components/rotary_encoder/test.rp2040.yaml @@ -0,0 +1,25 @@ +sensor: + - platform: rotary_encoder + name: Rotary Encoder + id: rotary_encoder1 + pin_a: 13 + pin_b: 14 + pin_reset: 15 + filters: + - or: + - debounce: 0.1s + - delta: 10 + resolution: 4 + min_value: -10 + max_value: 30 + on_value: + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: 10 + - sensor.rotary_encoder.set_value: + id: rotary_encoder1 + value: !lambda "return -1;" + on_clockwise: + - logger.log: Clockwise + on_anticlockwise: + - logger.log: Anticlockwise diff --git a/tests/components/rp2040_pio_led_strip/common.yaml b/tests/components/rp2040_pio_led_strip/common.yaml new file mode 100644 index 000000000000..b9b1436cdb14 --- /dev/null +++ b/tests/components/rp2040_pio_led_strip/common.yaml @@ -0,0 +1,18 @@ +light: + - platform: rp2040_pio_led_strip + id: led_strip + pin: 4 + num_leds: 60 + pio: 0 + rgb_order: GRB + chipset: WS2812 + - platform: rp2040_pio_led_strip + id: led_strip_custom_timings + pin: 5 + num_leds: 60 + pio: 1 + rgb_order: GRB + bit0_high: .1us + bit0_low: 1.2us + bit1_high: .69us + bit1_low: .4us diff --git a/tests/components/rp2040_pio_led_strip/test.rp2040.yaml b/tests/components/rp2040_pio_led_strip/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/rp2040_pio_led_strip/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/rp2040_pwm/common.yaml b/tests/components/rp2040_pwm/common.yaml new file mode 100644 index 000000000000..45c039106fea --- /dev/null +++ b/tests/components/rp2040_pwm/common.yaml @@ -0,0 +1,7 @@ +output: + - platform: rp2040_pwm + id: light_output_1 + pin: 2 + - platform: rp2040_pwm + id: light_output_2 + pin: 3 diff --git a/tests/components/rp2040_pwm/test.rp2040.yaml b/tests/components/rp2040_pwm/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/rp2040_pwm/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/rpi_dpi_rgb/common.yaml b/tests/components/rpi_dpi_rgb/common.yaml new file mode 100644 index 000000000000..9ce2d9b9fd7f --- /dev/null +++ b/tests/components/rpi_dpi_rgb/common.yaml @@ -0,0 +1,40 @@ +psram: + mode: octal + speed: 80MHz +display: + - platform: rpi_dpi_rgb + update_interval: never + auto_clear_enabled: false + id: rpi_display + color_order: RGB + rotation: 90 + dimensions: + width: 800 + height: 480 + de_pin: + number: 40 + hsync_pin: 39 + vsync_pin: 41 + pclk_pin: 42 + data_pins: + red: + - number: 45 # r1 + ignore_strapping_warning: true + - 48 # r2 + - 47 # r3 + - 21 # r4 + - number: 14 # r5 + ignore_strapping_warning: false + green: + - 5 # g0 + - 6 # g1 + - 7 # g2 + - 15 # g3 + - 16 # g4 + - 4 # g5 + blue: + - 8 # b1 + - 3 # b2 + - 46 # b3 + - 9 # b4 + - 1 # b5 diff --git a/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml b/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/rpi_dpi_rgb/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/rtttl/test.esp32-c3-idf.yaml b/tests/components/rtttl/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c525f417dec6 --- /dev/null +++ b/tests/components/rtttl/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: ledc + id: rtttl_output + pin: 1 + frequency: 1500Hz + channel: 14 + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/rtttl/test.esp32-c3.yaml b/tests/components/rtttl/test.esp32-c3.yaml new file mode 100644 index 000000000000..c525f417dec6 --- /dev/null +++ b/tests/components/rtttl/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: ledc + id: rtttl_output + pin: 1 + frequency: 1500Hz + channel: 14 + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/rtttl/test.esp32-idf.yaml b/tests/components/rtttl/test.esp32-idf.yaml new file mode 100644 index 000000000000..367a670741c1 --- /dev/null +++ b/tests/components/rtttl/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: ledc + id: rtttl_output + pin: 13 + frequency: 1500Hz + channel: 14 + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/rtttl/test.esp32.yaml b/tests/components/rtttl/test.esp32.yaml new file mode 100644 index 000000000000..367a670741c1 --- /dev/null +++ b/tests/components/rtttl/test.esp32.yaml @@ -0,0 +1,16 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: ledc + id: rtttl_output + pin: 13 + frequency: 1500Hz + channel: 14 + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/rtttl/test.esp8266.yaml b/tests/components/rtttl/test.esp8266.yaml new file mode 100644 index 000000000000..c3b87c0f7255 --- /dev/null +++ b/tests/components/rtttl/test.esp8266.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: esp8266_pwm + id: rtttl_output + pin: 13 + frequency: 1500Hz + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/rtttl/test.rp2040.yaml b/tests/components/rtttl/test.rp2040.yaml new file mode 100644 index 000000000000..ea240aa34d78 --- /dev/null +++ b/tests/components/rtttl/test.rp2040.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e' + - rtttl.stop + +output: + - platform: rp2040_pwm + id: rtttl_output + pin: 3 + frequency: 1500Hz + max_power: 0.5 + +rtttl: + output: rtttl_output diff --git a/tests/components/ruuvi_ble/common.yaml b/tests/components/ruuvi_ble/common.yaml new file mode 100644 index 000000000000..1f155fd8e1a3 --- /dev/null +++ b/tests/components/ruuvi_ble/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +ruuvi_ble: diff --git a/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml b/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvi_ble/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvi_ble/test.esp32-c3.yaml b/tests/components/ruuvi_ble/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvi_ble/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvi_ble/test.esp32-idf.yaml b/tests/components/ruuvi_ble/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvi_ble/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvi_ble/test.esp32.yaml b/tests/components/ruuvi_ble/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvi_ble/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvitag/common.yaml b/tests/components/ruuvitag/common.yaml new file mode 100644 index 000000000000..79906177105d --- /dev/null +++ b/tests/components/ruuvitag/common.yaml @@ -0,0 +1,27 @@ +esp32_ble_tracker: + +sensor: + - platform: ruuvitag + mac_address: FF:56:D3:2F:7D:E8 + humidity: + name: RuuviTag Humidity + temperature: + name: RuuviTag Temperature + pressure: + name: RuuviTag Pressure + acceleration: + name: RuuviTag Acceleration + acceleration_x: + name: RuuviTag Acceleration X + acceleration_y: + name: RuuviTag Acceleration Y + acceleration_z: + name: RuuviTag Acceleration Z + battery_voltage: + name: RuuviTag Battery Voltage + tx_power: + name: RuuviTag TX Power + movement_counter: + name: RuuviTag Movement Counter + measurement_sequence_number: + name: RuuviTag Measurement Sequence Number diff --git a/tests/components/ruuvitag/test.esp32-c3-idf.yaml b/tests/components/ruuvitag/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvitag/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvitag/test.esp32-c3.yaml b/tests/components/ruuvitag/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvitag/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvitag/test.esp32-idf.yaml b/tests/components/ruuvitag/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvitag/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ruuvitag/test.esp32.yaml b/tests/components/ruuvitag/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ruuvitag/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/common.yaml b/tests/components/safe_mode/common.yaml new file mode 100644 index 000000000000..df0abd9aec82 --- /dev/null +++ b/tests/components/safe_mode/common.yaml @@ -0,0 +1,13 @@ +wifi: + ssid: MySSID + password: password1 + +ota: + +button: + - platform: safe_mode + name: Safe Mode Button + +switch: + - platform: safe_mode + name: Safe Mode Switch diff --git a/tests/components/safe_mode/test.esp32-c3-idf.yaml b/tests/components/safe_mode/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/test.esp32-c3.yaml b/tests/components/safe_mode/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/test.esp32-idf.yaml b/tests/components/safe_mode/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/test.esp32.yaml b/tests/components/safe_mode/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/test.esp8266.yaml b/tests/components/safe_mode/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/safe_mode/test.rp2040.yaml b/tests/components/safe_mode/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/safe_mode/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/scd30/test.esp32-c3-idf.yaml b/tests/components/scd30/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..80f02a1b872a --- /dev/null +++ b/tests/components/scd30/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 5 + sda: 4 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd30/test.esp32-c3.yaml b/tests/components/scd30/test.esp32-c3.yaml new file mode 100644 index 000000000000..80f02a1b872a --- /dev/null +++ b/tests/components/scd30/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 5 + sda: 4 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd30/test.esp32-idf.yaml b/tests/components/scd30/test.esp32-idf.yaml new file mode 100644 index 000000000000..b48f8054c89a --- /dev/null +++ b/tests/components/scd30/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 16 + sda: 17 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd30/test.esp32.yaml b/tests/components/scd30/test.esp32.yaml new file mode 100644 index 000000000000..b48f8054c89a --- /dev/null +++ b/tests/components/scd30/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 16 + sda: 17 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd30/test.esp8266.yaml b/tests/components/scd30/test.esp8266.yaml new file mode 100644 index 000000000000..80f02a1b872a --- /dev/null +++ b/tests/components/scd30/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 5 + sda: 4 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd30/test.rp2040.yaml b/tests/components/scd30/test.rp2040.yaml new file mode 100644 index 000000000000..80f02a1b872a --- /dev/null +++ b/tests/components/scd30/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd30 + scl: 5 + sda: 4 + +sensor: + - platform: scd30 + co2: + name: SCD30 CO2 + temperature: + id: scd30_temperature + name: SCD30 Temperature + humidity: + name: SCD30 Humidity + address: 0x61 + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.esp32-c3-idf.yaml b/tests/components/scd4x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..353293be65df --- /dev/null +++ b/tests/components/scd4x/test.esp32-c3-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 5 + sda: 4 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.esp32-c3.yaml b/tests/components/scd4x/test.esp32-c3.yaml new file mode 100644 index 000000000000..353293be65df --- /dev/null +++ b/tests/components/scd4x/test.esp32-c3.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 5 + sda: 4 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.esp32-idf.yaml b/tests/components/scd4x/test.esp32-idf.yaml new file mode 100644 index 000000000000..02cec921d2a7 --- /dev/null +++ b/tests/components/scd4x/test.esp32-idf.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 16 + sda: 17 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.esp32.yaml b/tests/components/scd4x/test.esp32.yaml new file mode 100644 index 000000000000..02cec921d2a7 --- /dev/null +++ b/tests/components/scd4x/test.esp32.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 16 + sda: 17 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.esp8266.yaml b/tests/components/scd4x/test.esp8266.yaml new file mode 100644 index 000000000000..353293be65df --- /dev/null +++ b/tests/components/scd4x/test.esp8266.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 5 + sda: 4 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/scd4x/test.rp2040.yaml b/tests/components/scd4x/test.rp2040.yaml new file mode 100644 index 000000000000..353293be65df --- /dev/null +++ b/tests/components/scd4x/test.rp2040.yaml @@ -0,0 +1,20 @@ +i2c: + - id: i2c_scd4x + scl: 5 + sda: 4 + +sensor: + - platform: scd4x + id: scd40 + co2: + name: SCD4X CO2 + temperature: + id: scd4x_temperature + name: SCD4X Temperature + humidity: + name: SCD4X Humidity + automatic_self_calibration: true + altitude_compensation: 10m + ambient_pressure_compensation: 961mBar + temperature_offset: 4.2C + update_interval: 15s diff --git a/tests/components/script/common.yaml b/tests/components/script/common.yaml new file mode 100644 index 000000000000..bfd5d0e7ffc1 --- /dev/null +++ b/tests/components/script/common.yaml @@ -0,0 +1,55 @@ +esphome: + on_boot: + then: + - script.execute: my_script + - script.execute: + id: my_script_with_params + prefix: "Test" + param2: 0 + param3: true + - script.wait: my_script + - script.stop: my_script + - if: + condition: + - script.is_running: my_script + then: + - logger.log: my_script is running + +script: + - id: my_script + mode: single + then: + - lambda: 'ESP_LOGD("main", "Hello World!");' + - id: my_script_queued + mode: queued + max_runs: 2 + then: + - lambda: 'ESP_LOGD("main", "Hello World!");' + - id: my_script_parallel + mode: parallel + max_runs: 2 + then: + - lambda: 'ESP_LOGD("main", "Hello World!");' + - id: my_script_restart + mode: restart + then: + - lambda: 'ESP_LOGD("main", "Hello World!");' + - id: my_script_with_params + parameters: + prefix: string + param2: uint8_t + param3: bool + then: + - lambda: 'ESP_LOGD(prefix.c_str(), "Hello World! %u %u", param2, param3);' + - if: + condition: + for: + time: !lambda "return param2;" + condition: + script.is_running: my_script + then: + - lambda: 'ESP_LOGD("main", "API has stayed connected for at least %u minutes", param2);' + - repeat: + count: 5 + then: + - logger.log: looping! diff --git a/tests/components/script/test.bk72xx.yaml b/tests/components/script/test.bk72xx.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.bk72xx.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.esp32-c3-idf.yaml b/tests/components/script/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.esp32-c3.yaml b/tests/components/script/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.esp32-idf.yaml b/tests/components/script/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.esp32.yaml b/tests/components/script/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.esp8266.yaml b/tests/components/script/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/script/test.rp2040.yaml b/tests/components/script/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/script/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..0c2144f98349 --- /dev/null +++ b/tests/components/sdm_meter/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdm_meter/test.esp32-c3.yaml b/tests/components/sdm_meter/test.esp32-c3.yaml new file mode 100644 index 000000000000..0c2144f98349 --- /dev/null +++ b/tests/components/sdm_meter/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdm_meter/test.esp32-idf.yaml b/tests/components/sdm_meter/test.esp32-idf.yaml new file mode 100644 index 000000000000..eb3958db1957 --- /dev/null +++ b/tests/components/sdm_meter/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdm_meter/test.esp32.yaml b/tests/components/sdm_meter/test.esp32.yaml new file mode 100644 index 000000000000..eb3958db1957 --- /dev/null +++ b/tests/components/sdm_meter/test.esp32.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdm_meter/test.esp8266.yaml b/tests/components/sdm_meter/test.esp8266.yaml new file mode 100644 index 000000000000..0c2144f98349 --- /dev/null +++ b/tests/components/sdm_meter/test.esp8266.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdm_meter/test.rp2040.yaml b/tests/components/sdm_meter/test.rp2040.yaml new file mode 100644 index 000000000000..0c2144f98349 --- /dev/null +++ b/tests/components/sdm_meter/test.rp2040.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sdm_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sdm_meter + phase_a: + current: + name: Phase A Current + voltage: + name: Phase A Voltage + active_power: + name: Phase A Power + power_factor: + name: Phase A Power Factor + apparent_power: + name: Phase A Apparent Power + reactive_power: + name: Phase A Reactive Power + phase_angle: + name: Phase A Phase Angle diff --git a/tests/components/sdp3x/test.esp32-c3-idf.yaml b/tests/components/sdp3x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..42b90f1b8144 --- /dev/null +++ b/tests/components/sdp3x/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 5 + sda: 4 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sdp3x/test.esp32-c3.yaml b/tests/components/sdp3x/test.esp32-c3.yaml new file mode 100644 index 000000000000..42b90f1b8144 --- /dev/null +++ b/tests/components/sdp3x/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 5 + sda: 4 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sdp3x/test.esp32-idf.yaml b/tests/components/sdp3x/test.esp32-idf.yaml new file mode 100644 index 000000000000..00666082eb33 --- /dev/null +++ b/tests/components/sdp3x/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 16 + sda: 17 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sdp3x/test.esp32.yaml b/tests/components/sdp3x/test.esp32.yaml new file mode 100644 index 000000000000..00666082eb33 --- /dev/null +++ b/tests/components/sdp3x/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 16 + sda: 17 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sdp3x/test.esp8266.yaml b/tests/components/sdp3x/test.esp8266.yaml new file mode 100644 index 000000000000..42b90f1b8144 --- /dev/null +++ b/tests/components/sdp3x/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 5 + sda: 4 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sdp3x/test.rp2040.yaml b/tests/components/sdp3x/test.rp2040.yaml new file mode 100644 index 000000000000..42b90f1b8144 --- /dev/null +++ b/tests/components/sdp3x/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_sdp3x + scl: 5 + sda: 4 + +sensor: + - platform: sdp3x + id: filter_pressure + name: HVAC Filter Pressure drop + accuracy_decimals: 3 + update_interval: 5s diff --git a/tests/components/sds011/test.esp32-c3-idf.yaml b/tests/components/sds011/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e680a62dfe54 --- /dev/null +++ b/tests/components/sds011/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/sds011/test.esp32-c3.yaml b/tests/components/sds011/test.esp32-c3.yaml new file mode 100644 index 000000000000..e680a62dfe54 --- /dev/null +++ b/tests/components/sds011/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/sds011/test.esp32-idf.yaml b/tests/components/sds011/test.esp32-idf.yaml new file mode 100644 index 000000000000..275390f14c75 --- /dev/null +++ b/tests/components/sds011/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/sds011/test.esp32.yaml b/tests/components/sds011/test.esp32.yaml new file mode 100644 index 000000000000..275390f14c75 --- /dev/null +++ b/tests/components/sds011/test.esp32.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/sds011/test.esp8266.yaml b/tests/components/sds011/test.esp8266.yaml new file mode 100644 index 000000000000..e680a62dfe54 --- /dev/null +++ b/tests/components/sds011/test.esp8266.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/sds011/test.rp2040.yaml b/tests/components/sds011/test.rp2040.yaml new file mode 100644 index 000000000000..e680a62dfe54 --- /dev/null +++ b/tests/components/sds011/test.rp2040.yaml @@ -0,0 +1,14 @@ +uart: + - id: uart_sdm_sds011 + tx_pin: 4 + rx_pin: 5 + baud_rate: 115200 + +sensor: + - platform: sds011 + pm_2_5: + name: SDS011 PM2.5 + pm_10_0: + name: SDS011 PM10.0 + rx_only: false + update_interval: 5min diff --git a/tests/components/seeed_mr24hpc1/common.yaml b/tests/components/seeed_mr24hpc1/common.yaml new file mode 100644 index 000000000000..38692b3e5e3d --- /dev/null +++ b/tests/components/seeed_mr24hpc1/common.yaml @@ -0,0 +1,92 @@ +uart: + - id: seeed_mr24hpc1_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr24hpc1: + id: my_seeed_mr24hpc1 + uart_id: seeed_mr24hpc1_uart + +sensor: + - platform: seeed_mr24hpc1 + custom_presence_of_detection: + name: "Static Distance" + movement_signs: + name: "Body Movement Parameter" + custom_motion_distance: + name: "Motion Distance" + custom_spatial_static_value: + name: "Existence Energy" + custom_spatial_motion_value: + name: "Motion Energy" + custom_motion_speed: + name: "Motion Speed" + custom_mode_num: + name: "Current Custom Mode" + +binary_sensor: + - platform: seeed_mr24hpc1 + has_target: + name: "Presence Information" + +switch: + - platform: seeed_mr24hpc1 + underlying_open_function: + name: Underlying Open Function Info Output Switch + +text_sensor: + - platform: seeed_mr24hpc1 + heart_beat: + name: "Heartbeat" + product_model: + name: "Product Model" + product_id: + name: "Product ID" + hardware_model: + name: "Hardware Model" + hardware_version: + name: "Hardware Version" + keep_away: + name: "Active Reporting Of Proximity" + motion_status: + name: "Motion Information" + custom_mode_end: + name: "Custom Mode Status" + +number: + - platform: seeed_mr24hpc1 + sensitivity: + name: "Sensitivity" + custom_mode: + name: "Custom Mode" + existence_threshold: + name: "Existence Energy Threshold" + motion_threshold: + name: "Motion Energy Threshold" + motion_trigger: + name: "Motion Trigger Time" + motion_to_rest: + name: "Motion To Rest Time" + custom_unman_time: + name: "Time For Entering No Person State (Underlying Open Function)" + +select: + - platform: seeed_mr24hpc1 + scene_mode: + name: "Scene" + unman_time: + name: "Time For Entering No Person State (Standard Function)" + existence_boundary: + name: "Existence Boundary" + motion_boundary: + name: "Motion Boundary" + +button: + - platform: seeed_mr24hpc1 + restart: + name: "Module Restart" + custom_set_end: + name: "End Of Custom Mode Settings" diff --git a/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml b/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4fb884abf41d --- /dev/null +++ b/tests/components/seeed_mr24hpc1/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml b/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml new file mode 100644 index 000000000000..4fb884abf41d --- /dev/null +++ b/tests/components/seeed_mr24hpc1/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !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 new file mode 100644 index 000000000000..5f6e69f96f6f --- /dev/null +++ b/tests/components/selec_meter/test.esp32-c3-idf.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/selec_meter/test.esp32-c3.yaml b/tests/components/selec_meter/test.esp32-c3.yaml new file mode 100644 index 000000000000..5f6e69f96f6f --- /dev/null +++ b/tests/components/selec_meter/test.esp32-c3.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/selec_meter/test.esp32-idf.yaml b/tests/components/selec_meter/test.esp32-idf.yaml new file mode 100644 index 000000000000..648adc175785 --- /dev/null +++ b/tests/components/selec_meter/test.esp32-idf.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/selec_meter/test.esp32.yaml b/tests/components/selec_meter/test.esp32.yaml new file mode 100644 index 000000000000..648adc175785 --- /dev/null +++ b/tests/components/selec_meter/test.esp32.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/selec_meter/test.esp8266.yaml b/tests/components/selec_meter/test.esp8266.yaml new file mode 100644 index 000000000000..5f6e69f96f6f --- /dev/null +++ b/tests/components/selec_meter/test.esp8266.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/selec_meter/test.rp2040.yaml b/tests/components/selec_meter/test.rp2040.yaml new file mode 100644 index 000000000000..5f6e69f96f6f --- /dev/null +++ b/tests/components/selec_meter/test.rp2040.yaml @@ -0,0 +1,45 @@ +uart: + - id: uart_selec_meter + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true diff --git a/tests/components/sen0321/test.esp32-c3-idf.yaml b/tests/components/sen0321/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7fa461a7fa5c --- /dev/null +++ b/tests/components/sen0321/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 5 + sda: 4 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen0321/test.esp32-c3.yaml b/tests/components/sen0321/test.esp32-c3.yaml new file mode 100644 index 000000000000..7fa461a7fa5c --- /dev/null +++ b/tests/components/sen0321/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 5 + sda: 4 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen0321/test.esp32-idf.yaml b/tests/components/sen0321/test.esp32-idf.yaml new file mode 100644 index 000000000000..44f76bf5e641 --- /dev/null +++ b/tests/components/sen0321/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 16 + sda: 17 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen0321/test.esp32.yaml b/tests/components/sen0321/test.esp32.yaml new file mode 100644 index 000000000000..44f76bf5e641 --- /dev/null +++ b/tests/components/sen0321/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 16 + sda: 17 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen0321/test.esp8266.yaml b/tests/components/sen0321/test.esp8266.yaml new file mode 100644 index 000000000000..7fa461a7fa5c --- /dev/null +++ b/tests/components/sen0321/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 5 + sda: 4 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen0321/test.rp2040.yaml b/tests/components/sen0321/test.rp2040.yaml new file mode 100644 index 000000000000..7fa461a7fa5c --- /dev/null +++ b/tests/components/sen0321/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sen0321 + scl: 5 + sda: 4 + +sensor: + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s diff --git a/tests/components/sen21231/test.esp32-c3-idf.yaml b/tests/components/sen21231/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..efd7843f56c8 --- /dev/null +++ b/tests/components/sen21231/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 5 + sda: 4 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen21231/test.esp32-c3.yaml b/tests/components/sen21231/test.esp32-c3.yaml new file mode 100644 index 000000000000..efd7843f56c8 --- /dev/null +++ b/tests/components/sen21231/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 5 + sda: 4 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen21231/test.esp32-idf.yaml b/tests/components/sen21231/test.esp32-idf.yaml new file mode 100644 index 000000000000..3173683f17b2 --- /dev/null +++ b/tests/components/sen21231/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 16 + sda: 17 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen21231/test.esp32.yaml b/tests/components/sen21231/test.esp32.yaml new file mode 100644 index 000000000000..3173683f17b2 --- /dev/null +++ b/tests/components/sen21231/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 16 + sda: 17 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen21231/test.esp8266.yaml b/tests/components/sen21231/test.esp8266.yaml new file mode 100644 index 000000000000..efd7843f56c8 --- /dev/null +++ b/tests/components/sen21231/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 5 + sda: 4 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen21231/test.rp2040.yaml b/tests/components/sen21231/test.rp2040.yaml new file mode 100644 index 000000000000..efd7843f56c8 --- /dev/null +++ b/tests/components/sen21231/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_sen21231 + scl: 5 + sda: 4 + +sensor: + - platform: sen21231 + id: sen21231_sensor1 + name: Person Sensor diff --git a/tests/components/sen5x/test.esp32-c3-idf.yaml b/tests/components/sen5x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3352a59b1725 --- /dev/null +++ b/tests/components/sen5x/test.esp32-c3-idf.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 5 + sda: 4 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.esp32-c3.yaml b/tests/components/sen5x/test.esp32-c3.yaml new file mode 100644 index 000000000000..3352a59b1725 --- /dev/null +++ b/tests/components/sen5x/test.esp32-c3.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 5 + sda: 4 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.esp32-idf.yaml b/tests/components/sen5x/test.esp32-idf.yaml new file mode 100644 index 000000000000..b8f89c435f40 --- /dev/null +++ b/tests/components/sen5x/test.esp32-idf.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 16 + sda: 17 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.esp32.yaml b/tests/components/sen5x/test.esp32.yaml new file mode 100644 index 000000000000..b8f89c435f40 --- /dev/null +++ b/tests/components/sen5x/test.esp32.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 16 + sda: 17 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.esp8266.yaml b/tests/components/sen5x/test.esp8266.yaml new file mode 100644 index 000000000000..3352a59b1725 --- /dev/null +++ b/tests/components/sen5x/test.esp8266.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 5 + sda: 4 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/sen5x/test.rp2040.yaml b/tests/components/sen5x/test.rp2040.yaml new file mode 100644 index 000000000000..3352a59b1725 --- /dev/null +++ b/tests/components/sen5x/test.rp2040.yaml @@ -0,0 +1,49 @@ +i2c: + - id: i2c_sen5x + scl: 5 + sda: 4 + +sensor: + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 diff --git a/tests/components/senseair/test.esp32-c3-idf.yaml b/tests/components/senseair/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..41a441f4961b --- /dev/null +++ b/tests/components/senseair/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/senseair/test.esp32-c3.yaml b/tests/components/senseair/test.esp32-c3.yaml new file mode 100644 index 000000000000..41a441f4961b --- /dev/null +++ b/tests/components/senseair/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/senseair/test.esp32-idf.yaml b/tests/components/senseair/test.esp32-idf.yaml new file mode 100644 index 000000000000..daa4645f5976 --- /dev/null +++ b/tests/components/senseair/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/senseair/test.esp32.yaml b/tests/components/senseair/test.esp32.yaml new file mode 100644 index 000000000000..daa4645f5976 --- /dev/null +++ b/tests/components/senseair/test.esp32.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/senseair/test.esp8266.yaml b/tests/components/senseair/test.esp8266.yaml new file mode 100644 index 000000000000..41a441f4961b --- /dev/null +++ b/tests/components/senseair/test.esp8266.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/senseair/test.rp2040.yaml b/tests/components/senseair/test.rp2040.yaml new file mode 100644 index 000000000000..41a441f4961b --- /dev/null +++ b/tests/components/senseair/test.rp2040.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_senseair + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: senseair + id: senseair0 + co2: + name: SenseAir CO2 Value + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s diff --git a/tests/components/servo/test.esp32-c3-idf.yaml b/tests/components/servo/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..29ebea3359b4 --- /dev/null +++ b/tests/components/servo/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: ledc + id: servo_output_1 + pin: 1 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/servo/test.esp32-c3.yaml b/tests/components/servo/test.esp32-c3.yaml new file mode 100644 index 000000000000..29ebea3359b4 --- /dev/null +++ b/tests/components/servo/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: ledc + id: servo_output_1 + pin: 1 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/servo/test.esp32-idf.yaml b/tests/components/servo/test.esp32-idf.yaml new file mode 100644 index 000000000000..e769f055b43b --- /dev/null +++ b/tests/components/servo/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: ledc + id: servo_output_1 + pin: 12 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/servo/test.esp32.yaml b/tests/components/servo/test.esp32.yaml new file mode 100644 index 000000000000..e769f055b43b --- /dev/null +++ b/tests/components/servo/test.esp32.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: ledc + id: servo_output_1 + pin: 12 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/servo/test.esp8266.yaml b/tests/components/servo/test.esp8266.yaml new file mode 100644 index 000000000000..48b4421641cd --- /dev/null +++ b/tests/components/servo/test.esp8266.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: esp8266_pwm + id: servo_output_1 + pin: 12 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/servo/test.rp2040.yaml b/tests/components/servo/test.rp2040.yaml new file mode 100644 index 000000000000..75efa9407e9a --- /dev/null +++ b/tests/components/servo/test.rp2040.yaml @@ -0,0 +1,19 @@ +esphome: + on_boot: + then: + - servo.write: + id: test_servo + level: -100.0% + - servo.detach: test_servo + +output: + - platform: rp2040_pwm + id: servo_output_1 + pin: 12 + +servo: + id: test_servo + output: servo_output_1 + restore: true + min_level: 4% + max_level: 8% diff --git a/tests/components/sfa30/test.esp32-c3-idf.yaml b/tests/components/sfa30/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..119059e4e275 --- /dev/null +++ b/tests/components/sfa30/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 5 + sda: 4 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sfa30/test.esp32-c3.yaml b/tests/components/sfa30/test.esp32-c3.yaml new file mode 100644 index 000000000000..119059e4e275 --- /dev/null +++ b/tests/components/sfa30/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 5 + sda: 4 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sfa30/test.esp32-idf.yaml b/tests/components/sfa30/test.esp32-idf.yaml new file mode 100644 index 000000000000..dc7e4988e5f4 --- /dev/null +++ b/tests/components/sfa30/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 16 + sda: 17 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sfa30/test.esp32.yaml b/tests/components/sfa30/test.esp32.yaml new file mode 100644 index 000000000000..dc7e4988e5f4 --- /dev/null +++ b/tests/components/sfa30/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 16 + sda: 17 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sfa30/test.esp8266.yaml b/tests/components/sfa30/test.esp8266.yaml new file mode 100644 index 000000000000..119059e4e275 --- /dev/null +++ b/tests/components/sfa30/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 5 + sda: 4 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sfa30/test.rp2040.yaml b/tests/components/sfa30/test.rp2040.yaml new file mode 100644 index 000000000000..119059e4e275 --- /dev/null +++ b/tests/components/sfa30/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sfa30 + scl: 5 + sda: 4 + +sensor: + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + address: 0x5D + update_interval: 30s diff --git a/tests/components/sgp30/test.esp32-c3-idf.yaml b/tests/components/sgp30/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..45de67e94bdc --- /dev/null +++ b/tests/components/sgp30/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 5 + sda: 4 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp30/test.esp32-c3.yaml b/tests/components/sgp30/test.esp32-c3.yaml new file mode 100644 index 000000000000..45de67e94bdc --- /dev/null +++ b/tests/components/sgp30/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 5 + sda: 4 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp30/test.esp32-idf.yaml b/tests/components/sgp30/test.esp32-idf.yaml new file mode 100644 index 000000000000..6ea23c25cdaf --- /dev/null +++ b/tests/components/sgp30/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 16 + sda: 17 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp30/test.esp32.yaml b/tests/components/sgp30/test.esp32.yaml new file mode 100644 index 000000000000..6ea23c25cdaf --- /dev/null +++ b/tests/components/sgp30/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 16 + sda: 17 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp30/test.esp8266.yaml b/tests/components/sgp30/test.esp8266.yaml new file mode 100644 index 000000000000..45de67e94bdc --- /dev/null +++ b/tests/components/sgp30/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 5 + sda: 4 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp30/test.rp2040.yaml b/tests/components/sgp30/test.rp2040.yaml new file mode 100644 index 000000000000..45de67e94bdc --- /dev/null +++ b/tests/components/sgp30/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sgp30 + scl: 5 + sda: 4 + +sensor: + - platform: sgp30 + eco2: + name: Workshop eCO2 + accuracy_decimals: 1 + tvoc: + name: Workshop TVOC + accuracy_decimals: 1 + address: 0x58 + update_interval: 5s diff --git a/tests/components/sgp4x/test.esp32-c3-idf.yaml b/tests/components/sgp4x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b2876478bd08 --- /dev/null +++ b/tests/components/sgp4x/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 5 + sda: 4 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/sgp4x/test.esp32-c3.yaml b/tests/components/sgp4x/test.esp32-c3.yaml new file mode 100644 index 000000000000..b2876478bd08 --- /dev/null +++ b/tests/components/sgp4x/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 5 + sda: 4 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/sgp4x/test.esp32-idf.yaml b/tests/components/sgp4x/test.esp32-idf.yaml new file mode 100644 index 000000000000..c7380b5a103a --- /dev/null +++ b/tests/components/sgp4x/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 16 + sda: 17 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/sgp4x/test.esp32.yaml b/tests/components/sgp4x/test.esp32.yaml new file mode 100644 index 000000000000..c7380b5a103a --- /dev/null +++ b/tests/components/sgp4x/test.esp32.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 16 + sda: 17 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/sgp4x/test.esp8266.yaml b/tests/components/sgp4x/test.esp8266.yaml new file mode 100644 index 000000000000..b2876478bd08 --- /dev/null +++ b/tests/components/sgp4x/test.esp8266.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 5 + sda: 4 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/sgp4x/test.rp2040.yaml b/tests/components/sgp4x/test.rp2040.yaml new file mode 100644 index 000000000000..b2876478bd08 --- /dev/null +++ b/tests/components/sgp4x/test.rp2040.yaml @@ -0,0 +1,27 @@ +i2c: + - id: i2c_sgp4x + scl: 5 + sda: 4 + +sensor: + - platform: sgp4x + voc: + name: VOC Index + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: NOx + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + update_interval: 5s diff --git a/tests/components/shelly_dimmer/common.yaml b/tests/components/shelly_dimmer/common.yaml new file mode 100644 index 000000000000..3acd0260d59f --- /dev/null +++ b/tests/components/shelly_dimmer/common.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_shelly_dimmer + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +light: + - platform: shelly_dimmer + name: Shelly Dimmer Light + power: + name: Shelly Dimmer Power + voltage: + name: Shelly Dimmer Voltage + current: + name: Shelly Dimmer Current + max_brightness: 500 + firmware: "51.6" + nrst_pin: 13 + boot0_pin: 14 diff --git a/tests/components/shelly_dimmer/test.esp8266.yaml b/tests/components/shelly_dimmer/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shelly_dimmer/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sht3xd/test.esp32-c3-idf.yaml b/tests/components/sht3xd/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0409ff65c6e8 --- /dev/null +++ b/tests/components/sht3xd/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 5 + sda: 4 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht3xd/test.esp32-c3.yaml b/tests/components/sht3xd/test.esp32-c3.yaml new file mode 100644 index 000000000000..0409ff65c6e8 --- /dev/null +++ b/tests/components/sht3xd/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 5 + sda: 4 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht3xd/test.esp32-idf.yaml b/tests/components/sht3xd/test.esp32-idf.yaml new file mode 100644 index 000000000000..2b6ee50760e8 --- /dev/null +++ b/tests/components/sht3xd/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 16 + sda: 17 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht3xd/test.esp32.yaml b/tests/components/sht3xd/test.esp32.yaml new file mode 100644 index 000000000000..2b6ee50760e8 --- /dev/null +++ b/tests/components/sht3xd/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 16 + sda: 17 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht3xd/test.esp8266.yaml b/tests/components/sht3xd/test.esp8266.yaml new file mode 100644 index 000000000000..0409ff65c6e8 --- /dev/null +++ b/tests/components/sht3xd/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 5 + sda: 4 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht3xd/test.rp2040.yaml b/tests/components/sht3xd/test.rp2040.yaml new file mode 100644 index 000000000000..0409ff65c6e8 --- /dev/null +++ b/tests/components/sht3xd/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht3xd + scl: 5 + sda: 4 + +sensor: + - platform: sht3xd + temperature: + name: SHT3XD Temperature + humidity: + name: SHT3XD Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.esp32-c3-idf.yaml b/tests/components/sht4x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0bcdd864f68d --- /dev/null +++ b/tests/components/sht4x/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 5 + sda: 4 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.esp32-c3.yaml b/tests/components/sht4x/test.esp32-c3.yaml new file mode 100644 index 000000000000..0bcdd864f68d --- /dev/null +++ b/tests/components/sht4x/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 5 + sda: 4 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.esp32-idf.yaml b/tests/components/sht4x/test.esp32-idf.yaml new file mode 100644 index 000000000000..13ec524d7dfc --- /dev/null +++ b/tests/components/sht4x/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 16 + sda: 17 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.esp32.yaml b/tests/components/sht4x/test.esp32.yaml new file mode 100644 index 000000000000..13ec524d7dfc --- /dev/null +++ b/tests/components/sht4x/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 16 + sda: 17 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.esp8266.yaml b/tests/components/sht4x/test.esp8266.yaml new file mode 100644 index 000000000000..0bcdd864f68d --- /dev/null +++ b/tests/components/sht4x/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 5 + sda: 4 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/sht4x/test.rp2040.yaml b/tests/components/sht4x/test.rp2040.yaml new file mode 100644 index 000000000000..0bcdd864f68d --- /dev/null +++ b/tests/components/sht4x/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_sht4x + scl: 5 + sda: 4 + +sensor: + - platform: sht4x + temperature: + name: SHT4X Temperature + humidity: + name: SHT4X Humidity + address: 0x44 + update_interval: 15s diff --git a/tests/components/shtcx/test.esp32-c3-idf.yaml b/tests/components/shtcx/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c1c7a2a63f97 --- /dev/null +++ b/tests/components/shtcx/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 5 + sda: 4 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shtcx/test.esp32-c3.yaml b/tests/components/shtcx/test.esp32-c3.yaml new file mode 100644 index 000000000000..c1c7a2a63f97 --- /dev/null +++ b/tests/components/shtcx/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 5 + sda: 4 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shtcx/test.esp32-idf.yaml b/tests/components/shtcx/test.esp32-idf.yaml new file mode 100644 index 000000000000..619bac9548fc --- /dev/null +++ b/tests/components/shtcx/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 16 + sda: 17 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shtcx/test.esp32.yaml b/tests/components/shtcx/test.esp32.yaml new file mode 100644 index 000000000000..619bac9548fc --- /dev/null +++ b/tests/components/shtcx/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 16 + sda: 17 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shtcx/test.esp8266.yaml b/tests/components/shtcx/test.esp8266.yaml new file mode 100644 index 000000000000..c1c7a2a63f97 --- /dev/null +++ b/tests/components/shtcx/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 5 + sda: 4 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shtcx/test.rp2040.yaml b/tests/components/shtcx/test.rp2040.yaml new file mode 100644 index 000000000000..c1c7a2a63f97 --- /dev/null +++ b/tests/components/shtcx/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_shtcx + scl: 5 + sda: 4 + +sensor: + - platform: shtcx + temperature: + name: SHTCX Temperature + humidity: + name: SHTCX Humidity + address: 0x70 + update_interval: 15s diff --git a/tests/components/shutdown/common.yaml b/tests/components/shutdown/common.yaml new file mode 100644 index 000000000000..f47e7da85de8 --- /dev/null +++ b/tests/components/shutdown/common.yaml @@ -0,0 +1,7 @@ +button: + - platform: shutdown + name: Shutdown Button + +switch: + - platform: shutdown + name: Shutdown Switch diff --git a/tests/components/shutdown/test.esp32-c3-idf.yaml b/tests/components/shutdown/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/shutdown/test.esp32-c3.yaml b/tests/components/shutdown/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/shutdown/test.esp32-idf.yaml b/tests/components/shutdown/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/shutdown/test.esp32.yaml b/tests/components/shutdown/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/shutdown/test.esp8266.yaml b/tests/components/shutdown/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/shutdown/test.rp2040.yaml b/tests/components/shutdown/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/shutdown/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/common.yaml b/tests/components/sigma_delta_output/common.yaml new file mode 100644 index 000000000000..2a9a5d2c3b35 --- /dev/null +++ b/tests/components/sigma_delta_output/common.yaml @@ -0,0 +1,16 @@ +output: + - platform: sigma_delta_output + id: sddac + pin: 4 + turn_on_action: + then: + - logger.log: "Turned on" + turn_off_action: + then: + - logger.log: "Turned off" + state_change_action: + then: + - logger.log: + format: "Changed state: %d" + args: ["state"] + update_interval: 60s diff --git a/tests/components/sigma_delta_output/test.esp32-c3-idf.yaml b/tests/components/sigma_delta_output/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/test.esp32-c3.yaml b/tests/components/sigma_delta_output/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/test.esp32-idf.yaml b/tests/components/sigma_delta_output/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/test.esp32.yaml b/tests/components/sigma_delta_output/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/test.esp8266.yaml b/tests/components/sigma_delta_output/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sigma_delta_output/test.rp2040.yaml b/tests/components/sigma_delta_output/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sigma_delta_output/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sim800l/test.esp32-c3-idf.yaml b/tests/components/sim800l/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..7ff359d1e7d9 --- /dev/null +++ b/tests/components/sim800l/test.esp32-c3-idf.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/sim800l/test.esp32-c3.yaml b/tests/components/sim800l/test.esp32-c3.yaml new file mode 100644 index 000000000000..7ff359d1e7d9 --- /dev/null +++ b/tests/components/sim800l/test.esp32-c3.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/sim800l/test.esp32-idf.yaml b/tests/components/sim800l/test.esp32-idf.yaml new file mode 100644 index 000000000000..c116548c6fd2 --- /dev/null +++ b/tests/components/sim800l/test.esp32-idf.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/sim800l/test.esp32.yaml b/tests/components/sim800l/test.esp32.yaml new file mode 100644 index 000000000000..c116548c6fd2 --- /dev/null +++ b/tests/components/sim800l/test.esp32.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/sim800l/test.esp8266.yaml b/tests/components/sim800l/test.esp8266.yaml new file mode 100644 index 000000000000..7ff359d1e7d9 --- /dev/null +++ b/tests/components/sim800l/test.esp8266.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/sim800l/test.rp2040.yaml b/tests/components/sim800l/test.rp2040.yaml new file mode 100644 index 000000000000..7ff359d1e7d9 --- /dev/null +++ b/tests/components/sim800l/test.rp2040.yaml @@ -0,0 +1,37 @@ +esphome: + on_boot: + then: + - sim800l.send_sms: + recipient: '+15551234567' + message: Hello there + - sim800l.dial: + recipient: '+15551234567' + - sim800l.connect + - sim800l.disconnect + - sim800l.send_ussd: + ussd: test_ussd + +uart: + - id: uart_sim800l + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sim800l: + on_sms_received: + - lambda: |- + std::string str; + str = sender; + str = message; + - sim800l.send_sms: + message: hello you + recipient: "+1234" + - sim800l.dial: + recipient: "+1234" + on_incoming_call: + - logger.log: + format: "Incoming call from '%s'" + args: ["caller_id.c_str()"] + - sim800l.disconnect + on_ussd_received: + - logger.log: "ussd_received" diff --git a/tests/components/slow_pwm/common.yaml b/tests/components/slow_pwm/common.yaml new file mode 100644 index 000000000000..6bfb2f8ac547 --- /dev/null +++ b/tests/components/slow_pwm/common.yaml @@ -0,0 +1,6 @@ +output: + - platform: slow_pwm + id: test_slow_pwm + pin: 4 + period: 15s + restart_cycle_on_state_change: false diff --git a/tests/components/slow_pwm/test.esp32-c3-idf.yaml b/tests/components/slow_pwm/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.esp32-c3.yaml b/tests/components/slow_pwm/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.esp32-idf.yaml b/tests/components/slow_pwm/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.esp32.yaml b/tests/components/slow_pwm/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.esp8266.yaml b/tests/components/slow_pwm/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/slow_pwm/test.rp2040.yaml b/tests/components/slow_pwm/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/slow_pwm/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/common.yaml b/tests/components/sm16716/common.yaml new file mode 100644 index 000000000000..3bf2712f4edc --- /dev/null +++ b/tests/components/sm16716/common.yaml @@ -0,0 +1,16 @@ +sm16716: + clock_pin: 4 + data_pin: 5 + num_channels: 3 + num_chips: 1 + +output: + - platform: sm16716 + id: sm16716_red + channel: 1 + - platform: sm16716 + id: sm16716_green + channel: 0 + - platform: sm16716 + id: sm16716_blue + channel: 2 diff --git a/tests/components/sm16716/test.esp32-c3-idf.yaml b/tests/components/sm16716/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/test.esp32-c3.yaml b/tests/components/sm16716/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/test.esp32-idf.yaml b/tests/components/sm16716/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/test.esp32.yaml b/tests/components/sm16716/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/test.esp8266.yaml b/tests/components/sm16716/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm16716/test.rp2040.yaml b/tests/components/sm16716/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm16716/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/common.yaml b/tests/components/sm2135/common.yaml new file mode 100644 index 000000000000..9a0de6083962 --- /dev/null +++ b/tests/components/sm2135/common.yaml @@ -0,0 +1,22 @@ +sm2135: + clock_pin: 4 + data_pin: 5 + rgb_current: 20mA + cw_current: 60mA + +output: + - platform: sm2135 + id: sm2135_0 + channel: 0 + - platform: sm2135 + id: sm2135_1 + channel: 1 + - platform: sm2135 + id: sm2135_2 + channel: 2 + - platform: sm2135 + id: sm2135_3 + channel: 3 + - platform: sm2135 + id: sm2135_4 + channel: 4 diff --git a/tests/components/sm2135/test.esp32-c3-idf.yaml b/tests/components/sm2135/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/test.esp32-c3.yaml b/tests/components/sm2135/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/test.esp32-idf.yaml b/tests/components/sm2135/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/test.esp32.yaml b/tests/components/sm2135/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/test.esp8266.yaml b/tests/components/sm2135/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2135/test.rp2040.yaml b/tests/components/sm2135/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2135/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/common.yaml b/tests/components/sm2235/common.yaml new file mode 100644 index 000000000000..043d43d6f15e --- /dev/null +++ b/tests/components/sm2235/common.yaml @@ -0,0 +1,22 @@ +sm2235: + clock_pin: 4 + data_pin: 5 + max_power_color_channels: 9 + max_power_white_channels: 9 + +output: + - platform: sm2235 + id: sm2235_red + channel: 1 + - platform: sm2235 + id: sm2235_green + channel: 0 + - platform: sm2235 + id: sm2235_blue + channel: 2 + - platform: sm2235 + id: sm2235_coldwhite + channel: 4 + - platform: sm2235 + id: sm2235_warmwhite + channel: 3 diff --git a/tests/components/sm2235/test.esp32-c3-idf.yaml b/tests/components/sm2235/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/test.esp32-c3.yaml b/tests/components/sm2235/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/test.esp32-idf.yaml b/tests/components/sm2235/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/test.esp32.yaml b/tests/components/sm2235/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/test.esp8266.yaml b/tests/components/sm2235/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2235/test.rp2040.yaml b/tests/components/sm2235/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2235/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/common.yaml b/tests/components/sm2335/common.yaml new file mode 100644 index 000000000000..a5b2aedeb5ac --- /dev/null +++ b/tests/components/sm2335/common.yaml @@ -0,0 +1,22 @@ +sm2335: + clock_pin: 4 + data_pin: 5 + max_power_color_channels: 9 + max_power_white_channels: 9 + +output: + - platform: sm2335 + id: sm2335_red + channel: 1 + - platform: sm2335 + id: sm2335_green + channel: 0 + - platform: sm2335 + id: sm2335_blue + channel: 2 + - platform: sm2335 + id: sm2335_coldwhite + channel: 4 + - platform: sm2335 + id: sm2335_warmwhite + channel: 3 diff --git a/tests/components/sm2335/test.esp32-c3-idf.yaml b/tests/components/sm2335/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/test.esp32-c3.yaml b/tests/components/sm2335/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/test.esp32-idf.yaml b/tests/components/sm2335/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/test.esp32.yaml b/tests/components/sm2335/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/test.esp8266.yaml b/tests/components/sm2335/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm2335/test.rp2040.yaml b/tests/components/sm2335/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sm2335/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sm300d2/test.esp32-c3-idf.yaml b/tests/components/sm300d2/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..bcd0a728b2ae --- /dev/null +++ b/tests/components/sm300d2/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sm300d2/test.esp32-c3.yaml b/tests/components/sm300d2/test.esp32-c3.yaml new file mode 100644 index 000000000000..bcd0a728b2ae --- /dev/null +++ b/tests/components/sm300d2/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sm300d2/test.esp32-idf.yaml b/tests/components/sm300d2/test.esp32-idf.yaml new file mode 100644 index 000000000000..92dba4fb3bcc --- /dev/null +++ b/tests/components/sm300d2/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sm300d2/test.esp32.yaml b/tests/components/sm300d2/test.esp32.yaml new file mode 100644 index 000000000000..92dba4fb3bcc --- /dev/null +++ b/tests/components/sm300d2/test.esp32.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sm300d2/test.esp8266.yaml b/tests/components/sm300d2/test.esp8266.yaml new file mode 100644 index 000000000000..bcd0a728b2ae --- /dev/null +++ b/tests/components/sm300d2/test.esp8266.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sm300d2/test.rp2040.yaml b/tests/components/sm300d2/test.rp2040.yaml new file mode 100644 index 000000000000..bcd0a728b2ae --- /dev/null +++ b/tests/components/sm300d2/test.rp2040.yaml @@ -0,0 +1,23 @@ +uart: + - id: uart_sm300d2 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: sm300d2 + co2: + name: SM300D2 CO2 Value + formaldehyde: + name: SM300D2 Formaldehyde Value + tvoc: + name: SM300D2 TVOC Value + pm_2_5: + name: SM300D2 PM2.5 Value + pm_10_0: + name: SM300D2 PM10 Value + temperature: + name: SM300D2 Temperature Value + humidity: + name: SM300D2 Humidity Value + update_interval: 60s diff --git a/tests/components/sml/test.esp32-c3-idf.yaml b/tests/components/sml/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..903f968c266c --- /dev/null +++ b/tests/components/sml/test.esp32-c3-idf.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/sml/test.esp32-c3.yaml b/tests/components/sml/test.esp32-c3.yaml new file mode 100644 index 000000000000..903f968c266c --- /dev/null +++ b/tests/components/sml/test.esp32-c3.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/sml/test.esp32-idf.yaml b/tests/components/sml/test.esp32-idf.yaml new file mode 100644 index 000000000000..7217199380af --- /dev/null +++ b/tests/components/sml/test.esp32-idf.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/sml/test.esp32.yaml b/tests/components/sml/test.esp32.yaml new file mode 100644 index 000000000000..7217199380af --- /dev/null +++ b/tests/components/sml/test.esp32.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/sml/test.esp8266.yaml b/tests/components/sml/test.esp8266.yaml new file mode 100644 index 000000000000..903f968c266c --- /dev/null +++ b/tests/components/sml/test.esp8266.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/sml/test.rp2040.yaml b/tests/components/sml/test.rp2040.yaml new file mode 100644 index 000000000000..903f968c266c --- /dev/null +++ b/tests/components/sml/test.rp2040.yaml @@ -0,0 +1,31 @@ +uart: + - id: uart_sml + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sml: + id: mysml + on_data: + - logger.log: "SML on_data" + +sensor: + - platform: sml + name: Total energy + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "1-0:1.8.0" + unit_of_measurement: kWh + accuracy_decimals: 1 + device_class: energy + state_class: total_increasing + filters: + - multiply: 0.0001 + +text_sensor: + - platform: sml + name: Manufacturer + sml_id: mysml + server_id: 0123456789abcdef + obis_code: "129-129:199.130.3" + format: text diff --git a/tests/components/smt100/test.esp32-c3-idf.yaml b/tests/components/smt100/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4277f2567b63 --- /dev/null +++ b/tests/components/smt100/test.esp32-c3-idf.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/smt100/test.esp32-c3.yaml b/tests/components/smt100/test.esp32-c3.yaml new file mode 100644 index 000000000000..4277f2567b63 --- /dev/null +++ b/tests/components/smt100/test.esp32-c3.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/smt100/test.esp32-idf.yaml b/tests/components/smt100/test.esp32-idf.yaml new file mode 100644 index 000000000000..7c19f4bc45a2 --- /dev/null +++ b/tests/components/smt100/test.esp32-idf.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/smt100/test.esp32.yaml b/tests/components/smt100/test.esp32.yaml new file mode 100644 index 000000000000..7c19f4bc45a2 --- /dev/null +++ b/tests/components/smt100/test.esp32.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/smt100/test.esp8266.yaml b/tests/components/smt100/test.esp8266.yaml new file mode 100644 index 000000000000..4277f2567b63 --- /dev/null +++ b/tests/components/smt100/test.esp8266.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/smt100/test.rp2040.yaml b/tests/components/smt100/test.rp2040.yaml new file mode 100644 index 000000000000..4277f2567b63 --- /dev/null +++ b/tests/components/smt100/test.rp2040.yaml @@ -0,0 +1,19 @@ +uart: + - id: uart_smt100 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +sensor: + - platform: smt100 + counts: + name: Counts + dielectric_constant: + name: Dielectric Constant + temperature: + name: Temperature + moisture: + name: Moisture + voltage: + name: Voltage + update_interval: 60s diff --git a/tests/components/sn74hc165/test.esp32-c3-idf.yaml b/tests/components/sn74hc165/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f687b23c9d96 --- /dev/null +++ b/tests/components/sn74hc165/test.esp32-c3-idf.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 3 + data_pin: 4 + load_pin: 5 + clock_inhibit_pin: 6 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc165/test.esp32-c3.yaml b/tests/components/sn74hc165/test.esp32-c3.yaml new file mode 100644 index 000000000000..f687b23c9d96 --- /dev/null +++ b/tests/components/sn74hc165/test.esp32-c3.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 3 + data_pin: 4 + load_pin: 5 + clock_inhibit_pin: 6 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc165/test.esp32-idf.yaml b/tests/components/sn74hc165/test.esp32-idf.yaml new file mode 100644 index 000000000000..55b06aec9bd1 --- /dev/null +++ b/tests/components/sn74hc165/test.esp32-idf.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 13 + data_pin: 14 + load_pin: 15 + clock_inhibit_pin: 16 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc165/test.esp32.yaml b/tests/components/sn74hc165/test.esp32.yaml new file mode 100644 index 000000000000..55b06aec9bd1 --- /dev/null +++ b/tests/components/sn74hc165/test.esp32.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 13 + data_pin: 14 + load_pin: 15 + clock_inhibit_pin: 16 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc165/test.esp8266.yaml b/tests/components/sn74hc165/test.esp8266.yaml new file mode 100644 index 000000000000..55b06aec9bd1 --- /dev/null +++ b/tests/components/sn74hc165/test.esp8266.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 13 + data_pin: 14 + load_pin: 15 + clock_inhibit_pin: 16 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc165/test.rp2040.yaml b/tests/components/sn74hc165/test.rp2040.yaml new file mode 100644 index 000000000000..f687b23c9d96 --- /dev/null +++ b/tests/components/sn74hc165/test.rp2040.yaml @@ -0,0 +1,14 @@ +sn74hc165: + id: sn74hc165_hub + clock_pin: 3 + data_pin: 4 + load_pin: 5 + clock_inhibit_pin: 6 + sr_count: 2 + +binary_sensor: + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 diff --git a/tests/components/sn74hc595/test.esp32-c3-idf.yaml b/tests/components/sn74hc595/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9b093899d33d --- /dev/null +++ b/tests/components/sn74hc595/test.esp32-c3-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 0 + data_pin: 1 + latch_pin: 2 + oe_pin: 3 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 8 + oe_pin: 9 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sn74hc595/test.esp32-c3.yaml b/tests/components/sn74hc595/test.esp32-c3.yaml new file mode 100644 index 000000000000..9b093899d33d --- /dev/null +++ b/tests/components/sn74hc595/test.esp32-c3.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 0 + data_pin: 1 + latch_pin: 2 + oe_pin: 3 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 8 + oe_pin: 9 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sn74hc595/test.esp32-idf.yaml b/tests/components/sn74hc595/test.esp32-idf.yaml new file mode 100644 index 000000000000..f695395797ba --- /dev/null +++ b/tests/components/sn74hc595/test.esp32-idf.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 12 + data_pin: 13 + latch_pin: 14 + oe_pin: 18 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 21 + oe_pin: 22 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sn74hc595/test.esp32.yaml b/tests/components/sn74hc595/test.esp32.yaml new file mode 100644 index 000000000000..f695395797ba --- /dev/null +++ b/tests/components/sn74hc595/test.esp32.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 12 + data_pin: 13 + latch_pin: 14 + oe_pin: 18 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 21 + oe_pin: 22 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sn74hc595/test.esp8266.yaml b/tests/components/sn74hc595/test.esp8266.yaml new file mode 100644 index 000000000000..64bf5d1925e9 --- /dev/null +++ b/tests/components/sn74hc595/test.esp8266.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 0 + data_pin: 2 + latch_pin: 4 + oe_pin: 5 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 15 + oe_pin: 16 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sn74hc595/test.rp2040.yaml b/tests/components/sn74hc595/test.rp2040.yaml new file mode 100644 index 000000000000..de8e1926594c --- /dev/null +++ b/tests/components/sn74hc595/test.rp2040.yaml @@ -0,0 +1,27 @@ +spi: + - id: spi_sn74hc595 + clk_pin: 6 + mosi_pin: 5 + miso_pin: 4 + +sn74hc595: + - id: sn74hc595_hub + clock_pin: 0 + data_pin: 1 + latch_pin: 2 + oe_pin: 3 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: 8 + oe_pin: 9 + spi_id: spi_sn74hc595 + type: spi + sr_count: 2 + +switch: + - platform: gpio + name: SN74HC595 Pin 0 + pin: + sn74hc595: sn74hc595_hub_2 + number: 0 + inverted: false diff --git a/tests/components/sntp/common.yaml b/tests/components/sntp/common.yaml new file mode 100644 index 000000000000..3e9e465296a5 --- /dev/null +++ b/tests/components/sntp/common.yaml @@ -0,0 +1,15 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + on_time: + cron: "/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI" + then: + - lambda: 'ESP_LOGD("main", "time");' diff --git a/tests/components/sntp/test.esp32-c3-idf.yaml b/tests/components/sntp/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sntp/test.esp32-c3.yaml b/tests/components/sntp/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sntp/test.esp32-idf.yaml b/tests/components/sntp/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sntp/test.esp32.yaml b/tests/components/sntp/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sntp/test.esp8266.yaml b/tests/components/sntp/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sntp/test.rp2040.yaml b/tests/components/sntp/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sntp/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sonoff_d1/test.esp32-idf.yaml b/tests/components/sonoff_d1/test.esp32-idf.yaml new file mode 100644 index 000000000000..dc35e3b6acf1 --- /dev/null +++ b/tests/components/sonoff_d1/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_sonoff_d1 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +light: + - platform: sonoff_d1 + id: d1_light + name: Sonoff D1 Dimmer + restore_mode: RESTORE_DEFAULT_OFF + use_rm433_remote: false diff --git a/tests/components/sonoff_d1/test.esp32.yaml b/tests/components/sonoff_d1/test.esp32.yaml new file mode 100644 index 000000000000..dc35e3b6acf1 --- /dev/null +++ b/tests/components/sonoff_d1/test.esp32.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_sonoff_d1 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +light: + - platform: sonoff_d1 + id: d1_light + name: Sonoff D1 Dimmer + restore_mode: RESTORE_DEFAULT_OFF + use_rm433_remote: false diff --git a/tests/components/sonoff_d1/test.esp8266.yaml b/tests/components/sonoff_d1/test.esp8266.yaml new file mode 100644 index 000000000000..c4a62f4cb359 --- /dev/null +++ b/tests/components/sonoff_d1/test.esp8266.yaml @@ -0,0 +1,12 @@ +uart: + - id: uart_sonoff_d1 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +light: + - platform: sonoff_d1 + id: d1_light + name: Sonoff D1 Dimmer + restore_mode: RESTORE_DEFAULT_OFF + use_rm433_remote: false diff --git a/tests/components/speaker/test.esp32-c3-idf.yaml b/tests/components/speaker/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c7809baacedb --- /dev/null +++ b/tests/components/speaker/test.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +esphome: + on_boot: + then: + - speaker.play: [0, 1, 2, 3] + - speaker.stop + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 3 + mode: mono diff --git a/tests/components/speaker/test.esp32-c3.yaml b/tests/components/speaker/test.esp32-c3.yaml new file mode 100644 index 000000000000..c7809baacedb --- /dev/null +++ b/tests/components/speaker/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +esphome: + on_boot: + then: + - speaker.play: [0, 1, 2, 3] + - speaker.stop + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 3 + mode: mono diff --git a/tests/components/speaker/test.esp32-idf.yaml b/tests/components/speaker/test.esp32-idf.yaml new file mode 100644 index 000000000000..416e203d7bed --- /dev/null +++ b/tests/components/speaker/test.esp32-idf.yaml @@ -0,0 +1,17 @@ +esphome: + on_boot: + then: + - speaker.play: [0, 1, 2, 3] + - speaker.stop + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 13 + mode: mono diff --git a/tests/components/speaker/test.esp32.yaml b/tests/components/speaker/test.esp32.yaml new file mode 100644 index 000000000000..416e203d7bed --- /dev/null +++ b/tests/components/speaker/test.esp32.yaml @@ -0,0 +1,17 @@ +esphome: + on_boot: + then: + - speaker.play: [0, 1, 2, 3] + - speaker.stop + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 13 + mode: mono diff --git a/tests/components/speed/test.esp32-c3-idf.yaml b/tests/components/speed/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fa1920676e9b --- /dev/null +++ b/tests/components/speed/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/speed/test.esp32-c3.yaml b/tests/components/speed/test.esp32-c3.yaml new file mode 100644 index 000000000000..fa1920676e9b --- /dev/null +++ b/tests/components/speed/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 2 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/speed/test.esp32-idf.yaml b/tests/components/speed/test.esp32-idf.yaml new file mode 100644 index 000000000000..29a55e9eddba --- /dev/null +++ b/tests/components/speed/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/speed/test.esp32.yaml b/tests/components/speed/test.esp32.yaml new file mode 100644 index 000000000000..29a55e9eddba --- /dev/null +++ b/tests/components/speed/test.esp32.yaml @@ -0,0 +1,9 @@ +output: + - platform: ledc + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/speed/test.esp8266.yaml b/tests/components/speed/test.esp8266.yaml new file mode 100644 index 000000000000..6ed9949cf5db --- /dev/null +++ b/tests/components/speed/test.esp8266.yaml @@ -0,0 +1,9 @@ +output: + - platform: esp8266_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/speed/test.rp2040.yaml b/tests/components/speed/test.rp2040.yaml new file mode 100644 index 000000000000..02b572db7522 --- /dev/null +++ b/tests/components/speed/test.rp2040.yaml @@ -0,0 +1,9 @@ +output: + - platform: rp2040_pwm + id: fan_output_1 + pin: 12 + +fan: + - platform: speed + id: fan_speed + output: fan_output_1 diff --git a/tests/components/spi/test.esp32-c3-idf.yaml b/tests/components/spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f49470ad0746 --- /dev/null +++ b/tests/components/spi/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 diff --git a/tests/components/spi/test.esp32-c3.yaml b/tests/components/spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..f49470ad0746 --- /dev/null +++ b/tests/components/spi/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 diff --git a/tests/components/spi/test.esp32-idf.yaml b/tests/components/spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..1cdcf461dd27 --- /dev/null +++ b/tests/components/spi/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 diff --git a/tests/components/spi/test.esp32-s3-idf.yaml b/tests/components/spi/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..8db934023a34 --- /dev/null +++ b/tests/components/spi/test.esp32-s3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_id_1 + type: single + clk_pin: + number: GPIO0 + ignore_strapping_warning: true + allow_other_uses: false + mosi_pin: GPIO6 + interface: hardware + - id: quad_spi + type: quad + clk_pin: 47 + interface: spi3 + data_pins: + - number: 40 + allow_other_uses: false + - 41 + - 42 + - 43 + - id: spi_id_3 + clk_pin: 8 + mosi_pin: 9 + interface: any + diff --git a/tests/components/spi/test.esp32.yaml b/tests/components/spi/test.esp32.yaml new file mode 100644 index 000000000000..1cdcf461dd27 --- /dev/null +++ b/tests/components/spi/test.esp32.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 diff --git a/tests/components/spi/test.esp8266.yaml b/tests/components/spi/test.esp8266.yaml new file mode 100644 index 000000000000..83f110921fb8 --- /dev/null +++ b/tests/components/spi/test.esp8266.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 diff --git a/tests/components/spi/test.rp2040.yaml b/tests/components/spi/test.rp2040.yaml new file mode 100644 index 000000000000..1e39d247fe9b --- /dev/null +++ b/tests/components/spi/test.rp2040.yaml @@ -0,0 +1,5 @@ +spi: + - id: spi_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 diff --git a/tests/components/spi_device/test.esp32-c3-idf.yaml b/tests/components/spi_device/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..49e27336766d --- /dev/null +++ b/tests/components/spi_device/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32-c3.yaml b/tests/components/spi_device/test.esp32-c3.yaml new file mode 100644 index 000000000000..49e27336766d --- /dev/null +++ b/tests/components/spi_device/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32-idf.yaml b/tests/components/spi_device/test.esp32-idf.yaml new file mode 100644 index 000000000000..cad8ca49f8e1 --- /dev/null +++ b/tests/components/spi_device/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp32.yaml b/tests/components/spi_device/test.esp32.yaml new file mode 100644 index 000000000000..cad8ca49f8e1 --- /dev/null +++ b/tests/components/spi_device/test.esp32.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_device/test.esp8266.yaml b/tests/components/spi_device/test.esp8266.yaml new file mode 100644 index 000000000000..1b191bdb6a8c --- /dev/null +++ b/tests/components/spi_device/test.esp8266.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_device/test.rp2040.yaml b/tests/components/spi_device/test.rp2040.yaml new file mode 100644 index 000000000000..c70493c70d91 --- /dev/null +++ b/tests/components/spi_device/test.rp2040.yaml @@ -0,0 +1,11 @@ +spi: + - id: spi_device1 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +spi_device: + id: spi_device_test + data_rate: 2MHz + mode: 3 + bit_order: lsb_first diff --git a/tests/components/spi_led_strip/test.esp32-c3-idf.yaml b/tests/components/spi_led_strip/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..983ad2863f75 --- /dev/null +++ b/tests/components/spi_led_strip/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/spi_led_strip/test.esp32-c3.yaml b/tests/components/spi_led_strip/test.esp32-c3.yaml new file mode 100644 index 000000000000..983ad2863f75 --- /dev/null +++ b/tests/components/spi_led_strip/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/spi_led_strip/test.esp32-idf.yaml b/tests/components/spi_led_strip/test.esp32-idf.yaml new file mode 100644 index 000000000000..f4a760bf4c3d --- /dev/null +++ b/tests/components/spi_led_strip/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/spi_led_strip/test.esp32.yaml b/tests/components/spi_led_strip/test.esp32.yaml new file mode 100644 index 000000000000..f4a760bf4c3d --- /dev/null +++ b/tests/components/spi_led_strip/test.esp32.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/spi_led_strip/test.esp8266.yaml b/tests/components/spi_led_strip/test.esp8266.yaml new file mode 100644 index 000000000000..8e76303b6afc --- /dev/null +++ b/tests/components/spi_led_strip/test.esp8266.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/spi_led_strip/test.rp2040.yaml b/tests/components/spi_led_strip/test.rp2040.yaml new file mode 100644 index 000000000000..9d12f1592b79 --- /dev/null +++ b/tests/components/spi_led_strip/test.rp2040.yaml @@ -0,0 +1,13 @@ +spi: + - id: spi_spi_led_strip + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +light: + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz diff --git a/tests/components/sprinkler/common.yaml b/tests/components/sprinkler/common.yaml new file mode 100644 index 000000000000..f099f777295b --- /dev/null +++ b/tests/components/sprinkler/common.yaml @@ -0,0 +1,83 @@ +esphome: + on_boot: + then: + - sprinkler.start_full_cycle: yard_sprinkler_ctrlr + - sprinkler.start_from_queue: yard_sprinkler_ctrlr + - sprinkler.start_single_valve: + id: yard_sprinkler_ctrlr + valve_number: 0 + run_duration: 600s + - sprinkler.shutdown: yard_sprinkler_ctrlr + - sprinkler.next_valve: yard_sprinkler_ctrlr + - sprinkler.previous_valve: yard_sprinkler_ctrlr + - sprinkler.pause: yard_sprinkler_ctrlr + - sprinkler.resume: yard_sprinkler_ctrlr + - sprinkler.resume_or_start_full_cycle: yard_sprinkler_ctrlr + - sprinkler.queue_valve: + id: yard_sprinkler_ctrlr + valve_number: 2 + run_duration: 900s + - sprinkler.clear_queued_valves: yard_sprinkler_ctrlr + - sprinkler.set_multiplier: + id: yard_sprinkler_ctrlr + multiplier: 1.5 + - sprinkler.set_repeat: + id: yard_sprinkler_ctrlr + repeat: 2 + - sprinkler.set_divider: + id: yard_sprinkler_ctrlr + divider: 2 + - sprinkler.set_valve_run_duration: + id: yard_sprinkler_ctrlr + valve_number: 0 + run_duration: 600s + +switch: + - platform: template + id: switch1 + optimistic: true + - platform: template + id: switch2 + optimistic: true + +sprinkler: + - id: yard_sprinkler_ctrlr + main_switch: Yard Sprinklers + auto_advance_switch: Yard Sprinklers Auto Advance + reverse_switch: Yard Sprinklers Reverse + pump_start_pump_delay: 2s + pump_stop_valve_delay: 4s + pump_switch_off_during_valve_open_delay: true + valve_open_delay: 5s + valves: + - valve_switch: Yard Valve 0 + enable_switch: Enable Yard Valve 0 + pump_switch_id: switch1 + run_duration: 10s + valve_switch_id: switch2 + - valve_switch: Yard Valve 1 + enable_switch: Enable Yard Valve 1 + pump_switch_id: switch1 + run_duration: 10s + valve_switch_id: switch2 + - valve_switch: Yard Valve 2 + enable_switch: Enable Yard Valve 2 + pump_switch_id: switch1 + run_duration: 10s + valve_switch_id: switch2 + - id: garden_sprinkler_ctrlr + main_switch: Garden Sprinklers + auto_advance_switch: Garden Sprinklers Auto Advance + reverse_switch: Garden Sprinklers Reverse + valve_overlap: 5s + valves: + - valve_switch: Garden Valve 0 + enable_switch: Enable Garden Valve 0 + pump_switch_id: switch1 + run_duration: 10s + valve_switch_id: switch2 + - valve_switch: Garden Valve 1 + enable_switch: Enable Garden Valve 1 + pump_switch_id: switch1 + run_duration: 10s + valve_switch_id: switch2 diff --git a/tests/components/sprinkler/test.esp32-c3-idf.yaml b/tests/components/sprinkler/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sprinkler/test.esp32-c3.yaml b/tests/components/sprinkler/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sprinkler/test.esp32-idf.yaml b/tests/components/sprinkler/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sprinkler/test.esp32.yaml b/tests/components/sprinkler/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sprinkler/test.esp8266.yaml b/tests/components/sprinkler/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sprinkler/test.rp2040.yaml b/tests/components/sprinkler/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sprinkler/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sps30/test.esp32-c3-idf.yaml b/tests/components/sps30/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e071a00936df --- /dev/null +++ b/tests/components/sps30/test.esp32-c3-idf.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 5 + sda: 4 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/sps30/test.esp32-c3.yaml b/tests/components/sps30/test.esp32-c3.yaml new file mode 100644 index 000000000000..e071a00936df --- /dev/null +++ b/tests/components/sps30/test.esp32-c3.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 5 + sda: 4 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/sps30/test.esp32-idf.yaml b/tests/components/sps30/test.esp32-idf.yaml new file mode 100644 index 000000000000..f9d1ee4e55b5 --- /dev/null +++ b/tests/components/sps30/test.esp32-idf.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 16 + sda: 17 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/sps30/test.esp32.yaml b/tests/components/sps30/test.esp32.yaml new file mode 100644 index 000000000000..f9d1ee4e55b5 --- /dev/null +++ b/tests/components/sps30/test.esp32.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 16 + sda: 17 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/sps30/test.esp8266.yaml b/tests/components/sps30/test.esp8266.yaml new file mode 100644 index 000000000000..e071a00936df --- /dev/null +++ b/tests/components/sps30/test.esp8266.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 5 + sda: 4 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/sps30/test.rp2040.yaml b/tests/components/sps30/test.rp2040.yaml new file mode 100644 index 000000000000..e071a00936df --- /dev/null +++ b/tests/components/sps30/test.rp2040.yaml @@ -0,0 +1,36 @@ +i2c: + - id: i2c_sps30 + scl: 5 + sda: 4 + +sensor: + - platform: sps30 + pm_1_0: + name: Workshop PM <1µm Weight concentration + id: workshop_PM_1_0 + pm_2_5: + name: Workshop PM <2.5µm Weight concentration + id: workshop_PM_2_5 + pm_4_0: + name: Workshop PM <4µm Weight concentration + id: workshop_PM_4_0 + pm_10_0: + name: Workshop PM <10µm Weight concentration + id: workshop_PM_10_0 + pmc_0_5: + name: Workshop PM <0.5µm Number concentration + id: workshop_PMC_0_5 + pmc_1_0: + name: Workshop PM <1µm Number concentration + id: workshop_PMC_1_0 + pmc_2_5: + name: Workshop PM <2.5µm Number concentration + id: workshop_PMC_2_5 + pmc_4_0: + name: Workshop PM <4µm Number concentration + id: workshop_PMC_4_0 + pmc_10_0: + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 + address: 0x69 + update_interval: 10s diff --git a/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml b/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f4a301db5128 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_i2c/test.esp32-c3.yaml b/tests/components/ssd1306_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..f4a301db5128 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_i2c/test.esp32-idf.yaml b/tests/components/ssd1306_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..dddc67309ca6 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_i2c/test.esp32.yaml b/tests/components/ssd1306_i2c/test.esp32.yaml new file mode 100644 index 000000000000..dddc67309ca6 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_i2c/test.esp8266.yaml b/tests/components/ssd1306_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..f4a301db5128 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_i2c/test.rp2040.yaml b/tests/components/ssd1306_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..f4a301db5128 --- /dev/null +++ b/tests/components/ssd1306_i2c/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_ssd1306_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + model: SSD1306_128X64 + reset_pin: 3 + address: 0x3C + id: display1 + contrast: 60% + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..01b2d0e4a893 --- /dev/null +++ b/tests/components/ssd1306_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1306_spi/test.esp32-c3.yaml b/tests/components/ssd1306_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..01b2d0e4a893 --- /dev/null +++ b/tests/components/ssd1306_spi/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1306_spi/test.esp32-idf.yaml b/tests/components/ssd1306_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..b0e5e0f1a2e0 --- /dev/null +++ b/tests/components/ssd1306_spi/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1306_spi/test.esp32.yaml b/tests/components/ssd1306_spi/test.esp32.yaml new file mode 100644 index 000000000000..b0e5e0f1a2e0 --- /dev/null +++ b/tests/components/ssd1306_spi/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1306_spi/test.esp8266.yaml b/tests/components/ssd1306_spi/test.esp8266.yaml new file mode 100644 index 000000000000..135e364bb2eb --- /dev/null +++ b/tests/components/ssd1306_spi/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1306_spi/test.rp2040.yaml b/tests/components/ssd1306_spi/test.rp2040.yaml new file mode 100644 index 000000000000..94c4b85158ac --- /dev/null +++ b/tests/components/ssd1306_spi/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1306_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1306_spi + model: SSD1306 128x64 + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/ssd1322_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1322_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4fa9f86594d8 --- /dev/null +++ b/tests/components/ssd1322_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1322_spi/test.esp32-c3.yaml b/tests/components/ssd1322_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..4fa9f86594d8 --- /dev/null +++ b/tests/components/ssd1322_spi/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1322_spi/test.esp32-idf.yaml b/tests/components/ssd1322_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..aa6d0fbf013a --- /dev/null +++ b/tests/components/ssd1322_spi/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1322_spi/test.esp32.yaml b/tests/components/ssd1322_spi/test.esp32.yaml new file mode 100644 index 000000000000..aa6d0fbf013a --- /dev/null +++ b/tests/components/ssd1322_spi/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1322_spi/test.esp8266.yaml b/tests/components/ssd1322_spi/test.esp8266.yaml new file mode 100644 index 000000000000..a5aa565c09de --- /dev/null +++ b/tests/components/ssd1322_spi/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1322_spi/test.rp2040.yaml b/tests/components/ssd1322_spi/test.rp2040.yaml new file mode 100644 index 000000000000..59544e787810 --- /dev/null +++ b/tests/components/ssd1322_spi/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1322_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1322_spi + model: SSD1322 256x64 + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/ssd1325_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1325_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..0fa8cb64882b --- /dev/null +++ b/tests/components/ssd1325_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1325_spi/test.esp32-c3.yaml b/tests/components/ssd1325_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..0fa8cb64882b --- /dev/null +++ b/tests/components/ssd1325_spi/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1325_spi/test.esp32-idf.yaml b/tests/components/ssd1325_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..84d94eff287c --- /dev/null +++ b/tests/components/ssd1325_spi/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1325_spi/test.esp32.yaml b/tests/components/ssd1325_spi/test.esp32.yaml new file mode 100644 index 000000000000..84d94eff287c --- /dev/null +++ b/tests/components/ssd1325_spi/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1325_spi/test.esp8266.yaml b/tests/components/ssd1325_spi/test.esp8266.yaml new file mode 100644 index 000000000000..9d7e48358518 --- /dev/null +++ b/tests/components/ssd1325_spi/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1325_spi/test.rp2040.yaml b/tests/components/ssd1325_spi/test.rp2040.yaml new file mode 100644 index 000000000000..869663c19d64 --- /dev/null +++ b/tests/components/ssd1325_spi/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1325_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1325_spi + model: SSD1325 128x64 + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/ssd1327_i2c/test.esp32-c3-idf.yaml b/tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..cd28795ff1f2 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_i2c/test.esp32-c3.yaml b/tests/components/ssd1327_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..cd28795ff1f2 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_i2c/test.esp32-idf.yaml b/tests/components/ssd1327_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..e72a9c7b7a24 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 16 + sda: 17 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_i2c/test.esp32.yaml b/tests/components/ssd1327_i2c/test.esp32.yaml new file mode 100644 index 000000000000..e72a9c7b7a24 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.esp32.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 16 + sda: 17 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_i2c/test.esp8266.yaml b/tests/components/ssd1327_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..cd28795ff1f2 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.esp8266.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_i2c/test.rp2040.yaml b/tests/components/ssd1327_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..cd28795ff1f2 --- /dev/null +++ b/tests/components/ssd1327_i2c/test.rp2040.yaml @@ -0,0 +1,24 @@ +i2c: + - id: i2c_ssd1327_i2c + scl: 5 + sda: 4 + +display: + - platform: ssd1327_i2c + model: SSD1327_128x128 + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ec5795d71620 --- /dev/null +++ b/tests/components/ssd1327_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1327_spi/test.esp32-c3.yaml b/tests/components/ssd1327_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..ec5795d71620 --- /dev/null +++ b/tests/components/ssd1327_spi/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1327_spi/test.esp32-idf.yaml b/tests/components/ssd1327_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..e103ded187d0 --- /dev/null +++ b/tests/components/ssd1327_spi/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1327_spi/test.esp32.yaml b/tests/components/ssd1327_spi/test.esp32.yaml new file mode 100644 index 000000000000..e103ded187d0 --- /dev/null +++ b/tests/components/ssd1327_spi/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1327_spi/test.esp8266.yaml b/tests/components/ssd1327_spi/test.esp8266.yaml new file mode 100644 index 000000000000..30455d24ee39 --- /dev/null +++ b/tests/components/ssd1327_spi/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1327_spi/test.rp2040.yaml b/tests/components/ssd1327_spi/test.rp2040.yaml new file mode 100644 index 000000000000..f819ab2c411e --- /dev/null +++ b/tests/components/ssd1327_spi/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1327_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1327_spi + model: SSD1327 128x128 + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/ssd1331_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1331_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9a35918faf7d --- /dev/null +++ b/tests/components/ssd1331_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1331_spi + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1331_spi/test.esp32-c3.yaml b/tests/components/ssd1331_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..9a35918faf7d --- /dev/null +++ b/tests/components/ssd1331_spi/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1331_spi + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1331_spi/test.esp32-idf.yaml b/tests/components/ssd1331_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..e9eb8ea9ad51 --- /dev/null +++ b/tests/components/ssd1331_spi/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1331_spi + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1331_spi/test.esp32.yaml b/tests/components/ssd1331_spi/test.esp32.yaml new file mode 100644 index 000000000000..e9eb8ea9ad51 --- /dev/null +++ b/tests/components/ssd1331_spi/test.esp32.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1331_spi + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1331_spi/test.esp8266.yaml b/tests/components/ssd1331_spi/test.esp8266.yaml new file mode 100644 index 000000000000..3b319ef38ef2 --- /dev/null +++ b/tests/components/ssd1331_spi/test.esp8266.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1331_spi + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1331_spi/test.rp2040.yaml b/tests/components/ssd1331_spi/test.rp2040.yaml new file mode 100644 index 000000000000..947685b07a95 --- /dev/null +++ b/tests/components/ssd1331_spi/test.rp2040.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_ssd1331_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1331_spi + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/ssd1351_spi/test.esp32-c3-idf.yaml b/tests/components/ssd1351_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2a7266f502a5 --- /dev/null +++ b/tests/components/ssd1351_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1351_spi/test.esp32-c3.yaml b/tests/components/ssd1351_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..2a7266f502a5 --- /dev/null +++ b/tests/components/ssd1351_spi/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/ssd1351_spi/test.esp32-idf.yaml b/tests/components/ssd1351_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..8342cb972b5c --- /dev/null +++ b/tests/components/ssd1351_spi/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1351_spi/test.esp32.yaml b/tests/components/ssd1351_spi/test.esp32.yaml new file mode 100644 index 000000000000..8342cb972b5c --- /dev/null +++ b/tests/components/ssd1351_spi/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/ssd1351_spi/test.esp8266.yaml b/tests/components/ssd1351_spi/test.esp8266.yaml new file mode 100644 index 000000000000..7ed9a31ddee3 --- /dev/null +++ b/tests/components/ssd1351_spi/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/ssd1351_spi/test.rp2040.yaml b/tests/components/ssd1351_spi/test.rp2040.yaml new file mode 100644 index 000000000000..72936d046b13 --- /dev/null +++ b/tests/components/ssd1351_spi/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_ssd1351_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ssd1351_spi + model: "SSD1351 128x128" + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/st7567_i2c/test.esp32-c3-idf.yaml b/tests/components/st7567_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d77904050036 --- /dev/null +++ b/tests/components/st7567_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 5 + sda: 4 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_i2c/test.esp32-c3.yaml b/tests/components/st7567_i2c/test.esp32-c3.yaml new file mode 100644 index 000000000000..d77904050036 --- /dev/null +++ b/tests/components/st7567_i2c/test.esp32-c3.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 5 + sda: 4 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_i2c/test.esp32-idf.yaml b/tests/components/st7567_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..b7aee61b6881 --- /dev/null +++ b/tests/components/st7567_i2c/test.esp32-idf.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 16 + sda: 17 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_i2c/test.esp32.yaml b/tests/components/st7567_i2c/test.esp32.yaml new file mode 100644 index 000000000000..b7aee61b6881 --- /dev/null +++ b/tests/components/st7567_i2c/test.esp32.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 16 + sda: 17 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_i2c/test.esp8266.yaml b/tests/components/st7567_i2c/test.esp8266.yaml new file mode 100644 index 000000000000..d77904050036 --- /dev/null +++ b/tests/components/st7567_i2c/test.esp8266.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 5 + sda: 4 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_i2c/test.rp2040.yaml b/tests/components/st7567_i2c/test.rp2040.yaml new file mode 100644 index 000000000000..d77904050036 --- /dev/null +++ b/tests/components/st7567_i2c/test.rp2040.yaml @@ -0,0 +1,23 @@ +i2c: + - id: i2c_st7567_i2c + scl: 5 + sda: 4 + +display: + - platform: st7567_i2c + reset_pin: 3 + address: 0x3C + id: display1 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - id: page2 + lambda: |- + it.rectangle(0, 0, 10, 10); + on_page_change: + from: page1 + to: page2 + then: + lambda: |- + ESP_LOGD("display", "1 -> 2"); diff --git a/tests/components/st7567_spi/test.esp32-c3-idf.yaml b/tests/components/st7567_spi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b799ce7302c4 --- /dev/null +++ b/tests/components/st7567_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7567_spi + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/st7567_spi/test.esp32-c3.yaml b/tests/components/st7567_spi/test.esp32-c3.yaml new file mode 100644 index 000000000000..b799ce7302c4 --- /dev/null +++ b/tests/components/st7567_spi/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7567_spi + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + 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/st7567_spi/test.esp32-idf.yaml b/tests/components/st7567_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..bb4530248fba --- /dev/null +++ b/tests/components/st7567_spi/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7567_spi + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/st7567_spi/test.esp32.yaml b/tests/components/st7567_spi/test.esp32.yaml new file mode 100644 index 000000000000..bb4530248fba --- /dev/null +++ b/tests/components/st7567_spi/test.esp32.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7567_spi + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/st7567_spi/test.esp8266.yaml b/tests/components/st7567_spi/test.esp8266.yaml new file mode 100644 index 000000000000..bbc47e67f6b4 --- /dev/null +++ b/tests/components/st7567_spi/test.esp8266.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: st7567_spi + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/st7567_spi/test.rp2040.yaml b/tests/components/st7567_spi/test.rp2040.yaml new file mode 100644 index 000000000000..1b491101a85f --- /dev/null +++ b/tests/components/st7567_spi/test.rp2040.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7567_spi + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: st7567_spi + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/st7701s/common.yaml b/tests/components/st7701s/common.yaml new file mode 100644 index 000000000000..497df8c8ce8b --- /dev/null +++ b/tests/components/st7701s/common.yaml @@ -0,0 +1,60 @@ +psram: + mode: octal + speed: 80MHz +spi: + - id: lcd_spi + clk_pin: 41 + mosi_pin: 48 + +i2c: + sda: 39 + scl: 40 + scan: false + id: bus_a + +pca9554: + - id: p_c_a + pin_count: 16 + address: 0x20 + +display: + - platform: st7701s + spi_mode: MODE3 + color_order: RGB + dimensions: + width: 480 + height: 480 + invert_colors: true + transform: + mirror_x: true + mirror_y: true + cs_pin: + pca9554: p_c_a + number: 4 + reset_pin: + pca9554: p_c_a + number: 5 + de_pin: 18 + hsync_pin: 16 + vsync_pin: 17 + pclk_pin: 21 + init_sequence: 1 + data_pins: + - number: 0 + ignore_strapping_warning: true + - 1 + - 2 + - 3 + - number: 4 + ignore_strapping_warning: false + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 diff --git a/tests/components/st7701s/test.esp32-s3-idf.yaml b/tests/components/st7701s/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/st7701s/test.esp32-s3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/st7735/test.esp32-c3-idf.yaml b/tests/components/st7735/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fc6c2421c4cb --- /dev/null +++ b/tests/components/st7735/test.esp32-c3-idf.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7735/test.esp32-c3.yaml b/tests/components/st7735/test.esp32-c3.yaml new file mode 100644 index 000000000000..fc6c2421c4cb --- /dev/null +++ b/tests/components/st7735/test.esp32-c3.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 2 + dc_pin: 3 + reset_pin: 4 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7735/test.esp32-idf.yaml b/tests/components/st7735/test.esp32-idf.yaml new file mode 100644 index 000000000000..fd3f6cade6e8 --- /dev/null +++ b/tests/components/st7735/test.esp32-idf.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7735/test.esp32.yaml b/tests/components/st7735/test.esp32.yaml new file mode 100644 index 000000000000..fd3f6cade6e8 --- /dev/null +++ b/tests/components/st7735/test.esp32.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7735/test.esp8266.yaml b/tests/components/st7735/test.esp8266.yaml new file mode 100644 index 000000000000..ea8fa93c364c --- /dev/null +++ b/tests/components/st7735/test.esp8266.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7735/test.rp2040.yaml b/tests/components/st7735/test.rp2040.yaml new file mode 100644 index 000000000000..828f9a3ae14f --- /dev/null +++ b/tests/components/st7735/test.rp2040.yaml @@ -0,0 +1,29 @@ +spi: + - id: spi_st7735 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: st7735 + model: "INITR_18BLACKTAB" + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 + 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/st7789v/test.esp32-c3-idf.yaml b/tests/components/st7789v/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..1cb8d22fcbe8 --- /dev/null +++ b/tests/components/st7789v/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 2 + dc_pin: 3 + reset_pin: 8 + 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/st7789v/test.esp32-c3.yaml b/tests/components/st7789v/test.esp32-c3.yaml new file mode 100644 index 000000000000..1cb8d22fcbe8 --- /dev/null +++ b/tests/components/st7789v/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 2 + dc_pin: 3 + reset_pin: 8 + 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/st7789v/test.esp32-idf.yaml b/tests/components/st7789v/test.esp32-idf.yaml new file mode 100644 index 000000000000..54a9db6da198 --- /dev/null +++ b/tests/components/st7789v/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/st7789v/test.esp32.yaml b/tests/components/st7789v/test.esp32.yaml new file mode 100644 index 000000000000..54a9db6da198 --- /dev/null +++ b/tests/components/st7789v/test.esp32.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 12 + dc_pin: 13 + reset_pin: 14 + 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/st7789v/test.esp8266.yaml b/tests/components/st7789v/test.esp8266.yaml new file mode 100644 index 000000000000..deeceb8c9a37 --- /dev/null +++ b/tests/components/st7789v/test.esp8266.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 15 + dc_pin: 16 + reset_pin: 5 + 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/st7789v/test.rp2040.yaml b/tests/components/st7789v/test.rp2040.yaml new file mode 100644 index 000000000000..778aa2aa55a9 --- /dev/null +++ b/tests/components/st7789v/test.rp2040.yaml @@ -0,0 +1,25 @@ +spi: + - id: spi_st7789v + clk_pin: 2 + mosi_pin: 3 + miso_pin: 8 + +display: + - platform: st7789v + model: TTGO TDisplay 135x240 + cs_pin: 5 + dc_pin: 6 + reset_pin: 7 + 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/st7920/test.esp32-c3-idf.yaml b/tests/components/st7920/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..84ae88461f40 --- /dev/null +++ b/tests/components/st7920/test.esp32-c3-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7920 + cs_pin: 2 + height: 128 + width: 64 + 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/st7920/test.esp32-c3.yaml b/tests/components/st7920/test.esp32-c3.yaml new file mode 100644 index 000000000000..84ae88461f40 --- /dev/null +++ b/tests/components/st7920/test.esp32-c3.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: st7920 + cs_pin: 2 + height: 128 + width: 64 + 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/st7920/test.esp32-idf.yaml b/tests/components/st7920/test.esp32-idf.yaml new file mode 100644 index 000000000000..cdcbc8564242 --- /dev/null +++ b/tests/components/st7920/test.esp32-idf.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7920 + cs_pin: 12 + height: 128 + width: 64 + 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/st7920/test.esp32.yaml b/tests/components/st7920/test.esp32.yaml new file mode 100644 index 000000000000..cdcbc8564242 --- /dev/null +++ b/tests/components/st7920/test.esp32.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: st7920 + cs_pin: 12 + height: 128 + width: 64 + 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/st7920/test.esp8266.yaml b/tests/components/st7920/test.esp8266.yaml new file mode 100644 index 000000000000..0450bf1c5e24 --- /dev/null +++ b/tests/components/st7920/test.esp8266.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: st7920 + cs_pin: 15 + height: 128 + width: 64 + 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/st7920/test.rp2040.yaml b/tests/components/st7920/test.rp2040.yaml new file mode 100644 index 000000000000..f442820e7b43 --- /dev/null +++ b/tests/components/st7920/test.rp2040.yaml @@ -0,0 +1,24 @@ +spi: + - id: spi_st7920 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: st7920 + cs_pin: 5 + height: 128 + width: 64 + 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/status/common.yaml b/tests/components/status/common.yaml new file mode 100644 index 000000000000..c14157566b64 --- /dev/null +++ b/tests/components/status/common.yaml @@ -0,0 +1,10 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +binary_sensor: + - platform: status + id: node_status + name: Node Status diff --git a/tests/components/status/test.esp32-c3-idf.yaml b/tests/components/status/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status/test.esp32-c3.yaml b/tests/components/status/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status/test.esp32-idf.yaml b/tests/components/status/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status/test.esp32.yaml b/tests/components/status/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status/test.esp8266.yaml b/tests/components/status/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status/test.rp2040.yaml b/tests/components/status/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/common.yaml b/tests/components/status_led/common.yaml new file mode 100644 index 000000000000..ec66c219d342 --- /dev/null +++ b/tests/components/status_led/common.yaml @@ -0,0 +1,10 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +light: + - platform: status_led + name: Switch State + pin: 4 diff --git a/tests/components/status_led/test.esp32-c3-idf.yaml b/tests/components/status_led/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/test.esp32-c3.yaml b/tests/components/status_led/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/test.esp32-idf.yaml b/tests/components/status_led/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/test.esp32.yaml b/tests/components/status_led/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/test.esp8266.yaml b/tests/components/status_led/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/status_led/test.rp2040.yaml b/tests/components/status_led/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/status_led/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/common.yaml b/tests/components/stepper/common.yaml new file mode 100644 index 000000000000..fcf575961886 --- /dev/null +++ b/tests/components/stepper/common.yaml @@ -0,0 +1,27 @@ +stepper: + - platform: a4988 + id: test_stepper + step_pin: 3 + dir_pin: 4 + sleep_pin: 5 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 + +switch: + - platform: template + name: Stepper Switch + assumed_state: true + turn_on_action: + - stepper.set_target: + id: test_stepper + target: !lambda |- + static int32_t i = 0; + i += 1000; + if (i > 5000) { + i = -5000; + } + return i; + - stepper.report_position: + id: test_stepper + position: 0 diff --git a/tests/components/stepper/test.esp32-c3-idf.yaml b/tests/components/stepper/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/test.esp32-c3.yaml b/tests/components/stepper/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/test.esp32-idf.yaml b/tests/components/stepper/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/test.esp32.yaml b/tests/components/stepper/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/test.esp8266.yaml b/tests/components/stepper/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/stepper/test.rp2040.yaml b/tests/components/stepper/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/stepper/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sts3x/test.esp32-c3-idf.yaml b/tests/components/sts3x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..87980ce3a7be --- /dev/null +++ b/tests/components/sts3x/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 5 + sda: 4 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sts3x/test.esp32-c3.yaml b/tests/components/sts3x/test.esp32-c3.yaml new file mode 100644 index 000000000000..87980ce3a7be --- /dev/null +++ b/tests/components/sts3x/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 5 + sda: 4 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sts3x/test.esp32-idf.yaml b/tests/components/sts3x/test.esp32-idf.yaml new file mode 100644 index 000000000000..a74d61e748cb --- /dev/null +++ b/tests/components/sts3x/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 16 + sda: 17 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sts3x/test.esp32.yaml b/tests/components/sts3x/test.esp32.yaml new file mode 100644 index 000000000000..a74d61e748cb --- /dev/null +++ b/tests/components/sts3x/test.esp32.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 16 + sda: 17 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sts3x/test.esp8266.yaml b/tests/components/sts3x/test.esp8266.yaml new file mode 100644 index 000000000000..87980ce3a7be --- /dev/null +++ b/tests/components/sts3x/test.esp8266.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 5 + sda: 4 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sts3x/test.rp2040.yaml b/tests/components/sts3x/test.rp2040.yaml new file mode 100644 index 000000000000..87980ce3a7be --- /dev/null +++ b/tests/components/sts3x/test.rp2040.yaml @@ -0,0 +1,10 @@ +i2c: + - id: i2c_sts3x + scl: 5 + sda: 4 + +sensor: + - platform: sts3x + id: sts3x_sensor + name: STS3X Temperature + address: 0x4A diff --git a/tests/components/sun/common.yaml b/tests/components/sun/common.yaml new file mode 100644 index 000000000000..e0157424a06d --- /dev/null +++ b/tests/components/sun/common.yaml @@ -0,0 +1,38 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +time: + - platform: homeassistant + id: homeassistant_time + +sun: + latitude: 48.8584° + longitude: 2.2945° + on_sunrise: + - then: + - logger.log: Good morning + - elevation: 5° + then: + - logger.log: Good morning again + on_sunset: + - then: + - logger.log: Good evening + +sensor: + - platform: sun + name: Sun Elevation + type: elevation + - platform: sun + name: Sun Azimuth + type: azimuth + +text_sensor: + - platform: sun + name: Sun Next Sunrise + type: sunrise + - platform: sun + name: Sun Next Sunset + type: sunset diff --git a/tests/components/sun/test.esp32-c3-idf.yaml b/tests/components/sun/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sun/test.esp32-c3.yaml b/tests/components/sun/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sun/test.esp32-idf.yaml b/tests/components/sun/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sun/test.esp32.yaml b/tests/components/sun/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sun/test.esp8266.yaml b/tests/components/sun/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/sun/test.rp2040.yaml b/tests/components/sun/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/sun/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..6ec834db98b1 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-c3-idf.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32-c3.yaml b/tests/components/sun_gtil2/test.esp32-c3.yaml new file mode 100644 index 000000000000..6ec834db98b1 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-c3.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32-idf.yaml b/tests/components/sun_gtil2/test.esp32-idf.yaml new file mode 100644 index 000000000000..ed1e68e574f1 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32-idf.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 16 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp32.yaml b/tests/components/sun_gtil2/test.esp32.yaml new file mode 100644 index 000000000000..ed1e68e574f1 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp32.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 16 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.esp8266.yaml b/tests/components/sun_gtil2/test.esp8266.yaml new file mode 100644 index 000000000000..6ec834db98b1 --- /dev/null +++ b/tests/components/sun_gtil2/test.esp8266.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sun_gtil2/test.rp2040.yaml b/tests/components/sun_gtil2/test.rp2040.yaml new file mode 100644 index 000000000000..6ec834db98b1 --- /dev/null +++ b/tests/components/sun_gtil2/test.rp2040.yaml @@ -0,0 +1,44 @@ +uart: + - id: control_to_display + rx_pin: + number: 5 + baud_rate: 9600 + +sun_gtil2: + uart_id: control_to_display + +sensor: + - platform: sun_gtil2 + temperature: + id: gtil_temperature + name: "Heatsink Temperature" + filters: + - throttle_average: 30s + dc_voltage: + id: gtil_dc_voltage + name: "DC Voltage" + filters: + - throttle_average: 30s + ac_voltage: + id: gtil_ac_voltage + name: "AC Voltage" + filters: + - throttle_average: 30s + ac_power: + id: gtil_ac_power + name: "AC Power" + dc_power: + id: gtil_dc_power + name: "DC Power" + limiter_power: + id: gtil_limiter_power + internal: true + +text_sensor: + - platform: sun_gtil2 + state: + id: gtil_state + name: "State" + serial_number: + id: gtil_serial_number + internal: true diff --git a/tests/components/sx1509/test.esp32-c3-idf.yaml b/tests/components/sx1509/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ced849b3df0a --- /dev/null +++ b/tests/components/sx1509/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 5 + sda: 4 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/sx1509/test.esp32-c3.yaml b/tests/components/sx1509/test.esp32-c3.yaml new file mode 100644 index 000000000000..ced849b3df0a --- /dev/null +++ b/tests/components/sx1509/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 5 + sda: 4 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/sx1509/test.esp32-idf.yaml b/tests/components/sx1509/test.esp32-idf.yaml new file mode 100644 index 000000000000..1698f2abc4b4 --- /dev/null +++ b/tests/components/sx1509/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 16 + sda: 17 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/sx1509/test.esp32.yaml b/tests/components/sx1509/test.esp32.yaml new file mode 100644 index 000000000000..1698f2abc4b4 --- /dev/null +++ b/tests/components/sx1509/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 16 + sda: 17 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/sx1509/test.esp8266.yaml b/tests/components/sx1509/test.esp8266.yaml new file mode 100644 index 000000000000..ced849b3df0a --- /dev/null +++ b/tests/components/sx1509/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 5 + sda: 4 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/sx1509/test.rp2040.yaml b/tests/components/sx1509/test.rp2040.yaml new file mode 100644 index 000000000000..ced849b3df0a --- /dev/null +++ b/tests/components/sx1509/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_sx1509 + scl: 5 + sda: 4 + +sx1509: + - id: sx1509_hub + address: 0x3E + +binary_sensor: + - platform: gpio + name: GPIO SX1509 Test + pin: + sx1509: sx1509_hub + number: 3 diff --git a/tests/components/t6615/test.esp32-c3-idf.yaml b/tests/components/t6615/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..e8690c770f87 --- /dev/null +++ b/tests/components/t6615/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 4 + rx_pin: 5 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/t6615/test.esp32-c3.yaml b/tests/components/t6615/test.esp32-c3.yaml new file mode 100644 index 000000000000..e8690c770f87 --- /dev/null +++ b/tests/components/t6615/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 4 + rx_pin: 5 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/t6615/test.esp32-idf.yaml b/tests/components/t6615/test.esp32-idf.yaml new file mode 100644 index 000000000000..2cfaa0ae5b87 --- /dev/null +++ b/tests/components/t6615/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 17 + rx_pin: 16 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/t6615/test.esp32.yaml b/tests/components/t6615/test.esp32.yaml new file mode 100644 index 000000000000..2cfaa0ae5b87 --- /dev/null +++ b/tests/components/t6615/test.esp32.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 17 + rx_pin: 16 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/t6615/test.esp8266.yaml b/tests/components/t6615/test.esp8266.yaml new file mode 100644 index 000000000000..e8690c770f87 --- /dev/null +++ b/tests/components/t6615/test.esp8266.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 4 + rx_pin: 5 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/t6615/test.rp2040.yaml b/tests/components/t6615/test.rp2040.yaml new file mode 100644 index 000000000000..e8690c770f87 --- /dev/null +++ b/tests/components/t6615/test.rp2040.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_t6615 + tx_pin: 4 + rx_pin: 5 + baud_rate: 19200 + +sensor: + - platform: t6615 + co2: + name: CO2 Sensor diff --git a/tests/components/tca9548a/test.esp32-c3-idf.yaml b/tests/components/tca9548a/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2294530d1478 --- /dev/null +++ b/tests/components/tca9548a/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 5 + sda: 4 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tca9548a/test.esp32-c3.yaml b/tests/components/tca9548a/test.esp32-c3.yaml new file mode 100644 index 000000000000..2294530d1478 --- /dev/null +++ b/tests/components/tca9548a/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 5 + sda: 4 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tca9548a/test.esp32-idf.yaml b/tests/components/tca9548a/test.esp32-idf.yaml new file mode 100644 index 000000000000..7edb83c8219d --- /dev/null +++ b/tests/components/tca9548a/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 16 + sda: 17 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tca9548a/test.esp32.yaml b/tests/components/tca9548a/test.esp32.yaml new file mode 100644 index 000000000000..7edb83c8219d --- /dev/null +++ b/tests/components/tca9548a/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 16 + sda: 17 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tca9548a/test.esp8266.yaml b/tests/components/tca9548a/test.esp8266.yaml new file mode 100644 index 000000000000..2294530d1478 --- /dev/null +++ b/tests/components/tca9548a/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 5 + sda: 4 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tca9548a/test.rp2040.yaml b/tests/components/tca9548a/test.rp2040.yaml new file mode 100644 index 000000000000..2294530d1478 --- /dev/null +++ b/tests/components/tca9548a/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_tca9548a + scl: 5 + sda: 4 + +tca9548a: + - id: multiplex0 + address: 0x70 + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_tca9548a + - id: multiplex1 + address: 0x71 + i2c_id: multiplex0_chan0 diff --git a/tests/components/tcl112/test.esp32-c3-idf.yaml b/tests/components/tcl112/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..03c0e84fe5dd --- /dev/null +++ b/tests/components/tcl112/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: tcl112_sensor + lambda: "return 21;" + +climate: + - platform: tcl112 + name: TCL112 Climate with Sensor + supports_heat: true + supports_cool: true + sensor: tcl112_sensor diff --git a/tests/components/tcl112/test.esp32-c3.yaml b/tests/components/tcl112/test.esp32-c3.yaml new file mode 100644 index 000000000000..03c0e84fe5dd --- /dev/null +++ b/tests/components/tcl112/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: tcl112_sensor + lambda: "return 21;" + +climate: + - platform: tcl112 + name: TCL112 Climate with Sensor + supports_heat: true + supports_cool: true + sensor: tcl112_sensor diff --git a/tests/components/tcl112/test.esp32-idf.yaml b/tests/components/tcl112/test.esp32-idf.yaml new file mode 100644 index 000000000000..03c0e84fe5dd --- /dev/null +++ b/tests/components/tcl112/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: tcl112_sensor + lambda: "return 21;" + +climate: + - platform: tcl112 + name: TCL112 Climate with Sensor + supports_heat: true + supports_cool: true + sensor: tcl112_sensor diff --git a/tests/components/tcl112/test.esp32.yaml b/tests/components/tcl112/test.esp32.yaml new file mode 100644 index 000000000000..03c0e84fe5dd --- /dev/null +++ b/tests/components/tcl112/test.esp32.yaml @@ -0,0 +1,15 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: tcl112_sensor + lambda: "return 21;" + +climate: + - platform: tcl112 + name: TCL112 Climate with Sensor + supports_heat: true + supports_cool: true + sensor: tcl112_sensor diff --git a/tests/components/tcl112/test.esp8266.yaml b/tests/components/tcl112/test.esp8266.yaml new file mode 100644 index 000000000000..0a855369284f --- /dev/null +++ b/tests/components/tcl112/test.esp8266.yaml @@ -0,0 +1,15 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +sensor: + - platform: template + id: tcl112_sensor + lambda: "return 21;" + +climate: + - platform: tcl112 + name: TCL112 Climate with Sensor + supports_heat: true + supports_cool: true + sensor: tcl112_sensor diff --git a/tests/components/tcs34725/test.esp32-c3-idf.yaml b/tests/components/tcs34725/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..9b459c910416 --- /dev/null +++ b/tests/components/tcs34725/test.esp32-c3-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 5 + sda: 4 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tcs34725/test.esp32-c3.yaml b/tests/components/tcs34725/test.esp32-c3.yaml new file mode 100644 index 000000000000..9b459c910416 --- /dev/null +++ b/tests/components/tcs34725/test.esp32-c3.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 5 + sda: 4 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tcs34725/test.esp32-idf.yaml b/tests/components/tcs34725/test.esp32-idf.yaml new file mode 100644 index 000000000000..86ef82962e0d --- /dev/null +++ b/tests/components/tcs34725/test.esp32-idf.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 16 + sda: 17 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tcs34725/test.esp32.yaml b/tests/components/tcs34725/test.esp32.yaml new file mode 100644 index 000000000000..86ef82962e0d --- /dev/null +++ b/tests/components/tcs34725/test.esp32.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 16 + sda: 17 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tcs34725/test.esp8266.yaml b/tests/components/tcs34725/test.esp8266.yaml new file mode 100644 index 000000000000..9b459c910416 --- /dev/null +++ b/tests/components/tcs34725/test.esp8266.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 5 + sda: 4 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tcs34725/test.rp2040.yaml b/tests/components/tcs34725/test.rp2040.yaml new file mode 100644 index 000000000000..9b459c910416 --- /dev/null +++ b/tests/components/tcs34725/test.rp2040.yaml @@ -0,0 +1,21 @@ +i2c: + - id: i2c_tcs34725 + scl: 5 + sda: 4 + +sensor: + - platform: tcs34725 + red_channel: + name: Red Channel + green_channel: + name: Green Channel + blue_channel: + name: Blue Channel + clear_channel: + name: Clear Channel + illuminance: + name: Illuminance + color_temperature: + name: Color Temperature + integration_time: 614ms + gain: 60x diff --git a/tests/components/tee501/test.esp32-c3-idf.yaml b/tests/components/tee501/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..11991a615372 --- /dev/null +++ b/tests/components/tee501/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 5 + sda: 4 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/tee501/test.esp32-c3.yaml b/tests/components/tee501/test.esp32-c3.yaml new file mode 100644 index 000000000000..11991a615372 --- /dev/null +++ b/tests/components/tee501/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 5 + sda: 4 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/tee501/test.esp32-idf.yaml b/tests/components/tee501/test.esp32-idf.yaml new file mode 100644 index 000000000000..acf6fed4bfe0 --- /dev/null +++ b/tests/components/tee501/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 16 + sda: 17 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/tee501/test.esp32.yaml b/tests/components/tee501/test.esp32.yaml new file mode 100644 index 000000000000..acf6fed4bfe0 --- /dev/null +++ b/tests/components/tee501/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 16 + sda: 17 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/tee501/test.esp8266.yaml b/tests/components/tee501/test.esp8266.yaml new file mode 100644 index 000000000000..11991a615372 --- /dev/null +++ b/tests/components/tee501/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 5 + sda: 4 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/tee501/test.rp2040.yaml b/tests/components/tee501/test.rp2040.yaml new file mode 100644 index 000000000000..11991a615372 --- /dev/null +++ b/tests/components/tee501/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tee501 + scl: 5 + sda: 4 + +sensor: + - platform: tee501 + name: TEE501 Temperature + address: 0x48 diff --git a/tests/components/teleinfo/test.esp32-c3-idf.yaml b/tests/components/teleinfo/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..55641e1e01d9 --- /dev/null +++ b/tests/components/teleinfo/test.esp32-c3-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/teleinfo/test.esp32-c3.yaml b/tests/components/teleinfo/test.esp32-c3.yaml new file mode 100644 index 000000000000..55641e1e01d9 --- /dev/null +++ b/tests/components/teleinfo/test.esp32-c3.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/teleinfo/test.esp32-idf.yaml b/tests/components/teleinfo/test.esp32-idf.yaml new file mode 100644 index 000000000000..a5bd17614388 --- /dev/null +++ b/tests/components/teleinfo/test.esp32-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 17 + rx_pin: 16 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/teleinfo/test.esp32.yaml b/tests/components/teleinfo/test.esp32.yaml new file mode 100644 index 000000000000..a5bd17614388 --- /dev/null +++ b/tests/components/teleinfo/test.esp32.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 17 + rx_pin: 16 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/teleinfo/test.esp8266.yaml b/tests/components/teleinfo/test.esp8266.yaml new file mode 100644 index 000000000000..55641e1e01d9 --- /dev/null +++ b/tests/components/teleinfo/test.esp8266.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/teleinfo/test.rp2040.yaml b/tests/components/teleinfo/test.rp2040.yaml new file mode 100644 index 000000000000..55641e1e01d9 --- /dev/null +++ b/tests/components/teleinfo/test.rp2040.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_teleinfo + tx_pin: 4 + rx_pin: 5 + baud_rate: 1200 + parity: EVEN + +button: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: test_teleinfo + - delay: 20s + - component.update: test_teleinfo + - delay: 20s + - component.resume: test_teleinfo + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: test_teleinfo + update_interval: !lambda return 2500; + +teleinfo: + id: test_teleinfo + historical_mode: true + update_interval: 60s + +sensor: + - platform: teleinfo + name: hchc + tag_name: HCHC + teleinfo_id: test_teleinfo + unit_of_measurement: Wh + +text_sensor: + - platform: teleinfo + name: optarif + tag_name: OPTARIF + teleinfo_id: test_teleinfo diff --git a/tests/components/template/common.yaml b/tests/components/template/common.yaml new file mode 100644 index 000000000000..9e89424d8a68 --- /dev/null +++ b/tests/components/template/common.yaml @@ -0,0 +1,221 @@ +sensor: + - platform: template + name: "Template Sensor" + id: template_sens + lambda: |- + if (id(some_binary_sensor).state) { + return 42.0; + } else { + return 0.0; + } + update_interval: 60s + +esphome: + on_boot: + - sensor.template.publish: + id: template_sens + state: 42.0 + + # Templated + - sensor.template.publish: + id: template_sens + state: !lambda "return 42.0;" + + - datetime.date.set: + id: test_date + date: + year: 2021 + month: 1 + day: 1 + - datetime.date.set: + id: test_date + date: !lambda "return {.day_of_month = 1, .month = 1, .year = 2021};" + - datetime.date.set: + id: test_date + date: "2021-01-01" + +binary_sensor: + - platform: template + id: some_binary_sensor + name: "Garage Door Open" + lambda: |- + if (id(template_sens).state > 30) { + // Garage Door is open. + return true; + } else { + // Garage Door is closed. + return false; + } + +output: + - platform: template + id: outputsplit + type: float + write_action: + - logger.log: "write_action" + +switch: + - platform: template + name: "Template Switch" + lambda: |- + if (id(some_binary_sensor).state) { + return true; + } else { + return false; + } + turn_on_action: + - logger.log: "turn_on_action" + turn_off_action: + - logger.log: "turn_off_action" + +button: + - platform: template + name: "Template Button" + on_press: + - logger.log: Button Pressed + +cover: + - platform: template + name: "Template Cover" + lambda: |- + if (id(some_binary_sensor).state) { + return COVER_OPEN; + } else { + return COVER_CLOSED; + } + open_action: + - logger.log: open_action + close_action: + - logger.log: close_action + stop_action: + - logger.log: stop_action + optimistic: true + +number: + - platform: template + name: "Template number" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + +select: + - platform: template + name: "Template select" + optimistic: true + options: + - one + - two + - three + initial_option: two + +lock: + - platform: template + name: "Template Lock" + lambda: |- + if (id(some_binary_sensor).state) { + return LOCK_STATE_LOCKED; + } else { + return LOCK_STATE_UNLOCKED; + } + lock_action: + - logger.log: lock_action + unlock_action: + - logger.log: unlock_action + open_action: + - logger.log: open_action + +valve: + - platform: template + name: "Template Valve" + lambda: |- + if (id(some_binary_sensor).state) { + return VALVE_OPEN; + } else { + return VALVE_CLOSED; + } + open_action: + - logger.log: open_action + close_action: + - logger.log: close_action + stop_action: + - logger.log: stop_action + optimistic: true + +text: + - platform: template + name: "Template text" + optimistic: true + min_length: 0 + max_length: 100 + mode: text + - platform: template + name: "Template text lambda" + mode: text + update_interval: 1s + lambda: | + return std::string{"Hello!"}; + set_action: + then: + - logger.log: + format: Template Text set to %s + args: ["x.c_str()"] + +alarm_control_panel: + - platform: template + name: Alarm Panel + codes: + - "1234" + +datetime: + - platform: template + name: Date + id: test_date + type: date + initial_value: "2000-1-2" + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "Date: %04d-%02d-%02d" + args: + - x.year + - x.month + - x.day_of_month + - platform: template + name: Time + id: test_time + type: time + initial_value: "12:34:56am" + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "Time: %02d:%02d:%02d" + args: + - x.hour + - x.minute + - x.second + - platform: template + name: DateTime + id: test_datetime + type: datetime + initial_value: "2000-1-2 12:34:56" + set_action: + - logger.log: "set_value" + on_value: + - logger.log: + format: "DateTime: %04d-%02d-%02d %02d:%02d:%02d" + args: + - x.year + - x.month + - x.day_of_month + - x.hour + - x.minute + - x.second + +time: + - platform: sntp # Required for datetime + +wifi: # Required for sntp time + ap: diff --git a/tests/components/template/test.bk72xx.yaml b/tests/components/template/test.bk72xx.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.bk72xx.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp32-c3-idf.yaml b/tests/components/template/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp32-c3.yaml b/tests/components/template/test.esp32-c3.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp32-c3.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp32-idf.yaml b/tests/components/template/test.esp32-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp32-s3-idf.yaml b/tests/components/template/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp32-s3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp32.yaml b/tests/components/template/test.esp32.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp32.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.esp8266.yaml b/tests/components/template/test.esp8266.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.esp8266.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/template/test.rp2040.yaml b/tests/components/template/test.rp2040.yaml new file mode 100644 index 000000000000..25cb37a0b421 --- /dev/null +++ b/tests/components/template/test.rp2040.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/thermostat/common.yaml b/tests/components/thermostat/common.yaml new file mode 100644 index 000000000000..d630a93efc3f --- /dev/null +++ b/tests/components/thermostat/common.yaml @@ -0,0 +1,93 @@ +sensor: + - platform: template + id: thermostat_sensor + lambda: "return 21;" + +climate: + - platform: thermostat + name: Test Thermostat + sensor: thermostat_sensor + humidity_sensor: thermostat_sensor + preset: + - name: Default Preset + default_target_temperature_low: 18°C + default_target_temperature_high: 24°C + - name: Away + default_target_temperature_low: 16°C + default_target_temperature_high: 20°C + idle_action: + - logger.log: idle_action + cool_action: + - logger.log: cool_action + supplemental_cooling_action: + - logger.log: supplemental_cooling_action + heat_action: + - logger.log: heat_action + supplemental_heating_action: + - logger.log: supplemental_heating_action + dry_action: + - logger.log: dry_action + fan_only_action: + - logger.log: fan_only_action + auto_mode: + - logger.log: auto_mode + off_mode: + - logger.log: off_mode + heat_mode: + - logger.log: heat_mode + cool_mode: + - logger.log: cool_mode + dry_mode: + - logger.log: dry_mode + fan_only_mode: + - logger.log: fan_only_mode + fan_mode_auto_action: + - logger.log: fan_mode_auto_action + fan_mode_on_action: + - logger.log: fan_mode_on_action + fan_mode_off_action: + - logger.log: fan_mode_off_action + fan_mode_low_action: + - logger.log: fan_mode_low_action + fan_mode_medium_action: + - logger.log: fan_mode_medium_action + fan_mode_high_action: + - logger.log: fan_mode_high_action + fan_mode_middle_action: + - logger.log: fan_mode_middle_action + fan_mode_focus_action: + - logger.log: fan_mode_focus_action + fan_mode_diffuse_action: + - logger.log: fan_mode_diffuse_action + fan_mode_quiet_action: + - logger.log: fan_mode_quiet_action + swing_off_action: + - logger.log: swing_off_action + swing_horizontal_action: + - logger.log: swing_horizontal_action + swing_vertical_action: + - logger.log: swing_vertical_action + swing_both_action: + - logger.log: swing_both_action + startup_delay: true + supplemental_cooling_delta: 2.0 + cool_deadband: 0.5 + cool_overrun: 0.5 + min_cooling_off_time: 300s + min_cooling_run_time: 300s + max_cooling_run_time: 600s + supplemental_heating_delta: 2.0 + heat_deadband: 0.5 + heat_overrun: 0.5 + min_heating_off_time: 300s + min_heating_run_time: 300s + max_heating_run_time: 600s + min_fanning_off_time: 30s + min_fanning_run_time: 30s + min_fan_mode_switching_time: 15s + min_idle_time: 30s + set_point_minimum_differential: 0.5 + fan_only_action_uses_fan_mode_timer: true + fan_only_cooling: true + fan_with_cooling: true + fan_with_heating: true diff --git a/tests/components/thermostat/test.esp32-c3-idf.yaml b/tests/components/thermostat/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/thermostat/test.esp32-c3.yaml b/tests/components/thermostat/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/thermostat/test.esp32-idf.yaml b/tests/components/thermostat/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/thermostat/test.esp32.yaml b/tests/components/thermostat/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/thermostat/test.esp8266.yaml b/tests/components/thermostat/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/thermostat/test.rp2040.yaml b/tests/components/thermostat/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/thermostat/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/common.yaml b/tests/components/time/common.yaml new file mode 100644 index 000000000000..465be045dbdc --- /dev/null +++ b/tests/components/time/common.yaml @@ -0,0 +1,10 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +time: + - platform: homeassistant + - platform: sntp + id: sntp_time diff --git a/tests/components/time/test.esp32-c3-idf.yaml b/tests/components/time/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/test.esp32-c3.yaml b/tests/components/time/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/test.esp32-idf.yaml b/tests/components/time/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/test.esp32.yaml b/tests/components/time/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/test.esp8266.yaml b/tests/components/time/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time/test.rp2040.yaml b/tests/components/time/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/common.yaml b/tests/components/time_based/common.yaml new file mode 100644 index 000000000000..48c86de90f43 --- /dev/null +++ b/tests/components/time_based/common.yaml @@ -0,0 +1,12 @@ +cover: + - platform: time_based + name: Time Based Cover + id: time_based_cover + stop_action: + - logger.log: stop_action + open_action: + - logger.log: open_action + open_duration: 5min + close_action: + - logger.log: close_action + close_duration: 4.5min diff --git a/tests/components/time_based/test.esp32-c3-idf.yaml b/tests/components/time_based/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/test.esp32-c3.yaml b/tests/components/time_based/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/test.esp32-idf.yaml b/tests/components/time_based/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/test.esp32.yaml b/tests/components/time_based/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/test.esp8266.yaml b/tests/components/time_based/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/time_based/test.rp2040.yaml b/tests/components/time_based/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/time_based/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tlc59208f/test.esp32-c3-idf.yaml b/tests/components/tlc59208f/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..923ea4b4a455 --- /dev/null +++ b/tests/components/tlc59208f/test.esp32-c3-idf.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 5 + sda: 4 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc59208f/test.esp32-c3.yaml b/tests/components/tlc59208f/test.esp32-c3.yaml new file mode 100644 index 000000000000..923ea4b4a455 --- /dev/null +++ b/tests/components/tlc59208f/test.esp32-c3.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 5 + sda: 4 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc59208f/test.esp32-idf.yaml b/tests/components/tlc59208f/test.esp32-idf.yaml new file mode 100644 index 000000000000..2639de3b3d46 --- /dev/null +++ b/tests/components/tlc59208f/test.esp32-idf.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 16 + sda: 17 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc59208f/test.esp32.yaml b/tests/components/tlc59208f/test.esp32.yaml new file mode 100644 index 000000000000..2639de3b3d46 --- /dev/null +++ b/tests/components/tlc59208f/test.esp32.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 16 + sda: 17 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc59208f/test.esp8266.yaml b/tests/components/tlc59208f/test.esp8266.yaml new file mode 100644 index 000000000000..923ea4b4a455 --- /dev/null +++ b/tests/components/tlc59208f/test.esp8266.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 5 + sda: 4 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc59208f/test.rp2040.yaml b/tests/components/tlc59208f/test.rp2040.yaml new file mode 100644 index 000000000000..923ea4b4a455 --- /dev/null +++ b/tests/components/tlc59208f/test.rp2040.yaml @@ -0,0 +1,50 @@ +i2c: + - id: i2c_tlc59208f + scl: 5 + sda: 4 + +tlc59208f: + - address: 0x20 + id: tlc59208f_1 + - address: 0x22 + id: tlc59208f_2 + - address: 0x24 + id: tlc59208f_3 + +output: + - platform: tlc59208f + id: tlc_0 + channel: 0 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_1 + channel: 1 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_2 + channel: 2 + tlc59208f_id: tlc59208f_1 + - platform: tlc59208f + id: tlc_3 + channel: 0 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_4 + channel: 1 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_5 + channel: 2 + tlc59208f_id: tlc59208f_2 + - platform: tlc59208f + id: tlc_6 + channel: 0 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_7 + channel: 1 + tlc59208f_id: tlc59208f_3 + - platform: tlc59208f + id: tlc_8 + channel: 2 + tlc59208f_id: tlc59208f_3 diff --git a/tests/components/tlc5947/common.yaml b/tests/components/tlc5947/common.yaml new file mode 100644 index 000000000000..89588f3c76a5 --- /dev/null +++ b/tests/components/tlc5947/common.yaml @@ -0,0 +1,13 @@ +tlc5947: + clock_pin: ${clock_pin} + data_pin: ${data_pin} + lat_pin: ${lat_pin} + +output: + - platform: tlc5947 + id: output_1 + channel: 0 + max_power: 0.8 + - platform: tlc5947 + id: output_2 + channel: 1 diff --git a/tests/components/tlc5947/test.esp32-c3-idf.yaml b/tests/components/tlc5947/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4694c43642c5 --- /dev/null +++ b/tests/components/tlc5947/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + lat_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5947/test.esp32-c3.yaml b/tests/components/tlc5947/test.esp32-c3.yaml new file mode 100644 index 000000000000..4694c43642c5 --- /dev/null +++ b/tests/components/tlc5947/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + lat_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5947/test.esp32-idf.yaml b/tests/components/tlc5947/test.esp32-idf.yaml new file mode 100644 index 000000000000..52411bc1e9b7 --- /dev/null +++ b/tests/components/tlc5947/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO15 + data_pin: GPIO14 + lat_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5947/test.esp32.yaml b/tests/components/tlc5947/test.esp32.yaml new file mode 100644 index 000000000000..52411bc1e9b7 --- /dev/null +++ b/tests/components/tlc5947/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO15 + data_pin: GPIO14 + lat_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5947/test.esp8266.yaml b/tests/components/tlc5947/test.esp8266.yaml new file mode 100644 index 000000000000..44da5a07b32a --- /dev/null +++ b/tests/components/tlc5947/test.esp8266.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + lat_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5947/test.rp2040.yaml b/tests/components/tlc5947/test.rp2040.yaml new file mode 100644 index 000000000000..4694c43642c5 --- /dev/null +++ b/tests/components/tlc5947/test.rp2040.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + lat_pin: GPIO3 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/common.yaml b/tests/components/tlc5971/common.yaml new file mode 100644 index 000000000000..fe7fe25f0ee7 --- /dev/null +++ b/tests/components/tlc5971/common.yaml @@ -0,0 +1,12 @@ +tlc5971: + clock_pin: ${clock_pin} + data_pin: ${data_pin} + +output: + - platform: tlc5971 + id: output_1 + channel: 0 + max_power: 0.8 + - platform: tlc5971 + id: output_2 + channel: 1 diff --git a/tests/components/tlc5971/test.esp32-c3-idf.yaml b/tests/components/tlc5971/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d898a21d4692 --- /dev/null +++ b/tests/components/tlc5971/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp32-c3.yaml b/tests/components/tlc5971/test.esp32-c3.yaml new file mode 100644 index 000000000000..d898a21d4692 --- /dev/null +++ b/tests/components/tlc5971/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp32-idf.yaml b/tests/components/tlc5971/test.esp32-idf.yaml new file mode 100644 index 000000000000..e8943a572a19 --- /dev/null +++ b/tests/components/tlc5971/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + clock_pin: GPIO15 + data_pin: GPIO14 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp32-s2.yaml b/tests/components/tlc5971/test.esp32-s2.yaml new file mode 100644 index 000000000000..7bba0b01170b --- /dev/null +++ b/tests/components/tlc5971/test.esp32-s2.yaml @@ -0,0 +1,6 @@ +substitutions: + clock_pin: GPIO36 + data_pin: GPIO35 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp32.yaml b/tests/components/tlc5971/test.esp32.yaml new file mode 100644 index 000000000000..52411bc1e9b7 --- /dev/null +++ b/tests/components/tlc5971/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO15 + data_pin: GPIO14 + lat_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.esp8266.yaml b/tests/components/tlc5971/test.esp8266.yaml new file mode 100644 index 000000000000..52411bc1e9b7 --- /dev/null +++ b/tests/components/tlc5971/test.esp8266.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO15 + data_pin: GPIO14 + lat_pin: GPIO13 + +packages: + common: !include common.yaml diff --git a/tests/components/tlc5971/test.rp2040.yaml b/tests/components/tlc5971/test.rp2040.yaml new file mode 100644 index 000000000000..4694c43642c5 --- /dev/null +++ b/tests/components/tlc5971/test.rp2040.yaml @@ -0,0 +1,7 @@ +substitutions: + clock_pin: GPIO5 + data_pin: GPIO4 + lat_pin: GPIO3 + +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 new file mode 100644 index 000000000000..cddd64f31f65 --- /dev/null +++ b/tests/components/tm1621/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 7 + data_pin: 4 + read_pin: 5 + write_pin: 6 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1621/test.esp32-c3.yaml b/tests/components/tm1621/test.esp32-c3.yaml new file mode 100644 index 000000000000..cddd64f31f65 --- /dev/null +++ b/tests/components/tm1621/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 7 + data_pin: 4 + read_pin: 5 + write_pin: 6 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1621/test.esp32-idf.yaml b/tests/components/tm1621/test.esp32-idf.yaml new file mode 100644 index 000000000000..8eab46f0001b --- /dev/null +++ b/tests/components/tm1621/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 15 + data_pin: 14 + read_pin: 12 + write_pin: 13 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1621/test.esp32.yaml b/tests/components/tm1621/test.esp32.yaml new file mode 100644 index 000000000000..8eab46f0001b --- /dev/null +++ b/tests/components/tm1621/test.esp32.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 15 + data_pin: 14 + read_pin: 12 + write_pin: 13 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1621/test.esp8266.yaml b/tests/components/tm1621/test.esp8266.yaml new file mode 100644 index 000000000000..8eab46f0001b --- /dev/null +++ b/tests/components/tm1621/test.esp8266.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 15 + data_pin: 14 + read_pin: 12 + write_pin: 13 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1621/test.rp2040.yaml b/tests/components/tm1621/test.rp2040.yaml new file mode 100644 index 000000000000..cddd64f31f65 --- /dev/null +++ b/tests/components/tm1621/test.rp2040.yaml @@ -0,0 +1,12 @@ +display: + - platform: tm1621 + id: tm1621_display + cs_pin: 7 + data_pin: 4 + read_pin: 5 + write_pin: 6 + lambda: |- + it.printf(0, "%.1f", 20.0); + it.display_celsius(true); + it.printf(1, "%.1f", 20.0); + it.display_humidity(true); diff --git a/tests/components/tm1637/test.esp32-c3-idf.yaml b/tests/components/tm1637/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..fa4c95b443ed --- /dev/null +++ b/tests/components/tm1637/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 5 + dio_pin: 4 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1637/test.esp32-c3.yaml b/tests/components/tm1637/test.esp32-c3.yaml new file mode 100644 index 000000000000..fa4c95b443ed --- /dev/null +++ b/tests/components/tm1637/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 5 + dio_pin: 4 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1637/test.esp32-idf.yaml b/tests/components/tm1637/test.esp32-idf.yaml new file mode 100644 index 000000000000..bf5f331ccac6 --- /dev/null +++ b/tests/components/tm1637/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 15 + dio_pin: 14 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1637/test.esp32.yaml b/tests/components/tm1637/test.esp32.yaml new file mode 100644 index 000000000000..bf5f331ccac6 --- /dev/null +++ b/tests/components/tm1637/test.esp32.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 15 + dio_pin: 14 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1637/test.esp8266.yaml b/tests/components/tm1637/test.esp8266.yaml new file mode 100644 index 000000000000..fa4c95b443ed --- /dev/null +++ b/tests/components/tm1637/test.esp8266.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 5 + dio_pin: 4 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1637/test.rp2040.yaml b/tests/components/tm1637/test.rp2040.yaml new file mode 100644 index 000000000000..fa4c95b443ed --- /dev/null +++ b/tests/components/tm1637/test.rp2040.yaml @@ -0,0 +1,7 @@ +display: + - platform: tm1637 + clk_pin: 5 + dio_pin: 4 + intensity: 3 + lambda: |- + it.print("1234"); diff --git a/tests/components/tm1638/common.yaml b/tests/components/tm1638/common.yaml new file mode 100644 index 000000000000..b0c5cef528cd --- /dev/null +++ b/tests/components/tm1638/common.yaml @@ -0,0 +1,118 @@ +display: + - platform: tm1638 + id: tm1638_display + stb_pin: 2 + clk_pin: 5 + dio_pin: 4 + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); + +binary_sensor: + - platform: tm1638 + id: Button0 + key: 0 + filters: + - delayed_on: 10ms + on_press: + then: + - switch.turn_on: Led0 + on_release: + then: + - switch.turn_off: Led0 + - platform: tm1638 + id: Button1 + key: 1 + on_press: + then: + - switch.turn_on: Led1 + on_release: + then: + - switch.turn_off: Led1 + - platform: tm1638 + id: Button2 + key: 2 + on_press: + then: + - switch.turn_on: Led2 + on_release: + then: + - switch.turn_off: Led2 + - platform: tm1638 + id: Button3 + key: 3 + on_press: + then: + - switch.turn_on: Led3 + on_release: + then: + - switch.turn_off: Led3 + - platform: tm1638 + id: Button4 + key: 4 + on_press: + then: + - output.turn_on: Led4 + on_release: + then: + - output.turn_off: Led4 + - platform: tm1638 + id: Button5 + key: 5 + on_press: + then: + - output.turn_on: Led5 + on_release: + then: + - output.turn_off: Led5 + - platform: tm1638 + id: Button6 + key: 6 + on_press: + then: + - output.turn_on: Led6 + on_release: + then: + - output.turn_off: Led6 + - platform: tm1638 + id: Button7 + key: 7 + on_press: + then: + - output.turn_on: Led7 + on_release: + then: + - output.turn_off: Led7 + +switch: + - platform: tm1638 + id: Led0 + led: 0 + name: TM1638Led0 + - platform: tm1638 + id: Led1 + led: 1 + name: TM1638Led1 + - platform: tm1638 + id: Led2 + led: 2 + name: TM1638Led2 + - platform: tm1638 + id: Led3 + led: 3 + name: TM1638Led3 + +output: + - platform: tm1638 + id: Led4 + led: 4 + - platform: tm1638 + id: Led5 + led: 5 + - platform: tm1638 + id: Led6 + led: 6 + - platform: tm1638 + id: Led7 + led: 7 diff --git a/tests/components/tm1638/test.esp32-c3-idf.yaml b/tests/components/tm1638/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1638/test.esp32-c3.yaml b/tests/components/tm1638/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1638/test.esp32-idf.yaml b/tests/components/tm1638/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1638/test.esp32.yaml b/tests/components/tm1638/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1638/test.esp8266.yaml b/tests/components/tm1638/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1638/test.rp2040.yaml b/tests/components/tm1638/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1638/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1651/common.yaml b/tests/components/tm1651/common.yaml new file mode 100644 index 000000000000..667648f4d60a --- /dev/null +++ b/tests/components/tm1651/common.yaml @@ -0,0 +1,21 @@ +tm1651: + id: tm1651_battery + clk_pin: 5 + dio_pin: 4 + +esphome: + on_boot: + then: + - tm1651.set_level_percent: + id: tm1651_battery + level_percent: 50 + - tm1651.set_level: + id: tm1651_battery + level: 5 + - tm1651.set_brightness: + id: tm1651_battery + brightness: 2 + - tm1651.turn_on: + id: tm1651_battery + - tm1651.turn_off: + id: tm1651_battery diff --git a/tests/components/tm1651/test.esp32-c3.yaml b/tests/components/tm1651/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1651/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1651/test.esp32.yaml b/tests/components/tm1651/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1651/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1651/test.esp8266.yaml b/tests/components/tm1651/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1651/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tm1651/test.rp2040.yaml b/tests/components/tm1651/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tm1651/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tmp102/test.esp32-c3-idf.yaml b/tests/components/tmp102/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c1d35fca3f7d --- /dev/null +++ b/tests/components/tmp102/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 5 + sda: 4 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp102/test.esp32-c3.yaml b/tests/components/tmp102/test.esp32-c3.yaml new file mode 100644 index 000000000000..c1d35fca3f7d --- /dev/null +++ b/tests/components/tmp102/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 5 + sda: 4 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp102/test.esp32-idf.yaml b/tests/components/tmp102/test.esp32-idf.yaml new file mode 100644 index 000000000000..840bf7edb3e2 --- /dev/null +++ b/tests/components/tmp102/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 16 + sda: 17 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp102/test.esp32.yaml b/tests/components/tmp102/test.esp32.yaml new file mode 100644 index 000000000000..840bf7edb3e2 --- /dev/null +++ b/tests/components/tmp102/test.esp32.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 16 + sda: 17 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp102/test.esp8266.yaml b/tests/components/tmp102/test.esp8266.yaml new file mode 100644 index 000000000000..c1d35fca3f7d --- /dev/null +++ b/tests/components/tmp102/test.esp8266.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 5 + sda: 4 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp102/test.rp2040.yaml b/tests/components/tmp102/test.rp2040.yaml new file mode 100644 index 000000000000..c1d35fca3f7d --- /dev/null +++ b/tests/components/tmp102/test.rp2040.yaml @@ -0,0 +1,8 @@ +i2c: + - id: i2c_tmp102 + scl: 5 + sda: 4 + +sensor: + - platform: tmp102 + name: TMP102 Temperature diff --git a/tests/components/tmp1075/test.esp32-c3-idf.yaml b/tests/components/tmp1075/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..99433aa655f4 --- /dev/null +++ b/tests/components/tmp1075/test.esp32-c3-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 5 + sda: 4 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp1075/test.esp32-c3.yaml b/tests/components/tmp1075/test.esp32-c3.yaml new file mode 100644 index 000000000000..99433aa655f4 --- /dev/null +++ b/tests/components/tmp1075/test.esp32-c3.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 5 + sda: 4 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp1075/test.esp32-idf.yaml b/tests/components/tmp1075/test.esp32-idf.yaml new file mode 100644 index 000000000000..6c50d0da773c --- /dev/null +++ b/tests/components/tmp1075/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 16 + sda: 17 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp1075/test.esp32.yaml b/tests/components/tmp1075/test.esp32.yaml new file mode 100644 index 000000000000..6c50d0da773c --- /dev/null +++ b/tests/components/tmp1075/test.esp32.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 16 + sda: 17 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp1075/test.esp8266.yaml b/tests/components/tmp1075/test.esp8266.yaml new file mode 100644 index 000000000000..99433aa655f4 --- /dev/null +++ b/tests/components/tmp1075/test.esp8266.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 5 + sda: 4 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp1075/test.rp2040.yaml b/tests/components/tmp1075/test.rp2040.yaml new file mode 100644 index 000000000000..99433aa655f4 --- /dev/null +++ b/tests/components/tmp1075/test.rp2040.yaml @@ -0,0 +1,16 @@ +i2c: + - id: i2c_tmp1075 + scl: 5 + sda: 4 + +sensor: + - platform: tmp1075 + name: Temperature TMP1075 + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator + update_interval: 10s diff --git a/tests/components/tmp117/test.esp32-c3-idf.yaml b/tests/components/tmp117/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..61fc2cc03d6e --- /dev/null +++ b/tests/components/tmp117/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 5 + sda: 4 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tmp117/test.esp32-c3.yaml b/tests/components/tmp117/test.esp32-c3.yaml new file mode 100644 index 000000000000..61fc2cc03d6e --- /dev/null +++ b/tests/components/tmp117/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 5 + sda: 4 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tmp117/test.esp32-idf.yaml b/tests/components/tmp117/test.esp32-idf.yaml new file mode 100644 index 000000000000..03e0dd4e8ebf --- /dev/null +++ b/tests/components/tmp117/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 16 + sda: 17 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tmp117/test.esp32.yaml b/tests/components/tmp117/test.esp32.yaml new file mode 100644 index 000000000000..03e0dd4e8ebf --- /dev/null +++ b/tests/components/tmp117/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 16 + sda: 17 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tmp117/test.esp8266.yaml b/tests/components/tmp117/test.esp8266.yaml new file mode 100644 index 000000000000..61fc2cc03d6e --- /dev/null +++ b/tests/components/tmp117/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 5 + sda: 4 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tmp117/test.rp2040.yaml b/tests/components/tmp117/test.rp2040.yaml new file mode 100644 index 000000000000..61fc2cc03d6e --- /dev/null +++ b/tests/components/tmp117/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tmp117 + scl: 5 + sda: 4 + +sensor: + - platform: tmp117 + name: TMP117 Temperature + update_interval: 5s diff --git a/tests/components/tof10120/test.esp32-c3-idf.yaml b/tests/components/tof10120/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..01cde0df6aff --- /dev/null +++ b/tests/components/tof10120/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 5 + sda: 4 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/tof10120/test.esp32-c3.yaml b/tests/components/tof10120/test.esp32-c3.yaml new file mode 100644 index 000000000000..01cde0df6aff --- /dev/null +++ b/tests/components/tof10120/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 5 + sda: 4 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/tof10120/test.esp32-idf.yaml b/tests/components/tof10120/test.esp32-idf.yaml new file mode 100644 index 000000000000..74541ecde8e4 --- /dev/null +++ b/tests/components/tof10120/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 16 + sda: 17 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/tof10120/test.esp32.yaml b/tests/components/tof10120/test.esp32.yaml new file mode 100644 index 000000000000..74541ecde8e4 --- /dev/null +++ b/tests/components/tof10120/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 16 + sda: 17 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/tof10120/test.esp8266.yaml b/tests/components/tof10120/test.esp8266.yaml new file mode 100644 index 000000000000..01cde0df6aff --- /dev/null +++ b/tests/components/tof10120/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 5 + sda: 4 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/tof10120/test.rp2040.yaml b/tests/components/tof10120/test.rp2040.yaml new file mode 100644 index 000000000000..01cde0df6aff --- /dev/null +++ b/tests/components/tof10120/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_tof10120 + scl: 5 + sda: 4 + +sensor: + - platform: tof10120 + name: Distance sensor + update_interval: 5s diff --git a/tests/components/toshiba/test.esp32-c3-idf.yaml b/tests/components/toshiba/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..c134c7f5bda2 --- /dev/null +++ b/tests/components/toshiba/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: toshiba + name: Toshiba Climate diff --git a/tests/components/toshiba/test.esp32-c3.yaml b/tests/components/toshiba/test.esp32-c3.yaml new file mode 100644 index 000000000000..c134c7f5bda2 --- /dev/null +++ b/tests/components/toshiba/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: toshiba + name: Toshiba Climate diff --git a/tests/components/toshiba/test.esp32-idf.yaml b/tests/components/toshiba/test.esp32-idf.yaml new file mode 100644 index 000000000000..c134c7f5bda2 --- /dev/null +++ b/tests/components/toshiba/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: toshiba + name: Toshiba Climate diff --git a/tests/components/toshiba/test.esp32.yaml b/tests/components/toshiba/test.esp32.yaml new file mode 100644 index 000000000000..c134c7f5bda2 --- /dev/null +++ b/tests/components/toshiba/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: toshiba + name: Toshiba Climate diff --git a/tests/components/toshiba/test.esp8266.yaml b/tests/components/toshiba/test.esp8266.yaml new file mode 100644 index 000000000000..8730a5d4ab5a --- /dev/null +++ b/tests/components/toshiba/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: toshiba + name: Toshiba Climate diff --git a/tests/components/total_daily_energy/test.esp32-c3-idf.yaml b/tests/components/total_daily_energy/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..71afa45ed53e --- /dev/null +++ b/tests/components/total_daily_energy/test.esp32-c3-idf.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 5 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/total_daily_energy/test.esp32-c3.yaml b/tests/components/total_daily_energy/test.esp32-c3.yaml new file mode 100644 index 000000000000..71afa45ed53e --- /dev/null +++ b/tests/components/total_daily_energy/test.esp32-c3.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 5 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/total_daily_energy/test.esp32-idf.yaml b/tests/components/total_daily_energy/test.esp32-idf.yaml new file mode 100644 index 000000000000..34d452aae51a --- /dev/null +++ b/tests/components/total_daily_energy/test.esp32-idf.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 15 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/total_daily_energy/test.esp32.yaml b/tests/components/total_daily_energy/test.esp32.yaml new file mode 100644 index 000000000000..34d452aae51a --- /dev/null +++ b/tests/components/total_daily_energy/test.esp32.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 15 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/total_daily_energy/test.esp8266.yaml b/tests/components/total_daily_energy/test.esp8266.yaml new file mode 100644 index 000000000000..34d452aae51a --- /dev/null +++ b/tests/components/total_daily_energy/test.esp8266.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 15 + cf_pin: 14 + cf1_pin: 13 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/total_daily_energy/test.rp2040.yaml b/tests/components/total_daily_energy/test.rp2040.yaml new file mode 100644 index 000000000000..71afa45ed53e --- /dev/null +++ b/tests/components/total_daily_energy/test.rp2040.yaml @@ -0,0 +1,32 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + id: sntp_time + +sensor: + - platform: hlw8012 + sel_pin: 5 + cf_pin: 4 + cf1_pin: 3 + current: + name: HLW8012 Current + voltage: + name: HLW8012 Voltage + power: + name: HLW8012 Power + id: hlw8012_power + energy: + name: HLW8012 Energy + id: hlw8012_energy + update_interval: 15s + current_resistor: 0.001 ohm + voltage_divider: 2351 + change_mode_every: "never" + initial_mode: VOLTAGE + model: hlw8012 + - platform: total_daily_energy + name: HLW8012 Total Daily Energy + power_id: hlw8012_power diff --git a/tests/components/tsl2561/test.esp32-c3-idf.yaml b/tests/components/tsl2561/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..1ea768c5d942 --- /dev/null +++ b/tests/components/tsl2561/test.esp32-c3-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2561/test.esp32-c3.yaml b/tests/components/tsl2561/test.esp32-c3.yaml new file mode 100644 index 000000000000..1ea768c5d942 --- /dev/null +++ b/tests/components/tsl2561/test.esp32-c3.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2561/test.esp32-idf.yaml b/tests/components/tsl2561/test.esp32-idf.yaml new file mode 100644 index 000000000000..8d43c6241465 --- /dev/null +++ b/tests/components/tsl2561/test.esp32-idf.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 16 + sda: 17 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2561/test.esp32.yaml b/tests/components/tsl2561/test.esp32.yaml new file mode 100644 index 000000000000..8d43c6241465 --- /dev/null +++ b/tests/components/tsl2561/test.esp32.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 16 + sda: 17 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2561/test.esp8266.yaml b/tests/components/tsl2561/test.esp8266.yaml new file mode 100644 index 000000000000..1ea768c5d942 --- /dev/null +++ b/tests/components/tsl2561/test.esp8266.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2561/test.rp2040.yaml b/tests/components/tsl2561/test.rp2040.yaml new file mode 100644 index 000000000000..1ea768c5d942 --- /dev/null +++ b/tests/components/tsl2561/test.rp2040.yaml @@ -0,0 +1,13 @@ +i2c: + - id: i2c_tsl2561 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2561 + name: TSL2561 Ambient Light + address: 0x39 + is_cs_package: true + integration_time: 402ms + gain: 16x + update_interval: 15s diff --git a/tests/components/tsl2591/test.esp32-c3-idf.yaml b/tests/components/tsl2591/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..de57ef548ae1 --- /dev/null +++ b/tests/components/tsl2591/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tsl2591/test.esp32-c3.yaml b/tests/components/tsl2591/test.esp32-c3.yaml new file mode 100644 index 000000000000..de57ef548ae1 --- /dev/null +++ b/tests/components/tsl2591/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tsl2591/test.esp32-idf.yaml b/tests/components/tsl2591/test.esp32-idf.yaml new file mode 100644 index 000000000000..14f9311ae6fe --- /dev/null +++ b/tests/components/tsl2591/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 16 + sda: 17 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tsl2591/test.esp32.yaml b/tests/components/tsl2591/test.esp32.yaml new file mode 100644 index 000000000000..14f9311ae6fe --- /dev/null +++ b/tests/components/tsl2591/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 16 + sda: 17 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tsl2591/test.esp8266.yaml b/tests/components/tsl2591/test.esp8266.yaml new file mode 100644 index 000000000000..de57ef548ae1 --- /dev/null +++ b/tests/components/tsl2591/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tsl2591/test.rp2040.yaml b/tests/components/tsl2591/test.rp2040.yaml new file mode 100644 index 000000000000..de57ef548ae1 --- /dev/null +++ b/tests/components/tsl2591/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tsl2591 + scl: 5 + sda: 4 + +sensor: + - platform: tsl2591 + id: test_tsl2591 + address: 0x29 + integration_time: 600ms + gain: high + visible: + name: tsl2591 visible + id: tsl2591_vis + unit_of_measurement: pH + infrared: + name: tsl2591 infrared + id: tsl2591_ir + full_spectrum: + name: tsl2591 full_spectrum + id: tsl2591_fs + calculated_lux: + name: tsl2591 calculated_lux + id: tsl2591_cl + update_interval: 15s diff --git a/tests/components/tt21100/test.esp32-c3-idf.yaml b/tests/components/tt21100/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..17b8c8065afb --- /dev/null +++ b/tests/components/tt21100/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 6 + reset_pin: 7 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/tt21100/test.esp32-c3.yaml b/tests/components/tt21100/test.esp32-c3.yaml new file mode 100644 index 000000000000..17b8c8065afb --- /dev/null +++ b/tests/components/tt21100/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 6 + reset_pin: 7 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/tt21100/test.esp32-idf.yaml b/tests/components/tt21100/test.esp32-idf.yaml new file mode 100644 index 000000000000..2419b0ad6a40 --- /dev/null +++ b/tests/components/tt21100/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 14 + reset_pin: 15 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/tt21100/test.esp32-s2.yaml b/tests/components/tt21100/test.esp32-s2.yaml new file mode 100644 index 000000000000..7ebabcb1303e --- /dev/null +++ b/tests/components/tt21100/test.esp32-s2.yaml @@ -0,0 +1,43 @@ +i2c: + sda: GPIO8 + scl: GPIO18 + +spi: + clk_pin: 7 + mosi_pin: 11 + miso_pin: 9 + +display: + - platform: ili9xxx + id: my_display + model: ili9341 + cs_pin: 5 + dc_pin: 12 + reset_pin: 33 + auto_clear_enabled: false + data_rate: 40MHz + dimensions: 320x240 + update_interval: never + transform: + mirror_y: false + mirror_x: false + swap_xy: true + +touchscreen: + - platform: tt21100 + address: 0x24 + interrupt_pin: GPIO3 + on_touch: + - logger.log: "Touchscreen:: Touched" + +binary_sensor: + - platform: tt21100 + index: 0 + name: "Home" + + - platform: touchscreen + name: FanLo + x_min: 0 + x_max: 105 + y_min: 0 + y_max: 80 diff --git a/tests/components/tt21100/test.esp32.yaml b/tests/components/tt21100/test.esp32.yaml new file mode 100644 index 000000000000..2419b0ad6a40 --- /dev/null +++ b/tests/components/tt21100/test.esp32.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 16 + sda: 17 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 14 + reset_pin: 15 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/tt21100/test.esp8266.yaml b/tests/components/tt21100/test.esp8266.yaml new file mode 100644 index 000000000000..139301941757 --- /dev/null +++ b/tests/components/tt21100/test.esp8266.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 13 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 14 + reset_pin: 15 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/tt21100/test.rp2040.yaml b/tests/components/tt21100/test.rp2040.yaml new file mode 100644 index 000000000000..17b8c8065afb --- /dev/null +++ b/tests/components/tt21100/test.rp2040.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_tt21100 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 3 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: tt21100 + display: ssd1306_display + interrupt_pin: 6 + reset_pin: 7 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 diff --git a/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml b/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2006061c6e03 --- /dev/null +++ b/tests/components/ttp229_bsf/test.esp32-c3-idf.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 5 + sdo_pin: 4 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_bsf/test.esp32-c3.yaml b/tests/components/ttp229_bsf/test.esp32-c3.yaml new file mode 100644 index 000000000000..2006061c6e03 --- /dev/null +++ b/tests/components/ttp229_bsf/test.esp32-c3.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 5 + sdo_pin: 4 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_bsf/test.esp32-idf.yaml b/tests/components/ttp229_bsf/test.esp32-idf.yaml new file mode 100644 index 000000000000..edee6d164e6b --- /dev/null +++ b/tests/components/ttp229_bsf/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 16 + sdo_pin: 17 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_bsf/test.esp32.yaml b/tests/components/ttp229_bsf/test.esp32.yaml new file mode 100644 index 000000000000..edee6d164e6b --- /dev/null +++ b/tests/components/ttp229_bsf/test.esp32.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 16 + sdo_pin: 17 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_bsf/test.esp8266.yaml b/tests/components/ttp229_bsf/test.esp8266.yaml new file mode 100644 index 000000000000..2006061c6e03 --- /dev/null +++ b/tests/components/ttp229_bsf/test.esp8266.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 5 + sdo_pin: 4 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_bsf/test.rp2040.yaml b/tests/components/ttp229_bsf/test.rp2040.yaml new file mode 100644 index 000000000000..2006061c6e03 --- /dev/null +++ b/tests/components/ttp229_bsf/test.rp2040.yaml @@ -0,0 +1,8 @@ +ttp229_bsf: + scl_pin: 5 + sdo_pin: 4 + +binary_sensor: + - platform: ttp229_bsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml b/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..3927aff40eee --- /dev/null +++ b/tests/components/ttp229_lsf/test.esp32-c3-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 5 + sda: 4 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.esp32-c3.yaml b/tests/components/ttp229_lsf/test.esp32-c3.yaml new file mode 100644 index 000000000000..3927aff40eee --- /dev/null +++ b/tests/components/ttp229_lsf/test.esp32-c3.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 5 + sda: 4 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.esp32-idf.yaml b/tests/components/ttp229_lsf/test.esp32-idf.yaml new file mode 100644 index 000000000000..81fb96588373 --- /dev/null +++ b/tests/components/ttp229_lsf/test.esp32-idf.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 16 + sda: 17 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.esp32.yaml b/tests/components/ttp229_lsf/test.esp32.yaml new file mode 100644 index 000000000000..81fb96588373 --- /dev/null +++ b/tests/components/ttp229_lsf/test.esp32.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 16 + sda: 17 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.esp8266.yaml b/tests/components/ttp229_lsf/test.esp8266.yaml new file mode 100644 index 000000000000..3927aff40eee --- /dev/null +++ b/tests/components/ttp229_lsf/test.esp8266.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 5 + sda: 4 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/ttp229_lsf/test.rp2040.yaml b/tests/components/ttp229_lsf/test.rp2040.yaml new file mode 100644 index 000000000000..3927aff40eee --- /dev/null +++ b/tests/components/ttp229_lsf/test.rp2040.yaml @@ -0,0 +1,11 @@ +i2c: + - id: i2c_ttp229_lsf + scl: 5 + sda: 4 + +ttp229_lsf: + +binary_sensor: + - platform: ttp229_lsf + name: TTP229 Channel 0 + channel: 0 diff --git a/tests/components/tuya/test.esp32-c3-idf.yaml b/tests/components/tuya/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4892e807b147 --- /dev/null +++ b/tests/components/tuya/test.esp32-c3-idf.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +tuya: + status_pin: + number: 6 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tuya/test.esp32-c3.yaml b/tests/components/tuya/test.esp32-c3.yaml new file mode 100644 index 000000000000..4892e807b147 --- /dev/null +++ b/tests/components/tuya/test.esp32-c3.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +tuya: + status_pin: + number: 6 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tuya/test.esp32-idf.yaml b/tests/components/tuya/test.esp32-idf.yaml new file mode 100644 index 000000000000..9105522dcd16 --- /dev/null +++ b/tests/components/tuya/test.esp32-idf.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +tuya: + status_pin: + number: 15 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tuya/test.esp32.yaml b/tests/components/tuya/test.esp32.yaml new file mode 100644 index 000000000000..9105522dcd16 --- /dev/null +++ b/tests/components/tuya/test.esp32.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +tuya: + status_pin: + number: 15 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tuya/test.esp8266.yaml b/tests/components/tuya/test.esp8266.yaml new file mode 100644 index 000000000000..56177fb9825d --- /dev/null +++ b/tests/components/tuya/test.esp8266.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +tuya: + status_pin: + number: 16 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tuya/test.rp2040.yaml b/tests/components/tuya/test.rp2040.yaml new file mode 100644 index 000000000000..4892e807b147 --- /dev/null +++ b/tests/components/tuya/test.rp2040.yaml @@ -0,0 +1,78 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_tuya + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +tuya: + status_pin: + number: 6 + inverted: true + on_datapoint_update: + - sensor_datapoint: 6 + datapoint_type: raw + then: + - logger.log: Datapoint 6 updated + +binary_sensor: + - platform: tuya + id: tuya_binary_sensor + sensor_datapoint: 1 + +climate: + - platform: tuya + id: tuya_climate + switch_datapoint: 1 + target_temperature_datapoint: 3 + current_temperature_multiplier: 0.5 + target_temperature_multiplier: 0.5 + reports_fahrenheit: true + +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + +light: + - platform: tuya + id: tuya_light + switch_datapoint: 1 + dimmer_datapoint: 2 + min_value_datapoint: 3 + color_temperature_datapoint: 4 + min_value: 1 + max_value: 100 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + gamma_correct: 1 + +number: + - platform: tuya + id: tuya_number + number_datapoint: 102 + min_value: 0 + max_value: 17 + step: 1 + +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + +sensor: + - platform: tuya + id: tuya_sensor + sensor_datapoint: 1 + +switch: + - platform: tuya + id: tuya_switch + switch_datapoint: 1 diff --git a/tests/components/tx20/common.yaml b/tests/components/tx20/common.yaml new file mode 100644 index 000000000000..d8260593204e --- /dev/null +++ b/tests/components/tx20/common.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: tx20 + wind_speed: + name: Windspeed + wind_direction_degrees: + name: Winddirection Degrees + pin: 4 diff --git a/tests/components/tx20/test.esp32-c3-idf.yaml b/tests/components/tx20/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tx20/test.esp32-c3.yaml b/tests/components/tx20/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tx20/test.esp32-idf.yaml b/tests/components/tx20/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tx20/test.esp32.yaml b/tests/components/tx20/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tx20/test.esp8266.yaml b/tests/components/tx20/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/tx20/test.rp2040.yaml b/tests/components/tx20/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/tx20/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uart/test.esp32-c3-idf.yaml b/tests/components/uart/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..09178f16639d --- /dev/null +++ b/tests/components/uart/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32-c3.yaml b/tests/components/uart/test.esp32-c3.yaml new file mode 100644 index 000000000000..09178f16639d --- /dev/null +++ b/tests/components/uart/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml new file mode 100644 index 000000000000..bef5b460abef --- /dev/null +++ b/tests/components/uart/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp32.yaml b/tests/components/uart/test.esp32.yaml new file mode 100644 index 000000000000..bef5b460abef --- /dev/null +++ b/tests/components/uart/test.esp32.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.esp8266.yaml b/tests/components/uart/test.esp8266.yaml new file mode 100644 index 000000000000..09178f16639d --- /dev/null +++ b/tests/components/uart/test.esp8266.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/uart/test.rp2040.yaml b/tests/components/uart/test.rp2040.yaml new file mode 100644 index 000000000000..09178f16639d --- /dev/null +++ b/tests/components/uart/test.rp2040.yaml @@ -0,0 +1,15 @@ +esphome: + on_boot: + then: + - uart.write: 'Hello World' + - uart.write: [0x00, 0x20, 0x42] + +uart: + - id: uart_uart + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + data_bits: 8 + rx_buffer_size: 512 + parity: EVEN + stop_bits: 2 diff --git a/tests/components/ufire_ec/test.esp32-c3-idf.yaml b/tests/components/ufire_ec/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..aa72c992b86a --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32-c3.yaml b/tests/components/ufire_ec/test.esp32-c3.yaml new file mode 100644 index 000000000000..aa72c992b86a --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32-idf.yaml b/tests/components/ufire_ec/test.esp32-idf.yaml new file mode 100644 index 000000000000..5e6a0daa9e2c --- /dev/null +++ b/tests/components/ufire_ec/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp32.yaml b/tests/components/ufire_ec/test.esp32.yaml new file mode 100644 index 000000000000..5e6a0daa9e2c --- /dev/null +++ b/tests/components/ufire_ec/test.esp32.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.esp8266.yaml b/tests/components/ufire_ec/test.esp8266.yaml new file mode 100644 index 000000000000..aa72c992b86a --- /dev/null +++ b/tests/components/ufire_ec/test.esp8266.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ec/test.rp2040.yaml b/tests/components/ufire_ec/test.rp2040.yaml new file mode 100644 index 000000000000..aa72c992b86a --- /dev/null +++ b/tests/components/ufire_ec/test.rp2040.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ec.calibrate_probe: + id: ufire_ec_board + solution: 0.146 + temperature: !lambda "return id(test_sensor).state;" + - ufire_ec.reset: + +i2c: + - id: i2c_ufire_ec + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: test_sensor + temperature_compensation: 20.0 + temperature_coefficient: 0.019 diff --git a/tests/components/ufire_ise/test.esp32-c3-idf.yaml b/tests/components/ufire_ise/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..36aec7311366 --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-c3-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32-c3.yaml b/tests/components/ufire_ise/test.esp32-c3.yaml new file mode 100644 index 000000000000..36aec7311366 --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-c3.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32-idf.yaml b/tests/components/ufire_ise/test.esp32-idf.yaml new file mode 100644 index 000000000000..9ed0ac433a1c --- /dev/null +++ b/tests/components/ufire_ise/test.esp32-idf.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp32.yaml b/tests/components/ufire_ise/test.esp32.yaml new file mode 100644 index 000000000000..9ed0ac433a1c --- /dev/null +++ b/tests/components/ufire_ise/test.esp32.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 16 + sda: 17 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.esp8266.yaml b/tests/components/ufire_ise/test.esp8266.yaml new file mode 100644 index 000000000000..36aec7311366 --- /dev/null +++ b/tests/components/ufire_ise/test.esp8266.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/ufire_ise/test.rp2040.yaml b/tests/components/ufire_ise/test.rp2040.yaml new file mode 100644 index 000000000000..36aec7311366 --- /dev/null +++ b/tests/components/ufire_ise/test.rp2040.yaml @@ -0,0 +1,25 @@ +esphome: + on_boot: + then: + - ufire_ise.calibrate_probe_high: + id: ufire_ise_sensor + solution: 7.0 + - ufire_ise.calibrate_probe_low: + id: ufire_ise_sensor + solution: 4.0 + - ufire_ise.reset: + +i2c: + - id: i2c_ufire_ise + scl: 5 + sda: 4 + +sensor: + - platform: template + id: test_sensor + lambda: "return 21;" + - platform: ufire_ise + id: ufire_ise_sensor + temperature_sensor: test_sensor + ph: + name: Ufire pH diff --git a/tests/components/uln2003/test.esp32-c3-idf.yaml b/tests/components/uln2003/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..2d19d4dba394 --- /dev/null +++ b/tests/components/uln2003/test.esp32-c3-idf.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32-c3.yaml b/tests/components/uln2003/test.esp32-c3.yaml new file mode 100644 index 000000000000..2d19d4dba394 --- /dev/null +++ b/tests/components/uln2003/test.esp32-c3.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32-idf.yaml b/tests/components/uln2003/test.esp32-idf.yaml new file mode 100644 index 000000000000..61a6e94396dd --- /dev/null +++ b/tests/components/uln2003/test.esp32-idf.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp32.yaml b/tests/components/uln2003/test.esp32.yaml new file mode 100644 index 000000000000..61a6e94396dd --- /dev/null +++ b/tests/components/uln2003/test.esp32.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.esp8266.yaml b/tests/components/uln2003/test.esp8266.yaml new file mode 100644 index 000000000000..61a6e94396dd --- /dev/null +++ b/tests/components/uln2003/test.esp8266.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 12 + pin_b: 13 + pin_c: 14 + pin_d: 15 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/uln2003/test.rp2040.yaml b/tests/components/uln2003/test.rp2040.yaml new file mode 100644 index 000000000000..2d19d4dba394 --- /dev/null +++ b/tests/components/uln2003/test.rp2040.yaml @@ -0,0 +1,29 @@ +esphome: + on_boot: + then: + - stepper.report_position: + id: uln2003_stepper + position: 250 + - stepper.set_target: + id: uln2003_stepper + target: 250 + - stepper.set_acceleration: + id: uln2003_stepper + acceleration: 250 steps/s^2 + - stepper.set_deceleration: + id: uln2003_stepper + deceleration: 250 steps/s^2 + - stepper.set_speed: + id: uln2003_stepper + speed: 250 steps/s + +stepper: + - platform: uln2003 + id: uln2003_stepper + pin_a: 0 + pin_b: 1 + pin_c: 2 + pin_d: 3 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2 diff --git a/tests/components/ultrasonic/common.yaml b/tests/components/ultrasonic/common.yaml new file mode 100644 index 000000000000..f1f673d9184f --- /dev/null +++ b/tests/components/ultrasonic/common.yaml @@ -0,0 +1,7 @@ +sensor: + - platform: ultrasonic + id: ultrasonic_sensor1 + name: Ultrasonic Sensor + echo_pin: 4 + trigger_pin: 5 + timeout: 5.5m diff --git a/tests/components/ultrasonic/test.esp32-c3-idf.yaml b/tests/components/ultrasonic/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.esp32-c3.yaml b/tests/components/ultrasonic/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.esp32-idf.yaml b/tests/components/ultrasonic/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.esp32.yaml b/tests/components/ultrasonic/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.esp8266.yaml b/tests/components/ultrasonic/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/ultrasonic/test.rp2040.yaml b/tests/components/ultrasonic/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/ultrasonic/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/common.yaml b/tests/components/uponor_smatrix/common.yaml new file mode 100644 index 000000000000..cfdbacaa4c1d --- /dev/null +++ b/tests/components/uponor_smatrix/common.yaml @@ -0,0 +1,38 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uponor_uart + baud_rate: 19200 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + +time: + - platform: sntp + id: sntp_time + servers: + - 0.pool.ntp.org + - 1.pool.ntp.org + - 192.168.178.1 + +uponor_smatrix: + uart_id: uponor_uart + address: 0x110B + time_id: sntp_time + time_device_address: 0xDE13 + +climate: + - platform: uponor_smatrix + address: 0xDE13 + name: Thermostat Living Room + +sensor: + - platform: uponor_smatrix + address: 0xDE13 + humidity: + name: Thermostat Humidity Living Room + temperature: + name: Thermostat Temperature Living Room + external_temperature: + name: Thermostat Floor Temperature Living Room diff --git a/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml b/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..b516342f3bc2 --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32-c3.yaml b/tests/components/uponor_smatrix/test.esp32-c3.yaml new file mode 100644 index 000000000000..b516342f3bc2 --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-c3.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32-idf.yaml b/tests/components/uponor_smatrix/test.esp32-idf.yaml new file mode 100644 index 000000000000..f486544afa41 --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp32.yaml b/tests/components/uponor_smatrix/test.esp32.yaml new file mode 100644 index 000000000000..f486544afa41 --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.esp8266.yaml b/tests/components/uponor_smatrix/test.esp8266.yaml new file mode 100644 index 000000000000..b516342f3bc2 --- /dev/null +++ b/tests/components/uponor_smatrix/test.esp8266.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uponor_smatrix/test.rp2040.yaml b/tests/components/uponor_smatrix/test.rp2040.yaml new file mode 100644 index 000000000000..b516342f3bc2 --- /dev/null +++ b/tests/components/uponor_smatrix/test.rp2040.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/uptime/common.yaml b/tests/components/uptime/common.yaml new file mode 100644 index 000000000000..872a0e74022d --- /dev/null +++ b/tests/components/uptime/common.yaml @@ -0,0 +1,3 @@ +sensor: + - platform: uptime + name: Uptime Sensor diff --git a/tests/components/uptime/test.esp32-c3-idf.yaml b/tests/components/uptime/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uptime/test.esp32-c3.yaml b/tests/components/uptime/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uptime/test.esp32-idf.yaml b/tests/components/uptime/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uptime/test.esp32.yaml b/tests/components/uptime/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uptime/test.esp8266.yaml b/tests/components/uptime/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/uptime/test.rp2040.yaml b/tests/components/uptime/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/uptime/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/vbus/test.esp32-c3-idf.yaml b/tests/components/vbus/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..67ee542031ec --- /dev/null +++ b/tests/components/vbus/test.esp32-c3-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32-c3.yaml b/tests/components/vbus/test.esp32-c3.yaml new file mode 100644 index 000000000000..67ee542031ec --- /dev/null +++ b/tests/components/vbus/test.esp32-c3.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32-idf.yaml b/tests/components/vbus/test.esp32-idf.yaml new file mode 100644 index 000000000000..a0e5ca42cc32 --- /dev/null +++ b/tests/components/vbus/test.esp32-idf.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp32.yaml b/tests/components/vbus/test.esp32.yaml new file mode 100644 index 000000000000..a0e5ca42cc32 --- /dev/null +++ b/tests/components/vbus/test.esp32.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.esp8266.yaml b/tests/components/vbus/test.esp8266.yaml new file mode 100644 index 000000000000..67ee542031ec --- /dev/null +++ b/tests/components/vbus/test.esp8266.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/vbus/test.rp2040.yaml b/tests/components/vbus/test.rp2040.yaml new file mode 100644 index 000000000000..67ee542031ec --- /dev/null +++ b/tests/components/vbus/test.rp2040.yaml @@ -0,0 +1,42 @@ +uart: + - id: uart_vbus + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +vbus: + +binary_sensor: + - platform: vbus + model: deltasol_bs_plus + relay1: + name: Relay 1 On + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +sensor: + - platform: vbus + model: deltasol c + temperature_1: + name: Temperature 1 + temperature_2: + name: Temperature 2 + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time diff --git a/tests/components/veml3235/test.esp32-c3-idf.yaml b/tests/components/veml3235/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..1f79c5f50c78 --- /dev/null +++ b/tests/components/veml3235/test.esp32-c3-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32-c3.yaml b/tests/components/veml3235/test.esp32-c3.yaml new file mode 100644 index 000000000000..1f79c5f50c78 --- /dev/null +++ b/tests/components/veml3235/test.esp32-c3.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32-idf.yaml b/tests/components/veml3235/test.esp32-idf.yaml new file mode 100644 index 000000000000..3442fa431707 --- /dev/null +++ b/tests/components/veml3235/test.esp32-idf.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 16 + sda: 17 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp32.yaml b/tests/components/veml3235/test.esp32.yaml new file mode 100644 index 000000000000..3442fa431707 --- /dev/null +++ b/tests/components/veml3235/test.esp32.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 16 + sda: 17 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.esp8266.yaml b/tests/components/veml3235/test.esp8266.yaml new file mode 100644 index 000000000000..1f79c5f50c78 --- /dev/null +++ b/tests/components/veml3235/test.esp8266.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml3235/test.rp2040.yaml b/tests/components/veml3235/test.rp2040.yaml new file mode 100644 index 000000000000..1f79c5f50c78 --- /dev/null +++ b/tests/components/veml3235/test.rp2040.yaml @@ -0,0 +1,15 @@ +i2c: + - id: i2c_veml3235 + scl: 5 + sda: 4 + +sensor: + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms diff --git a/tests/components/veml7700/common.yaml b/tests/components/veml7700/common.yaml new file mode 100644 index 000000000000..6620c8d7e16b --- /dev/null +++ b/tests/components/veml7700/common.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: veml7700 + address: 0x10 + i2c_id: i2c_veml7700 + ambient_light: Ambient light + ambient_light_counts: Ambient light counts + full_spectrum: Full spectrum + full_spectrum_counts: Full spectrum counts + actual_integration_time: Actual integration time + actual_gain: Actual gain diff --git a/tests/components/veml7700/test.esp32-c3-idf.yaml b/tests/components/veml7700/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..ce0fa0125bde --- /dev/null +++ b/tests/components/veml7700/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32-c3.yaml b/tests/components/veml7700/test.esp32-c3.yaml new file mode 100644 index 000000000000..ce0fa0125bde --- /dev/null +++ b/tests/components/veml7700/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32-idf.yaml b/tests/components/veml7700/test.esp32-idf.yaml new file mode 100644 index 000000000000..4b812a1392e7 --- /dev/null +++ b/tests/components/veml7700/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp32.yaml b/tests/components/veml7700/test.esp32.yaml new file mode 100644 index 000000000000..4b812a1392e7 --- /dev/null +++ b/tests/components/veml7700/test.esp32.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.esp8266.yaml b/tests/components/veml7700/test.esp8266.yaml new file mode 100644 index 000000000000..ce0fa0125bde --- /dev/null +++ b/tests/components/veml7700/test.esp8266.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/veml7700/test.rp2040.yaml b/tests/components/veml7700/test.rp2040.yaml new file mode 100644 index 000000000000..ce0fa0125bde --- /dev/null +++ b/tests/components/veml7700/test.rp2040.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_veml7700 + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/version/common.yaml b/tests/components/version/common.yaml new file mode 100644 index 000000000000..7713afc37c27 --- /dev/null +++ b/tests/components/version/common.yaml @@ -0,0 +1,3 @@ +text_sensor: + - platform: version + name: "ESPHome Version" diff --git a/tests/components/version/test.esp32-c3-idf.yaml b/tests/components/version/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/version/test.esp32-c3.yaml b/tests/components/version/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/version/test.esp32-idf.yaml b/tests/components/version/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/version/test.esp32.yaml b/tests/components/version/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/version/test.esp8266.yaml b/tests/components/version/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/version/test.rp2040.yaml b/tests/components/version/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/version/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/vl53l0x/test.esp32-c3-idf.yaml b/tests/components/vl53l0x/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..832f7dcfbcc9 --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32-c3.yaml b/tests/components/vl53l0x/test.esp32-c3.yaml new file mode 100644 index 000000000000..832f7dcfbcc9 --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32-idf.yaml b/tests/components/vl53l0x/test.esp32-idf.yaml new file mode 100644 index 000000000000..8f35de0e729f --- /dev/null +++ b/tests/components/vl53l0x/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 16 + sda: 17 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp32.yaml b/tests/components/vl53l0x/test.esp32.yaml new file mode 100644 index 000000000000..8f35de0e729f --- /dev/null +++ b/tests/components/vl53l0x/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 16 + sda: 17 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.esp8266.yaml b/tests/components/vl53l0x/test.esp8266.yaml new file mode 100644 index 000000000000..832f7dcfbcc9 --- /dev/null +++ b/tests/components/vl53l0x/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/vl53l0x/test.rp2040.yaml b/tests/components/vl53l0x/test.rp2040.yaml new file mode 100644 index 000000000000..832f7dcfbcc9 --- /dev/null +++ b/tests/components/vl53l0x/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_vl53l0x + scl: 5 + sda: 4 + +sensor: + - platform: vl53l0x + name: VL53L0x Distance + address: 0x29 + enable_pin: 3 + timeout: 200us + update_interval: 60s diff --git a/tests/components/voice_assistant/test.esp32-c3-idf.yaml b/tests/components/voice_assistant/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..248ae4d0dc6e --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-c3-idf.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 2 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32-c3.yaml b/tests/components/voice_assistant/test.esp32-c3.yaml new file mode 100644 index 000000000000..248ae4d0dc6e --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-c3.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 6 + i2s_bclk_pin: 7 + i2s_mclk_pin: 5 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 3 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 2 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32-idf.yaml b/tests/components/voice_assistant/test.esp32-idf.yaml new file mode 100644 index 000000000000..2e0209311dd3 --- /dev/null +++ b/tests/components/voice_assistant/test.esp32-idf.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 13 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 12 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/voice_assistant/test.esp32.yaml b/tests/components/voice_assistant/test.esp32.yaml new file mode 100644 index 000000000000..2e0209311dd3 --- /dev/null +++ b/tests/components/voice_assistant/test.esp32.yaml @@ -0,0 +1,57 @@ +esphome: + on_boot: + then: + - voice_assistant.start + - voice_assistant.start_continuous + - voice_assistant.stop + +wifi: + ssid: MySSID + password: password1 + +api: + +i2s_audio: + i2s_lrclk_pin: 16 + i2s_bclk_pin: 17 + i2s_mclk_pin: 15 + +microphone: + - platform: i2s_audio + id: mic_id_external + i2s_din_pin: 13 + adc_type: external + pdm: false + +speaker: + - platform: i2s_audio + id: speaker_id + dac_type: external + i2s_dout_pin: 12 + mode: mono + +voice_assistant: + microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] diff --git a/tests/components/wake_on_lan/common.yaml b/tests/components/wake_on_lan/common.yaml new file mode 100644 index 000000000000..6a5351b624dc --- /dev/null +++ b/tests/components/wake_on_lan/common.yaml @@ -0,0 +1,9 @@ +wifi: + ssid: MySSID + password: password1 + +button: + - platform: wake_on_lan + id: wol_1 + name: wol_test_1 + target_mac_address: 12:34:56:78:90:ab diff --git a/tests/components/wake_on_lan/test.esp32-c3.yaml b/tests/components/wake_on_lan/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wake_on_lan/test.esp32.yaml b/tests/components/wake_on_lan/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wake_on_lan/test.esp8266.yaml b/tests/components/wake_on_lan/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wake_on_lan/test.rp2040.yaml b/tests/components/wake_on_lan/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wake_on_lan/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !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 new file mode 100644 index 000000000000..1c4547b7b4ca --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_waveshare_epaper + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3.yaml b/tests/components/waveshare_epaper/test.esp32-c3.yaml new file mode 100644 index 000000000000..1c4547b7b4ca --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-c3.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_waveshare_epaper + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-idf.yaml b/tests/components/waveshare_epaper/test.esp32-idf.yaml new file mode 100644 index 000000000000..b6082fcfbfee --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32-idf.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32.yaml b/tests/components/waveshare_epaper/test.esp32.yaml new file mode 100644 index 000000000000..2f06c5c51b0c --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp32.yaml @@ -0,0 +1,190 @@ +--- +spi: + - id: spi_id_1 + clk_pin: + number: GPIO18 + mosi_pin: + number: GPIO23 + miso_pin: + number: GPIO19 + interface: hardware + +display: + - platform: waveshare_epaper + model: 2.13in-ttgo-b1 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13in-ttgo-b74 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.90in + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.90inv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.90in-dke + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.70in-b + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.70in-bv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 1.54in-m5coreink-m09 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13inv3 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 2.13inv2 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp8266.yaml b/tests/components/waveshare_epaper/test.esp8266.yaml new file mode 100644 index 000000000000..1f076a67bef3 --- /dev/null +++ b/tests/components/waveshare_epaper/test.esp8266.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.rp2040.yaml b/tests/components/waveshare_epaper/test.rp2040.yaml new file mode 100644 index 000000000000..6050062d7e13 --- /dev/null +++ b/tests/components/waveshare_epaper/test.rp2040.yaml @@ -0,0 +1,107 @@ +spi: + - id: spi_bme280 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.90in + full_update_every: 30 + reset_duration: 200ms + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.70in-b + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 2.70in-bv2 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/web_server/common.yaml b/tests/components/web_server/common.yaml new file mode 100644 index 000000000000..94388726c3e4 --- /dev/null +++ b/tests/components/web_server/common.yaml @@ -0,0 +1,7 @@ +wifi: + ssid: MySSID + password: password1 + +web_server: + port: 8080 + version: 2 diff --git a/tests/components/web_server/test.esp32-c3-idf.yaml b/tests/components/web_server/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/web_server/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/web_server/test.esp32-c3.yaml b/tests/components/web_server/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/web_server/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/web_server/test.esp32-idf.yaml b/tests/components/web_server/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/web_server/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/web_server/test.esp32.yaml b/tests/components/web_server/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/web_server/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/web_server/test.esp8266.yaml b/tests/components/web_server/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/web_server/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/whirlpool/test.esp32-c3-idf.yaml b/tests/components/whirlpool/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4d0afc39a4bf --- /dev/null +++ b/tests/components/whirlpool/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32-c3.yaml b/tests/components/whirlpool/test.esp32-c3.yaml new file mode 100644 index 000000000000..4d0afc39a4bf --- /dev/null +++ b/tests/components/whirlpool/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32-idf.yaml b/tests/components/whirlpool/test.esp32-idf.yaml new file mode 100644 index 000000000000..4d0afc39a4bf --- /dev/null +++ b/tests/components/whirlpool/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp32.yaml b/tests/components/whirlpool/test.esp32.yaml new file mode 100644 index 000000000000..4d0afc39a4bf --- /dev/null +++ b/tests/components/whirlpool/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whirlpool/test.esp8266.yaml b/tests/components/whirlpool/test.esp8266.yaml new file mode 100644 index 000000000000..efd530c16005 --- /dev/null +++ b/tests/components/whirlpool/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: whirlpool + name: Whirlpool Climate diff --git a/tests/components/whynter/test.esp32-c3-idf.yaml b/tests/components/whynter/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dc8fb9584d11 --- /dev/null +++ b/tests/components/whynter/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32-c3.yaml b/tests/components/whynter/test.esp32-c3.yaml new file mode 100644 index 000000000000..dc8fb9584d11 --- /dev/null +++ b/tests/components/whynter/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32-idf.yaml b/tests/components/whynter/test.esp32-idf.yaml new file mode 100644 index 000000000000..dc8fb9584d11 --- /dev/null +++ b/tests/components/whynter/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp32.yaml b/tests/components/whynter/test.esp32.yaml new file mode 100644 index 000000000000..dc8fb9584d11 --- /dev/null +++ b/tests/components/whynter/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/whynter/test.esp8266.yaml b/tests/components/whynter/test.esp8266.yaml new file mode 100644 index 000000000000..a656a7427d67 --- /dev/null +++ b/tests/components/whynter/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: whynter + name: Whynter Climate diff --git a/tests/components/wiegand/common.yaml b/tests/components/wiegand/common.yaml new file mode 100644 index 000000000000..4e15a44b899c --- /dev/null +++ b/tests/components/wiegand/common.yaml @@ -0,0 +1,10 @@ +wiegand: + - id: test_wiegand + d0: 5 + d1: 4 + on_key: + - lambda: ESP_LOGI("KEY", "Received key %d", x); + on_tag: + - lambda: ESP_LOGI("TAG", "Received tag %s", x.c_str()); + on_raw: + - lambda: ESP_LOGI("RAW", "Received raw %d bits, value %llx", bits, value); diff --git a/tests/components/wiegand/test.esp32-c3-idf.yaml b/tests/components/wiegand/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wiegand/test.esp32-c3.yaml b/tests/components/wiegand/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wiegand/test.esp32-idf.yaml b/tests/components/wiegand/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wiegand/test.esp32.yaml b/tests/components/wiegand/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wiegand/test.esp8266.yaml b/tests/components/wiegand/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wiegand/test.rp2040.yaml b/tests/components/wiegand/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wiegand/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml new file mode 100644 index 000000000000..003f6347be94 --- /dev/null +++ b/tests/components/wifi/common.yaml @@ -0,0 +1,9 @@ +esphome: + on_boot: + then: + - wifi.disable + - wifi.enable + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/wifi/test.esp32-c3-idf.yaml b/tests/components/wifi/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/test.esp32-c3.yaml b/tests/components/wifi/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/test.esp32.yaml b/tests/components/wifi/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/test.esp8266.yaml b/tests/components/wifi/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi/test.rp2040.yaml b/tests/components/wifi/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/common.yaml b/tests/components/wifi_info/common.yaml new file mode 100644 index 000000000000..cf5ea563ba4a --- /dev/null +++ b/tests/components/wifi_info/common.yaml @@ -0,0 +1,18 @@ +wifi: + ssid: MySSID + password: password1 + +text_sensor: + - platform: wifi_info + scan_results: + name: Scan Results + ip_address: + name: IP Address + ssid: + name: SSID + bssid: + name: BSSID + mac_address: + name: Mac Address + dns_address: + name: DNS ADdress diff --git a/tests/components/wifi_info/test.esp32-c3-idf.yaml b/tests/components/wifi_info/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/test.esp32-c3.yaml b/tests/components/wifi_info/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/test.esp32-idf.yaml b/tests/components/wifi_info/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/test.esp32.yaml b/tests/components/wifi_info/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/test.esp8266.yaml b/tests/components/wifi_info/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_info/test.rp2040.yaml b/tests/components/wifi_info/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_info/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/common.yaml b/tests/components/wifi_signal/common.yaml new file mode 100644 index 000000000000..58d1cab244fd --- /dev/null +++ b/tests/components/wifi_signal/common.yaml @@ -0,0 +1,8 @@ +wifi: + ssid: MySSID + password: password1 + +sensor: + - platform: wifi_signal + name: WiFi Signal Sensor + update_interval: 15s diff --git a/tests/components/wifi_signal/test.esp32-c3-idf.yaml b/tests/components/wifi_signal/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.esp32-c3.yaml b/tests/components/wifi_signal/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.esp32-idf.yaml b/tests/components/wifi_signal/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.esp32.yaml b/tests/components/wifi_signal/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.esp8266.yaml b/tests/components/wifi_signal/test.esp8266.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.esp8266.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wifi_signal/test.rp2040.yaml b/tests/components/wifi_signal/test.rp2040.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/wifi_signal/test.rp2040.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wireguard/test.esp32-c3-idf.yaml b/tests/components/wireguard/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..37d172784208 --- /dev/null +++ b/tests/components/wireguard/test.esp32-c3-idf.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + +wireguard: + id: vpn + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp32-c3.yaml b/tests/components/wireguard/test.esp32-c3.yaml new file mode 100644 index 000000000000..37d172784208 --- /dev/null +++ b/tests/components/wireguard/test.esp32-c3.yaml @@ -0,0 +1,60 @@ +wifi: + ssid: MySSID + password: password1 + +time: + - platform: sntp + +wireguard: + id: vpn + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp32-idf.yaml b/tests/components/wireguard/test.esp32-idf.yaml new file mode 100644 index 000000000000..9ea7f00bdbd8 --- /dev/null +++ b/tests/components/wireguard/test.esp32-idf.yaml @@ -0,0 +1,62 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +network: + enable_ipv6: true + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp32.yaml b/tests/components/wireguard/test.esp32.yaml new file mode 100644 index 000000000000..9ea7f00bdbd8 --- /dev/null +++ b/tests/components/wireguard/test.esp32.yaml @@ -0,0 +1,62 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +network: + enable_ipv6: true + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wireguard/test.esp8266.yaml b/tests/components/wireguard/test.esp8266.yaml new file mode 100644 index 000000000000..9ea7f00bdbd8 --- /dev/null +++ b/tests/components/wireguard/test.esp8266.yaml @@ -0,0 +1,62 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +network: + enable_ipv6: true + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' diff --git a/tests/components/wk2132_i2c/common.yaml b/tests/components/wk2132_i2c/common.yaml new file mode 100644 index 000000000000..f9c8ab756d33 --- /dev/null +++ b/tests/components/wk2132_i2c/common.yaml @@ -0,0 +1,20 @@ +i2c: + id: i2c_bus + scl: ${scl_pin} + sda: ${sda_pin} + scan: true + frequency: 600kHz + +wk2132_i2c: + - id: wk2132_i2c_id + address: 0x70 + i2c_id: i2c_bus + uart: + - id: wk2132_id_0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2132_id_1 + channel: 1 + baud_rate: 19200 diff --git a/tests/components/wk2132_i2c/test.esp32-idf.yaml b/tests/components/wk2132_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2132_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml b/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2132_i2c/test.esp32-s3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2132_i2c/test.esp32-s3.yaml b/tests/components/wk2132_i2c/test.esp32-s3.yaml new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2132_i2c/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2132_i2c/test.esp32.yaml b/tests/components/wk2132_i2c/test.esp32.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2132_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/wk2132_spi/common.yaml b/tests/components/wk2132_spi/common.yaml new file mode 100644 index 000000000000..b21e89120ce6 --- /dev/null +++ b/tests/components/wk2132_spi/common.yaml @@ -0,0 +1,21 @@ +spi: + id: spi_bus + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +wk2132_spi: + - id: wk2132_spi_id + cs_pin: ${cs_pin} + spi_id: spi_bus + crystal: 11059200 + data_rate: 1MHz + uart: + - id: wk2132_spi_id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2132_spi_id1 + channel: 1 + baud_rate: 921600 diff --git a/tests/components/wk2132_spi/test.esp32-idf.yaml b/tests/components/wk2132_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2132_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !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 new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2132_spi/test.esp32-s3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2132_spi/test.esp32-s3.yaml b/tests/components/wk2132_spi/test.esp32-s3.yaml new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2132_spi/test.esp32-s3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2132_spi/test.esp32.yaml b/tests/components/wk2132_spi/test.esp32.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2132_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/wk2168_i2c/common.yaml b/tests/components/wk2168_i2c/common.yaml new file mode 100644 index 000000000000..fe4689d6db84 --- /dev/null +++ b/tests/components/wk2168_i2c/common.yaml @@ -0,0 +1,63 @@ +i2c: + id: i2c_bus + scl: ${scl_pin} + sda: ${sda_pin} + scan: true + frequency: 600kHz + +# component declaration +wk2168_i2c: + - id: bridge_i2c + i2c_id: i2c_bus + address: 0x70 + uart: + - id: id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: id1 + channel: 1 + baud_rate: 115200 + - id: id2 + channel: 2 + baud_rate: 115200 + - id: id3 + channel: 3 + baud_rate: 115200 + +# individual binary_sensor inputs +binary_sensor: + - platform: gpio + name: "pin_0" + pin: + wk2168_i2c: bridge_i2c + number: 0 + mode: + input: true + - platform: gpio + name: "pin_1" + pin: + wk2168_i2c: bridge_i2c + number: 1 + mode: + input: true + inverted: true + +# Individual binary outputs +switch: + - platform: gpio + name: "pin_2" + pin: + wk2168_i2c: bridge_i2c + number: 2 + mode: + output: true + - platform: gpio + name: "pin_3" + pin: + wk2168_i2c: bridge_i2c + number: 3 + mode: + output: true + inverted: true diff --git a/tests/components/wk2168_i2c/test.esp32-idf.yaml b/tests/components/wk2168_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2168_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !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 new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2168_i2c/test.esp32-s3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2168_i2c/test.esp32-s3.yaml b/tests/components/wk2168_i2c/test.esp32-s3.yaml new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2168_i2c/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2168_i2c/test.esp32.yaml b/tests/components/wk2168_i2c/test.esp32.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2168_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/wk2168_spi/common.yaml b/tests/components/wk2168_spi/common.yaml new file mode 100644 index 000000000000..7626e18df640 --- /dev/null +++ b/tests/components/wk2168_spi/common.yaml @@ -0,0 +1,63 @@ +spi: + id: spi_bus + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +wk2168_spi: + - id: bridge_spi + cs_pin: ${cs_pin} + spi_id: spi_bus + crystal: 11059200 + data_rate: 1MHz + uart: + - id: id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: id1 + channel: 1 + baud_rate: 115200 + - id: id2 + channel: 2 + baud_rate: 115200 + - id: id3 + channel: 3 + baud_rate: 115200 + +# individual binary_sensor inputs +binary_sensor: + - platform: gpio + name: "pin_0" + pin: + wk2168_spi: bridge_spi + number: 0 + mode: + input: true + - platform: gpio + name: "pin_1" + pin: + wk2168_spi: bridge_spi + number: 1 + mode: + input: true + inverted: true + +# Individual binary outputs +switch: + - platform: gpio + name: "pin_2" + pin: + wk2168_spi: bridge_spi + number: 2 + mode: + output: true + - platform: gpio + name: "pin_3" + pin: + wk2168_spi: bridge_spi + number: 3 + mode: + output: true + inverted: true diff --git a/tests/components/wk2168_spi/test.esp32-idf.yaml b/tests/components/wk2168_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2168_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !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 new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2168_spi/test.esp32-s3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2168_spi/test.esp32-s3.yaml b/tests/components/wk2168_spi/test.esp32-s3.yaml new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2168_spi/test.esp32-s3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2168_spi/test.esp32.yaml b/tests/components/wk2168_spi/test.esp32.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2168_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/wk2204_i2c/common.yaml b/tests/components/wk2204_i2c/common.yaml new file mode 100644 index 000000000000..80f636c6901d --- /dev/null +++ b/tests/components/wk2204_i2c/common.yaml @@ -0,0 +1,28 @@ +i2c: + id: i2c_bus + scl: ${scl_pin} + sda: ${sda_pin} + scan: true + frequency: 600kHz + +wk2204_i2c: + - id: wk2204_i2c_id + i2c_id: i2c_bus + address: 0x70 + uart: + - id: wk2204_id_0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2204_id_1 + channel: 1 + baud_rate: 19200 + - id: wk2204_id_2 + channel: 2 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2204_id_3 + channel: 3 + baud_rate: 19200 diff --git a/tests/components/wk2204_i2c/test.esp32-idf.yaml b/tests/components/wk2204_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2204_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !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 new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2204_i2c/test.esp32-s3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2204_i2c/test.esp32-s3.yaml b/tests/components/wk2204_i2c/test.esp32-s3.yaml new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2204_i2c/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2204_i2c/test.esp32.yaml b/tests/components/wk2204_i2c/test.esp32.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2204_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/wk2204_spi/common.yaml b/tests/components/wk2204_spi/common.yaml new file mode 100644 index 000000000000..3bae9c9a6d95 --- /dev/null +++ b/tests/components/wk2204_spi/common.yaml @@ -0,0 +1,29 @@ +spi: + id: spi_bus + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +wk2204_spi: + - id: wk2204_spi_id + cs_pin: ${cs_pin} + spi_id: spi_bus + crystal: 11059200 + data_rate: 1MHz + uart: + - id: wk2204_spi_id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2204_spi_id1 + channel: 1 + baud_rate: 921600 + - id: wk2204_spi_id2 + channel: 2 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: wk2204_spi_id3 + channel: 3 + baud_rate: 921600 diff --git a/tests/components/wk2204_spi/test.esp32-idf.yaml b/tests/components/wk2204_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2204_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !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 new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2204_spi/test.esp32-s3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2204_spi/test.esp32-s3.yaml b/tests/components/wk2204_spi/test.esp32-s3.yaml new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2204_spi/test.esp32-s3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2204_spi/test.esp32.yaml b/tests/components/wk2204_spi/test.esp32.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2204_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/wk2212_i2c/common.yaml b/tests/components/wk2212_i2c/common.yaml new file mode 100644 index 000000000000..2e891c55205f --- /dev/null +++ b/tests/components/wk2212_i2c/common.yaml @@ -0,0 +1,59 @@ +i2c: + id: i2c_bus + scl: ${scl_pin} + sda: ${sda_pin} + scan: true + frequency: 600kHz + +# component declaration +wk2212_i2c: + - id: bridge_i2c + i2c_id: i2c_bus + address: 0x70 + uart: + - id: uart_i2c_id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: uart_i2c_id1 + channel: 1 + baud_rate: 115200 + stop_bits: 1 + parity: none + +# individual binary_sensor inputs +binary_sensor: + - platform: gpio + name: "pin_0" + pin: + wk2212_i2c: bridge_i2c + number: 0 + mode: + input: true + - platform: gpio + name: "pin_1" + pin: + wk2212_i2c: bridge_i2c + number: 1 + mode: + input: true + inverted: true + +# Individual binary outputs +switch: + - platform: gpio + name: "pin_2" + pin: + wk2212_i2c: bridge_i2c + number: 2 + mode: + output: true + - platform: gpio + name: "pin_3" + pin: + wk2212_i2c: bridge_i2c + number: 3 + mode: + output: true + inverted: true diff --git a/tests/components/wk2212_i2c/test.esp32-idf.yaml b/tests/components/wk2212_i2c/test.esp32-idf.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2212_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !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 new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2212_i2c/test.esp32-s3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2212_i2c/test.esp32-s3.yaml b/tests/components/wk2212_i2c/test.esp32-s3.yaml new file mode 100644 index 000000000000..4942e3c2b310 --- /dev/null +++ b/tests/components/wk2212_i2c/test.esp32-s3.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO40 + sda_pin: GPIO41 + +<<: !include common.yaml diff --git a/tests/components/wk2212_i2c/test.esp32.yaml b/tests/components/wk2212_i2c/test.esp32.yaml new file mode 100644 index 000000000000..3b761d3fc16a --- /dev/null +++ b/tests/components/wk2212_i2c/test.esp32.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/wk2212_spi/common.yaml b/tests/components/wk2212_spi/common.yaml new file mode 100644 index 000000000000..ad9f11d9e8fe --- /dev/null +++ b/tests/components/wk2212_spi/common.yaml @@ -0,0 +1,58 @@ +spi: + id: spi_bus + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +wk2212_spi: + - id: bridge_spi + cs_pin: ${cs_pin} + spi_id: spi_bus + crystal: 11059200 + data_rate: 1MHz + uart: + - id: id0 + channel: 0 + baud_rate: 115200 + stop_bits: 1 + parity: none + - id: id1 + channel: 1 + baud_rate: 115200 + +# individual binary_sensor inputs +binary_sensor: + - platform: gpio + name: "pin_0" + pin: + wk2212_spi: bridge_spi + number: 0 + mode: + input: true + - platform: gpio + name: "pin_1" + pin: + wk2212_spi: bridge_spi + number: 1 + mode: + input: true + inverted: true + +# Individual binary outputs +switch: + - platform: gpio + name: "pin_2" + pin: + wk2212_spi: bridge_spi + number: 2 + mode: + output: true + - platform: gpio + name: "pin_3" + pin: + wk2212_spi: bridge_spi + number: 3 + mode: + output: true + inverted: true + diff --git a/tests/components/wk2212_spi/test.esp32-idf.yaml b/tests/components/wk2212_spi/test.esp32-idf.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2212_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !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 new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2212_spi/test.esp32-s3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2212_spi/test.esp32-s3.yaml b/tests/components/wk2212_spi/test.esp32-s3.yaml new file mode 100644 index 000000000000..b0aadf620ada --- /dev/null +++ b/tests/components/wk2212_spi/test.esp32-s3.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO40 + miso_pin: GPIO41 + mosi_pin: GPIO6 + cs_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/wk2212_spi/test.esp32.yaml b/tests/components/wk2212_spi/test.esp32.yaml new file mode 100644 index 000000000000..76e7138ab053 --- /dev/null +++ b/tests/components/wk2212_spi/test.esp32.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO18 + miso_pin: GPIO19 + mosi_pin: GPIO23 + cs_pin: GPIO5 + +<<: !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 new file mode 100644 index 000000000000..7cda1ba0602e --- /dev/null +++ b/tests/components/wl_134/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32-c3.yaml b/tests/components/wl_134/test.esp32-c3.yaml new file mode 100644 index 000000000000..7cda1ba0602e --- /dev/null +++ b/tests/components/wl_134/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32-idf.yaml b/tests/components/wl_134/test.esp32-idf.yaml new file mode 100644 index 000000000000..d517889d28d0 --- /dev/null +++ b/tests/components/wl_134/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp32.yaml b/tests/components/wl_134/test.esp32.yaml new file mode 100644 index 000000000000..d517889d28d0 --- /dev/null +++ b/tests/components/wl_134/test.esp32.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 17 + rx_pin: 16 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.esp8266.yaml b/tests/components/wl_134/test.esp8266.yaml new file mode 100644 index 000000000000..7cda1ba0602e --- /dev/null +++ b/tests/components/wl_134/test.esp8266.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wl_134/test.rp2040.yaml b/tests/components/wl_134/test.rp2040.yaml new file mode 100644 index 000000000000..7cda1ba0602e --- /dev/null +++ b/tests/components/wl_134/test.rp2040.yaml @@ -0,0 +1,10 @@ +uart: + - id: uart_wl_134 + tx_pin: 4 + rx_pin: 5 + baud_rate: 9600 + +text_sensor: + - platform: wl_134 + name: Transponder Code + reset: true diff --git a/tests/components/wled/test.esp32-c3.yaml b/tests/components/wled/test.esp32-c3.yaml new file mode 100644 index 000000000000..a24f28e15437 --- /dev/null +++ b/tests/components/wled/test.esp32-c3.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - wled: diff --git a/tests/components/wled/test.esp32.yaml b/tests/components/wled/test.esp32.yaml new file mode 100644 index 000000000000..a24f28e15437 --- /dev/null +++ b/tests/components/wled/test.esp32.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: esp32_rmt_led_strip + id: led_matrix_32x8 + default_transition_length: 500ms + chipset: ws2812 + rgb_order: GRB + num_leds: 256 + pin: 2 + rmt_channel: 0 + effects: + - wled: diff --git a/tests/components/wled/test.esp8266.yaml b/tests/components/wled/test.esp8266.yaml new file mode 100644 index 000000000000..e291af927c87 --- /dev/null +++ b/tests/components/wled/test.esp8266.yaml @@ -0,0 +1,17 @@ +wifi: + ssid: MySSID + password: password1 + +wled: + +light: + - platform: neopixelbus + id: addr + name: Neopixelbus Light + method: esp8266_uart + num_leds: 5 + pin: 2 + type: GRBW + variant: SK6812 + effects: + - wled: diff --git a/tests/components/x9c/test.esp32-c3-idf.yaml b/tests/components/x9c/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..a0480aa68f16 --- /dev/null +++ b/tests/components/x9c/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32-c3.yaml b/tests/components/x9c/test.esp32-c3.yaml new file mode 100644 index 000000000000..a0480aa68f16 --- /dev/null +++ b/tests/components/x9c/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32-idf.yaml b/tests/components/x9c/test.esp32-idf.yaml new file mode 100644 index 000000000000..28b18f7a92cb --- /dev/null +++ b/tests/components/x9c/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp32.yaml b/tests/components/x9c/test.esp32.yaml new file mode 100644 index 000000000000..28b18f7a92cb --- /dev/null +++ b/tests/components/x9c/test.esp32.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.esp8266.yaml b/tests/components/x9c/test.esp8266.yaml new file mode 100644 index 000000000000..28b18f7a92cb --- /dev/null +++ b/tests/components/x9c/test.esp8266.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 13 + inc_pin: 14 + ud_pin: 15 + initial_value: 0.5 diff --git a/tests/components/x9c/test.rp2040.yaml b/tests/components/x9c/test.rp2040.yaml new file mode 100644 index 000000000000..a0480aa68f16 --- /dev/null +++ b/tests/components/x9c/test.rp2040.yaml @@ -0,0 +1,7 @@ +output: + - platform: x9c + id: test_x9c + cs_pin: 3 + inc_pin: 4 + ud_pin: 5 + initial_value: 0.5 diff --git a/tests/components/xgzp68xx/test.esp32-c3-idf.yaml b/tests/components/xgzp68xx/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..25df8cc225ab --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-c3-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32-c3.yaml b/tests/components/xgzp68xx/test.esp32-c3.yaml new file mode 100644 index 000000000000..25df8cc225ab --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-c3.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32-idf.yaml b/tests/components/xgzp68xx/test.esp32-idf.yaml new file mode 100644 index 000000000000..fb5542112338 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32-idf.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 16 + sda: 17 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp32.yaml b/tests/components/xgzp68xx/test.esp32.yaml new file mode 100644 index 000000000000..fb5542112338 --- /dev/null +++ b/tests/components/xgzp68xx/test.esp32.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 16 + sda: 17 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.esp8266.yaml b/tests/components/xgzp68xx/test.esp8266.yaml new file mode 100644 index 000000000000..25df8cc225ab --- /dev/null +++ b/tests/components/xgzp68xx/test.esp8266.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xgzp68xx/test.rp2040.yaml b/tests/components/xgzp68xx/test.rp2040.yaml new file mode 100644 index 000000000000..25df8cc225ab --- /dev/null +++ b/tests/components/xgzp68xx/test.rp2040.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_xgzp68xx + scl: 5 + sda: 4 + +sensor: + - platform: xgzp68xx + k_value: 4096 + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure diff --git a/tests/components/xiaomi_ble/common.yaml b/tests/components/xiaomi_ble/common.yaml new file mode 100644 index 000000000000..9d103931772b --- /dev/null +++ b/tests/components/xiaomi_ble/common.yaml @@ -0,0 +1,3 @@ +esp32_ble_tracker: + +xiaomi_ble: diff --git a/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml b/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_ble/test.esp32-c3.yaml b/tests/components/xiaomi_ble/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_ble/test.esp32-idf.yaml b/tests/components/xiaomi_ble/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_ble/test.esp32.yaml b/tests/components/xiaomi_ble/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_ble/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgd1/common.yaml b/tests/components/xiaomi_cgd1/common.yaml new file mode 100644 index 000000000000..94ed09e8f2e6 --- /dev/null +++ b/tests/components/xiaomi_cgd1/common.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgd1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgd1/test.esp32-c3.yaml b/tests/components/xiaomi_cgd1/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgd1/test.esp32-idf.yaml b/tests/components/xiaomi_cgd1/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgd1/test.esp32.yaml b/tests/components/xiaomi_cgd1/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgd1/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgdk2/common.yaml b/tests/components/xiaomi_cgdk2/common.yaml new file mode 100644 index 000000000000..dddca56222ff --- /dev/null +++ b/tests/components/xiaomi_cgdk2/common.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgdk2 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml b/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml b/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgdk2/test.esp32.yaml b/tests/components/xiaomi_cgdk2/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgdk2/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgg1/common.yaml b/tests/components/xiaomi_cgg1/common.yaml new file mode 100644 index 000000000000..170aebfbde37 --- /dev/null +++ b/tests/components/xiaomi_cgg1/common.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_cgg1 + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd + temperature: + name: Xiaomi CGD1 Temperature + humidity: + name: Xiaomi CGD1 Humidity + battery_level: + name: Xiaomi CGD1 Battery Level diff --git a/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgg1/test.esp32-c3.yaml b/tests/components/xiaomi_cgg1/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgg1/test.esp32-idf.yaml b/tests/components/xiaomi_cgg1/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgg1/test.esp32.yaml b/tests/components/xiaomi_cgg1/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgg1/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgpr1/common.yaml b/tests/components/xiaomi_cgpr1/common.yaml new file mode 100644 index 000000000000..48082a886c78 --- /dev/null +++ b/tests/components/xiaomi_cgpr1/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_cgpr1 + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + battery_level: + name: CGPR1 battery Level + idle_time: + name: CGPR1 Idle Time + illuminance: + name: CGPR1 Illuminance diff --git a/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml b/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml b/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml b/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_cgpr1/test.esp32.yaml b/tests/components/xiaomi_cgpr1/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_cgpr1/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_gcls002/common.yaml b/tests/components/xiaomi_gcls002/common.yaml new file mode 100644 index 000000000000..32990708ccbf --- /dev/null +++ b/tests/components/xiaomi_gcls002/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_gcls002 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: GCLS02 Temperature + moisture: + name: GCLS02 Moisture + conductivity: + name: GCLS02 Soil Conductivity + illuminance: + name: GCLS02 Illuminance diff --git a/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_gcls002/test.esp32-c3.yaml b/tests/components/xiaomi_gcls002/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_gcls002/test.esp32-idf.yaml b/tests/components/xiaomi_gcls002/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_gcls002/test.esp32.yaml b/tests/components/xiaomi_gcls002/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_gcls002/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccjcy01/common.yaml b/tests/components/xiaomi_hhccjcy01/common.yaml new file mode 100644 index 000000000000..0def90948819 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/common.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccjcy01 + mac_address: 94:2B:FF:5C:91:61 + temperature: + name: Xiaomi HHCCJCY01 Temperature + moisture: + name: Xiaomi HHCCJCY01 Moisture + illuminance: + name: Xiaomi HHCCJCY01 Illuminance + conductivity: + name: Xiaomi HHCCJCY01 Soil Conductivity + battery_level: + name: Xiaomi HHCCJCY01 Battery Level diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccjcy01/test.esp32.yaml b/tests/components/xiaomi_hhccjcy01/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccjcy01/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccpot002/common.yaml b/tests/components/xiaomi_hhccpot002/common.yaml new file mode 100644 index 000000000000..2e5fa1462066 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/common.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_hhccpot002 + mac_address: 94:2B:FF:5C:91:61 + moisture: + name: HHCCPOT002 Moisture + conductivity: + name: HHCCPOT002 Soil Conductivity diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml b/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_hhccpot002/test.esp32.yaml b/tests/components/xiaomi_hhccpot002/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_hhccpot002/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_jqjcy01ym/common.yaml b/tests/components/xiaomi_jqjcy01ym/common.yaml new file mode 100644 index 000000000000..54c4b33dcd5e --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_jqjcy01ym + mac_address: 7A:80:8E:19:36:BA + temperature: + name: JQJCY01YM Temperature + humidity: + name: JQJCY01YM Humidity + formaldehyde: + name: JQJCY01YM Formaldehyde + battery_level: + name: JQJCY01YM Battery Level diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml b/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_jqjcy01ym/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02/common.yaml b/tests/components/xiaomi_lywsd02/common.yaml new file mode 100644 index 000000000000..3e40ab8d7088 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/common.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd02 + mac_address: 3F:5B:7D:82:58:4E + temperature: + name: Xiaomi LYWSD02 Temperature + humidity: + name: Xiaomi LYWSD02 Humidity + battery_level: + name: Xiaomi LYWSD02 Battery Level diff --git a/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml b/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml b/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd02/test.esp32.yaml b/tests/components/xiaomi_lywsd02/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd02/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd03mmc/common.yaml b/tests/components/xiaomi_lywsd03mmc/common.yaml new file mode 100644 index 000000000000..d10a859c562b --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/common.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsd03mmc + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c + temperature: + name: Xiaomi LYWSD03MMC Temperature + humidity: + name: Xiaomi LYWSD03MMC Humidity + battery_level: + name: Xiaomi LYWSD03MMC Battery Level diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml b/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsd03mmc/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsdcgq/common.yaml b/tests/components/xiaomi_lywsdcgq/common.yaml new file mode 100644 index 000000000000..d8422b4c0c1e --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/common.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_lywsdcgq + mac_address: 7A:80:8E:19:36:BA + temperature: + name: Xiaomi LYWSDCGQ Temperature + humidity: + name: Xiaomi LYWSDCGQ Humidity + battery_level: + name: Xiaomi LYWSDCGQ Battery Level diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_lywsdcgq/test.esp32.yaml b/tests/components/xiaomi_lywsdcgq/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_lywsdcgq/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc303/common.yaml b/tests/components/xiaomi_mhoc303/common.yaml new file mode 100644 index 000000000000..e4353d3c6ad3 --- /dev/null +++ b/tests/components/xiaomi_mhoc303/common.yaml @@ -0,0 +1,11 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc303 + mac_address: E7:50:59:32:A0:1C + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml b/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml b/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc303/test.esp32.yaml b/tests/components/xiaomi_mhoc303/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc303/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc401/common.yaml b/tests/components/xiaomi_mhoc401/common.yaml new file mode 100644 index 000000000000..ae378f5604ac --- /dev/null +++ b/tests/components/xiaomi_mhoc401/common.yaml @@ -0,0 +1,12 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_mhoc401 + mac_address: E7:50:59:32:A0:1C + bindkey: "eef418daf699a0c188f3bfd17e4565d9" + temperature: + name: MHO-C303 Temperature + humidity: + name: MHO-C303 Humidity + battery_level: + name: MHO-C303 Battery Level diff --git a/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml b/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml b/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mhoc401/test.esp32.yaml b/tests/components/xiaomi_mhoc401/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mhoc401/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale copy/common.yaml b/tests/components/xiaomi_miscale copy/common.yaml new file mode 100644 index 000000000000..89f32ad19957 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/common.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml b/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml b/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml b/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale copy/test.esp32.yaml b/tests/components/xiaomi_miscale copy/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale copy/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale/common.yaml b/tests/components/xiaomi_miscale/common.yaml new file mode 100644 index 000000000000..89f32ad19957 --- /dev/null +++ b/tests/components/xiaomi_miscale/common.yaml @@ -0,0 +1,9 @@ +esp32_ble_tracker: + +sensor: + - platform: xiaomi_miscale + mac_address: '5C:CA:D3:70:D4:A2' + weight: + name: "Xiaomi Mi Scale Weight" + impedance: + name: "Xiaomi Mi Scale Impedance" diff --git a/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml b/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale/test.esp32-c3.yaml b/tests/components/xiaomi_miscale/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale/test.esp32-idf.yaml b/tests/components/xiaomi_miscale/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_miscale/test.esp32.yaml b/tests/components/xiaomi_miscale/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_miscale/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mjyd02yla/common.yaml b/tests/components/xiaomi_mjyd02yla/common.yaml new file mode 100644 index 000000000000..dffcef84c42d --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/common.yaml @@ -0,0 +1,13 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mjyd02yla + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 + idle_time: + name: MJYD02YL-A Idle Time + light: + name: MJYD02YL-A Light Status + battery_level: + name: MJYD02YL-A Battery Level diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mjyd02yla/test.esp32.yaml b/tests/components/xiaomi_mjyd02yla/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mjyd02yla/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mue4094rt/common.yaml b/tests/components/xiaomi_mue4094rt/common.yaml new file mode 100644 index 000000000000..4f0e5ccbae38 --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/common.yaml @@ -0,0 +1,7 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_mue4094rt + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml b/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_mue4094rt/test.esp32.yaml b/tests/components/xiaomi_mue4094rt/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_mue4094rt/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_rtcgq02lm/common.yaml b/tests/components/xiaomi_rtcgq02lm/common.yaml new file mode 100644 index 000000000000..a2e0c66ba5fe --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/common.yaml @@ -0,0 +1,22 @@ +esp32_ble_tracker: + +xiaomi_rtcgq02lm: + - id: motion_rtcgq02lm + mac_address: 01:02:03:04:05:06 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + +binary_sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + motion: + name: Mi Motion Sensor 2 + light: + name: Mi Motion Sensor 2 Light + button: + name: Mi Motion Sensor 2 Button + +sensor: + - platform: xiaomi_rtcgq02lm + id: motion_rtcgq02lm + battery_level: + name: Mi Motion Sensor 2 Battery level diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml b/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_rtcgq02lm/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_wx08zm/common.yaml b/tests/components/xiaomi_wx08zm/common.yaml new file mode 100644 index 000000000000..3e83ad3e95fb --- /dev/null +++ b/tests/components/xiaomi_wx08zm/common.yaml @@ -0,0 +1,10 @@ +esp32_ble_tracker: + +binary_sensor: + - platform: xiaomi_wx08zm + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 + tablet: + name: WX08ZM Tablet Resource + battery_level: + name: WX08ZM Battery Level diff --git a/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml b/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml b/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-c3.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml b/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xiaomi_wx08zm/test.esp32.yaml b/tests/components/xiaomi_wx08zm/test.esp32.yaml new file mode 100644 index 000000000000..dade44d145b9 --- /dev/null +++ b/tests/components/xiaomi_wx08zm/test.esp32.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/xl9535/test.esp32-c3-idf.yaml b/tests/components/xl9535/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..178adf870e9a --- /dev/null +++ b/tests/components/xl9535/test.esp32-c3-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32-c3.yaml b/tests/components/xl9535/test.esp32-c3.yaml new file mode 100644 index 000000000000..178adf870e9a --- /dev/null +++ b/tests/components/xl9535/test.esp32-c3.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32-idf.yaml b/tests/components/xl9535/test.esp32-idf.yaml new file mode 100644 index 000000000000..a65aae890ea6 --- /dev/null +++ b/tests/components/xl9535/test.esp32-idf.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 16 + sda: 17 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp32.yaml b/tests/components/xl9535/test.esp32.yaml new file mode 100644 index 000000000000..a65aae890ea6 --- /dev/null +++ b/tests/components/xl9535/test.esp32.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 16 + sda: 17 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.esp8266.yaml b/tests/components/xl9535/test.esp8266.yaml new file mode 100644 index 000000000000..178adf870e9a --- /dev/null +++ b/tests/components/xl9535/test.esp8266.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xl9535/test.rp2040.yaml b/tests/components/xl9535/test.rp2040.yaml new file mode 100644 index 000000000000..178adf870e9a --- /dev/null +++ b/tests/components/xl9535/test.rp2040.yaml @@ -0,0 +1,26 @@ +i2c: + - id: i2c_xl9535 + scl: 5 + sda: 4 + +xl9535: + - id: xl9535_hub + address: 0x20 + +binary_sensor: + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false diff --git a/tests/components/xpt2046/test.esp32-c3-idf.yaml b/tests/components/xpt2046/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..f3a2cf9aaec3 --- /dev/null +++ b/tests/components/xpt2046/test.esp32-c3-idf.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 4 + interrupt_pin: 3 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-c3.yaml b/tests/components/xpt2046/test.esp32-c3.yaml new file mode 100644 index 000000000000..f3a2cf9aaec3 --- /dev/null +++ b/tests/components/xpt2046/test.esp32-c3.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 6 + mosi_pin: 7 + miso_pin: 5 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 4 + interrupt_pin: 3 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-idf.yaml b/tests/components/xpt2046/test.esp32-idf.yaml new file mode 100644 index 000000000000..bb166866f4fe --- /dev/null +++ b/tests/components/xpt2046/test.esp32-idf.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 13 + dc_pin: 14 + reset_pin: 21 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 18 + interrupt_pin: 19 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp32-s2.yaml b/tests/components/xpt2046/test.esp32-s2.yaml new file mode 100644 index 000000000000..6232ca957bd6 --- /dev/null +++ b/tests/components/xpt2046/test.esp32-s2.yaml @@ -0,0 +1,37 @@ +spi: + clk_pin: 7 + mosi_pin: 11 + miso_pin: 9 + +display: + - platform: ili9xxx + id: my_display + model: ili9341 + cs_pin: 5 + dc_pin: 12 + reset_pin: 33 + auto_clear_enabled: false + data_rate: 40MHz + dimensions: 320x240 + update_interval: never + transform: + mirror_y: false + mirror_x: false + swap_xy: true + +touchscreen: + - platform: xpt2046 + display: my_display + id: my_toucher + update_interval: 50ms + cs_pin: 18 + threshold: 300 + calibration: + x_min: 210 + x_max: 3890 + y_min: 170 + y_max: 3730 + transform: + mirror_x: false + mirror_y: true + swap_xy: true diff --git a/tests/components/xpt2046/test.esp32.yaml b/tests/components/xpt2046/test.esp32.yaml new file mode 100644 index 000000000000..bb166866f4fe --- /dev/null +++ b/tests/components/xpt2046/test.esp32.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 16 + mosi_pin: 17 + miso_pin: 15 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 13 + dc_pin: 14 + reset_pin: 21 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 18 + interrupt_pin: 19 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.esp8266.yaml b/tests/components/xpt2046/test.esp8266.yaml new file mode 100644 index 000000000000..a917290e8e4f --- /dev/null +++ b/tests/components/xpt2046/test.esp8266.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 14 + mosi_pin: 13 + miso_pin: 12 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 15 + dc_pin: 4 + reset_pin: 5 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 0 + interrupt_pin: 16 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/xpt2046/test.rp2040.yaml b/tests/components/xpt2046/test.rp2040.yaml new file mode 100644 index 000000000000..a7a49309ac8a --- /dev/null +++ b/tests/components/xpt2046/test.rp2040.yaml @@ -0,0 +1,34 @@ +spi: + - id: spi_xpt2046 + clk_pin: 2 + mosi_pin: 3 + miso_pin: 4 + +display: + - platform: ili9xxx + id: xpt_display + dimensions: 320x240 + model: TFT 2.4 + cs_pin: 8 + dc_pin: 9 + reset_pin: 10 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 5 + interrupt_pin: 6 + display: xpt_display + update_interval: 50ms + threshold: 400 + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] diff --git a/tests/components/yashima/test.esp32-c3-idf.yaml b/tests/components/yashima/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..4b6d6daee481 --- /dev/null +++ b/tests/components/yashima/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32-c3.yaml b/tests/components/yashima/test.esp32-c3.yaml new file mode 100644 index 000000000000..4b6d6daee481 --- /dev/null +++ b/tests/components/yashima/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32-idf.yaml b/tests/components/yashima/test.esp32-idf.yaml new file mode 100644 index 000000000000..4b6d6daee481 --- /dev/null +++ b/tests/components/yashima/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp32.yaml b/tests/components/yashima/test.esp32.yaml new file mode 100644 index 000000000000..4b6d6daee481 --- /dev/null +++ b/tests/components/yashima/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/yashima/test.esp8266.yaml b/tests/components/yashima/test.esp8266.yaml new file mode 100644 index 000000000000..296a7ede251f --- /dev/null +++ b/tests/components/yashima/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: yashima + name: Yashima Climate diff --git a/tests/components/zhlt01/test.esp32-c3-idf.yaml b/tests/components/zhlt01/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..d1dc3b4926a8 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32-c3.yaml b/tests/components/zhlt01/test.esp32-c3.yaml new file mode 100644 index 000000000000..d1dc3b4926a8 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-c3.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32-idf.yaml b/tests/components/zhlt01/test.esp32-idf.yaml new file mode 100644 index 000000000000..d1dc3b4926a8 --- /dev/null +++ b/tests/components/zhlt01/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp32.yaml b/tests/components/zhlt01/test.esp32.yaml new file mode 100644 index 000000000000..d1dc3b4926a8 --- /dev/null +++ b/tests/components/zhlt01/test.esp32.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 2 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zhlt01/test.esp8266.yaml b/tests/components/zhlt01/test.esp8266.yaml new file mode 100644 index 000000000000..40a00bc458f4 --- /dev/null +++ b/tests/components/zhlt01/test.esp8266.yaml @@ -0,0 +1,7 @@ +remote_transmitter: + pin: 5 + carrier_duty_percent: 50% + +climate: + - platform: zhlt01 + name: ZH/LT-01 Climate diff --git a/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml b/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..36e1697a3860 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-c3-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32-c3.yaml b/tests/components/zio_ultrasonic/test.esp32-c3.yaml new file mode 100644 index 000000000000..36e1697a3860 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-c3.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32-idf.yaml b/tests/components/zio_ultrasonic/test.esp32-idf.yaml new file mode 100644 index 000000000000..ad4050307e76 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32-idf.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 16 + sda: 17 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp32.yaml b/tests/components/zio_ultrasonic/test.esp32.yaml new file mode 100644 index 000000000000..ad4050307e76 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp32.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 16 + sda: 17 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.esp8266.yaml b/tests/components/zio_ultrasonic/test.esp8266.yaml new file mode 100644 index 000000000000..36e1697a3860 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.esp8266.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zio_ultrasonic/test.rp2040.yaml b/tests/components/zio_ultrasonic/test.rp2040.yaml new file mode 100644 index 000000000000..36e1697a3860 --- /dev/null +++ b/tests/components/zio_ultrasonic/test.rp2040.yaml @@ -0,0 +1,9 @@ +i2c: + - id: i2c_zio_ultrasonic + scl: 5 + sda: 4 + +sensor: + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s diff --git a/tests/components/zyaura/test.esp32-c3-idf.yaml b/tests/components/zyaura/test.esp32-c3-idf.yaml new file mode 100644 index 000000000000..90205c468c12 --- /dev/null +++ b/tests/components/zyaura/test.esp32-c3-idf.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32-c3.yaml b/tests/components/zyaura/test.esp32-c3.yaml new file mode 100644 index 000000000000..90205c468c12 --- /dev/null +++ b/tests/components/zyaura/test.esp32-c3.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32-idf.yaml b/tests/components/zyaura/test.esp32-idf.yaml new file mode 100644 index 000000000000..29116a978b09 --- /dev/null +++ b/tests/components/zyaura/test.esp32-idf.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 16 + data_pin: 17 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp32.yaml b/tests/components/zyaura/test.esp32.yaml new file mode 100644 index 000000000000..29116a978b09 --- /dev/null +++ b/tests/components/zyaura/test.esp32.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 16 + data_pin: 17 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.esp8266.yaml b/tests/components/zyaura/test.esp8266.yaml new file mode 100644 index 000000000000..90205c468c12 --- /dev/null +++ b/tests/components/zyaura/test.esp8266.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/components/zyaura/test.rp2040.yaml b/tests/components/zyaura/test.rp2040.yaml new file mode 100644 index 000000000000..90205c468c12 --- /dev/null +++ b/tests/components/zyaura/test.rp2040.yaml @@ -0,0 +1,10 @@ +sensor: + - platform: zyaura + clock_pin: 5 + data_pin: 4 + co2: + name: ZyAura CO2 + temperature: + name: ZyAura Temperature + humidity: + name: ZyAura Humidity diff --git a/tests/dashboard/__init__.py b/tests/dashboard/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dashboard/common.py b/tests/dashboard/common.py new file mode 100644 index 000000000000..f84c03aad820 --- /dev/null +++ b/tests/dashboard/common.py @@ -0,0 +1,6 @@ +import pathlib + + +def get_fixture_path(filename: str) -> pathlib.Path: + """Get path of fixture.""" + return pathlib.Path(__file__).parent.joinpath("fixtures", filename) diff --git a/tests/dashboard/fixtures/conf/pico.yaml b/tests/dashboard/fixtures/conf/pico.yaml new file mode 100644 index 000000000000..cf5b5b75bf54 --- /dev/null +++ b/tests/dashboard/fixtures/conf/pico.yaml @@ -0,0 +1,47 @@ +substitutions: + name: picoproxy + friendly_name: Pico Proxy + +esphome: + name: ${name} + friendly_name: ${friendly_name} + project: + name: esphome.bluetooth-proxy + version: "1.0" + +esp32: + board: esp32dev + framework: + type: esp-idf + +wifi: + ap: + +api: +logger: +ota: +improv_serial: + +dashboard_import: + package_import_url: github://esphome/firmware/bluetooth-proxy/esp32-generic.yaml@main + +button: + - platform: factory_reset + id: resetf + - platform: safe_mode + name: Safe Mode Boot + entity_category: diagnostic + +sensor: + - platform: template + id: pm11 + name: "pm 1.0µm" + lambda: return 1.0; + - platform: template + id: pm251 + name: "pm 2.5µm" + lambda: return 2.5; + - platform: template + id: pm101 + name: "pm 10µm" + lambda: return 10; diff --git a/tests/dashboard/test_web_server.py b/tests/dashboard/test_web_server.py new file mode 100644 index 000000000000..a61850abf32e --- /dev/null +++ b/tests/dashboard/test_web_server.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import asyncio +import json +import os +from unittest.mock import Mock + +import pytest +import pytest_asyncio +from tornado.httpclient import AsyncHTTPClient, HTTPResponse +from tornado.httpserver import HTTPServer +from tornado.ioloop import IOLoop +from tornado.testing import bind_unused_port + +from esphome.dashboard import web_server +from esphome.dashboard.core import DASHBOARD + +from .common import get_fixture_path + + +class DashboardTestHelper: + def __init__(self, io_loop: IOLoop, client: AsyncHTTPClient, port: int) -> None: + self.io_loop = io_loop + self.client = client + self.port = port + + async def fetch(self, path: str, **kwargs) -> HTTPResponse: + """Get a response for the given path.""" + if path.lower().startswith(("http://", "https://")): + url = path + else: + url = f"http://127.0.0.1:{self.port}{path}" + future = self.client.fetch(url, raise_error=True, **kwargs) + result = await future + return result + + +@pytest_asyncio.fixture() +async def dashboard() -> DashboardTestHelper: + sock, port = bind_unused_port() + args = Mock( + ha_addon=True, + configuration=get_fixture_path("conf"), + port=port, + ) + DASHBOARD.settings.parse_args(args) + app = web_server.make_app() + http_server = HTTPServer(app) + http_server.add_sockets([sock]) + await DASHBOARD.async_setup() + os.environ["DISABLE_HA_AUTHENTICATION"] = "1" + assert DASHBOARD.settings.using_password is False + assert DASHBOARD.settings.on_ha_addon is True + assert DASHBOARD.settings.using_auth is False + task = asyncio.create_task(DASHBOARD.async_run()) + client = AsyncHTTPClient() + io_loop = IOLoop(make_current=False) + yield DashboardTestHelper(io_loop, client, port) + task.cancel() + sock.close() + client.close() + io_loop.close() + + +@pytest.mark.asyncio +async def test_main_page(dashboard: DashboardTestHelper) -> None: + response = await dashboard.fetch("/") + assert response.code == 200 + + +@pytest.mark.asyncio +async def test_devices_page(dashboard: DashboardTestHelper) -> None: + response = await dashboard.fetch("/devices") + assert response.code == 200 + assert response.headers["content-type"] == "application/json" + json_data = json.loads(response.body.decode()) + configured_devices = json_data["configured"] + first_device = configured_devices[0] + assert first_device["name"] == "pico" + assert first_device["configuration"] == "pico.yaml" diff --git a/tests/dashboard/util/__init__.py b/tests/dashboard/util/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/dashboard/util/test_file.py b/tests/dashboard/util/test_file.py new file mode 100644 index 000000000000..51ba10b328bc --- /dev/null +++ b/tests/dashboard/util/test_file.py @@ -0,0 +1,56 @@ +import os +from pathlib import Path +from unittest.mock import patch + +import py +import pytest + +from esphome.dashboard.util.file import write_file, write_utf8_file + + +def test_write_utf8_file(tmp_path: Path) -> None: + write_utf8_file(tmp_path.joinpath("foo.txt"), "foo") + assert tmp_path.joinpath("foo.txt").read_text() == "foo" + + with pytest.raises(OSError): + write_utf8_file(Path("/dev/not-writable"), "bar") + + +def test_write_file(tmp_path: Path) -> None: + write_file(tmp_path.joinpath("foo.txt"), b"foo") + assert tmp_path.joinpath("foo.txt").read_text() == "foo" + + +def test_write_utf8_file_fails_at_rename( + tmpdir: py.path.local, caplog: pytest.LogCaptureFixture +) -> None: + """Test that if rename fails not not remove, we do not log the failed cleanup.""" + test_dir = tmpdir.mkdir("files") + test_file = Path(test_dir / "test.json") + + with ( + pytest.raises(OSError), + patch("esphome.dashboard.util.file.os.replace", side_effect=OSError), + ): + write_utf8_file(test_file, '{"some":"data"}', False) + + assert not os.path.exists(test_file) + + assert "File replacement cleanup failed" not in caplog.text + + +def test_write_utf8_file_fails_at_rename_and_remove( + tmpdir: py.path.local, caplog: pytest.LogCaptureFixture +) -> None: + """Test that if rename and remove both fail, we log the failed cleanup.""" + test_dir = tmpdir.mkdir("files") + test_file = Path(test_dir / "test.json") + + with ( + pytest.raises(OSError), + patch("esphome.dashboard.util.file.os.remove", side_effect=OSError), + patch("esphome.dashboard.util.file.os.replace", side_effect=OSError), + ): + write_utf8_file(test_file, '{"some":"data"}', False) + + assert "File replacement cleanup failed" in caplog.text diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index 236b9f5fc2fd..da5c6d10d047 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", "LivingRoomArea", "comment", __DATE__ ", " __TIME__, false); auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); log->set_uart_selection(logger::UART_SELECTION_UART0); diff --git a/tests/test1.1.yaml b/tests/test1.1.yaml new file mode 100644 index 000000000000..c71aa6e0efea --- /dev/null +++ b/tests/test1.1.yaml @@ -0,0 +1,232 @@ +--- +substitutions: + devicename: test1_1 + sensorname: my + textname: template + roomname: fastled_room + +esphome: + name: test1-1 + name_add_mac_suffix: true + platform: ESP32 + board: nodemcu-32s + platformio_options: + board_build.partitions: huge_app.csv + on_loop: + then: + - light.addressable_set: + id: addr1 + range_from: 0 + range_to: 100 + red: 100% + green: !lambda "return 255;" + blue: 0% + white: 100% + +wled: + +wifi: + networks: + - ssid: "MySSID" + password: "password1" + +uart: + - id: adalight_uart + tx_pin: GPIO25 + rx_pin: GPIO26 + baud_rate: 115200 + rx_buffer_size: 1024 + +adalight: + +network: + +e131: + +power_supply: + - id: atx_power_supply + enable_time: 20ms + keep_on_time: 10s + enable_on_boot: true + pin: + number: 13 + inverted: true + +i2c: + sda: 21 + scl: + number: 22 + allow_other_uses: true + scan: true + frequency: 100kHz + setup_priority: -100 + id: i2c_bus + +pca9685: + frequency: 500 + address: 0x0 + i2c_id: i2c_bus + +output: + - platform: pca9685 + id: pca_0 + channel: 0 + - platform: pca9685 + id: pca_1 + channel: 1 + - platform: pca9685 + id: pca_2 + channel: 2 + +light: + - platform: rgb + name: Living Room Lights + id: ${roomname}_lights + red: pca_0 + green: pca_1 + blue: pca_2 + - platform: fastled_clockless + id: addr1 + chipset: WS2811 + pin: + allow_other_uses: true + number: GPIO23 + num_leds: 60 + rgb_order: BRG + max_refresh_rate: 20ms + power_supply: atx_power_supply + color_correct: [75%, 100%, 50%] + name: FastLED WS2811 Light + effects: + - addressable_color_wipe: + - addressable_color_wipe: + name: Color Wipe Effect With Custom Values + colors: + - red: 100% + green: 100% + blue: 100% + num_leds: 1 + - red: 0% + green: 0% + blue: 0% + num_leds: 1 + add_led_interval: 100ms + reverse: false + - addressable_scan: + - addressable_scan: + name: Scan Effect With Custom Values + move_interval: 100ms + - addressable_twinkle: + - addressable_twinkle: + name: Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 4ms + - addressable_random_twinkle: + - addressable_random_twinkle: + name: Random Twinkle Effect With Custom Values + twinkle_probability: 5% + progress_interval: 32ms + - addressable_fireworks: + - addressable_fireworks: + name: Fireworks Effect With Custom Values + update_interval: 32ms + spark_probability: 10% + use_random_color: false + fade_out_rate: 120 + - addressable_flicker: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + - addressable_lambda: + name: Test For Custom Lambda Effect + lambda: |- + if (initial_run) { + it[0] = current_color; + } + + - wled: + port: 11111 + + - adalight: + uart_id: adalight_uart + + - e131: + universe: 1 + + - automation: + name: Custom Effect + sequence: + - light.addressable_set: + id: addr1 + red: 100% + green: 100% + blue: 0% + - delay: 100ms + - light.addressable_set: + id: addr1 + red: 0% + green: 100% + blue: 0% + + - platform: fastled_spi + id: addr2 + chipset: WS2801 + data_pin: + allow_other_uses: true + number: GPIO23 + clock_pin: + number: GPIO22 + allow_other_uses: true + data_rate: 2MHz + num_leds: 60 + rgb_order: BRG + name: FastLED SPI Light + - platform: neopixelbus + id: addr3 + name: Neopixelbus Light + gamma_correct: 2.8 + color_correct: [0.0, 0.0, 0.0, 0.0] + default_transition_length: 10s + power_supply: atx_power_supply + effects: + - addressable_flicker: + name: Flicker Effect With Custom Values + update_interval: 16ms + intensity: 5% + type: GRBW + variant: SK6812 + method: ESP32_I2S_0 + num_leds: 60 + pin: + allow_other_uses: true + number: GPIO23 + - platform: partition + name: Partition Light + segments: + - id: addr1 + from: 0 + to: 0 + - id: addr2 + from: 1 + to: 10 + - id: addr2 + from: 20 + to: 25 + - single_light_id: ${roomname}_lights + +canbus: + - platform: esp32_can + id: esp32_internal_can + rx_pin: GPIO04 + tx_pin: GPIO05 + can_id: 4 + bit_rate: 50kbps + +button: + - platform: template + name: Canbus Actions + on_press: + - canbus.send: "abc" + - canbus.send: [0, 1, 2] + - canbus.send: !lambda return {0, 1, 2}; diff --git a/tests/test1.yaml b/tests/test1.yaml index 3a6cfa0c4bcb..79b836da4a31 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -25,14 +25,6 @@ esphome: then: - lambda: >- ESP_LOGV("main", "ON LOOP!"); - - light.addressable_set: - id: addr1 - range_from: 0 - range_to: 100 - red: 100% - green: !lambda "return 255;" - blue: 0% - white: 100% - http_request.get: url: https://esphome.io headers: @@ -81,6 +73,13 @@ wifi: domain: .local reboot_timeout: 120s power_save_mode: light + on_connect: + - light.turn_on: ${roomname}_lights + on_disconnect: + - light.turn_off: ${roomname}_lights + +network: + enable_ipv6: true mdns: disabled: false @@ -182,23 +181,36 @@ mqtt: - light.turn_off: ${roomname}_lights i2c: - sda: 21 - scl: 22 + sda: + allow_other_uses: true + number: 21 + scl: + allow_other_uses: true + number: 22 scan: true frequency: 100kHz setup_priority: -100 id: i2c_bus spi: - clk_pin: GPIO21 - mosi_pin: GPIO22 - miso_pin: GPIO23 + id: spi_bus + clk_pin: + allow_other_uses: true + number: GPIO21 + mosi_pin: + allow_other_uses: true + number: GPIO22 + miso_pin: + allow_other_uses: true + number: GPIO23 uart: - tx_pin: + allow_other_uses: true number: GPIO22 inverted: true rx_pin: + allow_other_uses: true number: GPIO23 inverted: true baud_rate: 115200 @@ -219,18 +231,34 @@ uart: - lambda: UARTDebug::log_string(direction, bytes); - lambda: UARTDebug::log_int(direction, bytes, ','); - lambda: UARTDebug::log_binary(direction, bytes, ';'); - - - id: adalight_uart - tx_pin: GPIO25 - rx_pin: GPIO26 - baud_rate: 115200 - rx_buffer_size: 1024 - id: ld2410_uart - tx_pin: 18 - rx_pin: 23 + tx_pin: + allow_other_uses: true + number: 18 + rx_pin: + allow_other_uses: true + number: 23 baud_rate: 256000 parity: NONE stop_bits: 1 + - id: dfrobot_mmwave_uart + tx_pin: + allow_other_uses: true + number: 14 + rx_pin: + allow_other_uses: true + number: 27 + baud_rate: 115200 + - id: ld2420_uart + tx_pin: + allow_other_uses: true + number: 17 + rx_pin: + allow_other_uses: true + number: 16 + baud_rate: 115200 + parity: NONE + stop_bits: 1 - id: gcja5_uart rx_pin: GPIO10 parity: EVEN @@ -278,28 +306,55 @@ power_supply: keep_on_time: 10s pin: number: 13 + allow_other_uses: true inverted: true deep_sleep: run_duration: 20s sleep_duration: 50s - wakeup_pin: GPIO2 + wakeup_pin: + allow_other_uses: true + number: GPIO2 + ignore_strapping_warning: true wakeup_pin_mode: INVERT_WAKEUP ads1115: address: 0x48 i2c_id: i2c_bus -dallas: - pin: GPIO23 +ads1118: + spi_id: spi_bus + cs_pin: + allow_other_uses: true + number: GPIO12 -as3935_spi: - cs_pin: GPIO12 - irq_pin: GPIO13 +as5600: + i2c_id: i2c_bus + dir_pin: + number: 27 + allow_other_uses: true + direction: clockwise + start_position: 90deg + range: 180deg + watchdog: true + power_mode: low1 + hysteresis: lsb1 + slow_filter: 8x + fast_filter: lsb6 -wled: +dallas: + pin: + allow_other_uses: true + number: GPIO23 -adalight: +as3935_spi: + cs_pin: + ignore_strapping_warning: true + allow_other_uses: true + number: GPIO12 + irq_pin: + allow_other_uses: true + number: GPIO13 esp32_ble: io_capability: keyboard_only @@ -309,8 +364,10 @@ esp32_ble_tracker: ble_client: - mac_address: AA:BB:CC:DD:EE:FF id: ble_foo + auto_connect: true - mac_address: 11:22:33:44:55:66 id: ble_blah + auto_connect: false on_connect: then: - switch.turn_on: ble1_status @@ -329,7 +386,7 @@ ble_client: then: - ble_client.numeric_comparison_reply: id: ble_blah - accept: True + accept: true - mac_address: C4:4F:33:11:22:33 id: my_bedjet_ble_client @@ -339,15 +396,39 @@ bedjet: time_id: sntp_time mcp23s08: - id: mcp23s08_hub - cs_pin: GPIO12 + cs_pin: + ignore_strapping_warning: true + number: GPIO12 + allow_other_uses: true deviceaddress: 0 mcp23s17: - id: mcp23s17_hub - cs_pin: GPIO12 + cs_pin: + ignore_strapping_warning: true + number: GPIO12 + allow_other_uses: true deviceaddress: 1 +micronova: + enable_rx_pin: + allow_other_uses: true + number: 4 + uart_id: uart_0 + +dfrobot_sen0395: + - id: mmwave + uart_id: dfrobot_mmwave_uart + sensor: + - platform: xgzp68xx + i2c_id: i2c_bus + temperature: + name: Pressure Temperature + pressure: + name: Differential pressure + k_value: 4096 + - platform: pmwcs3 i2c_id: i2c_bus e25: @@ -496,6 +577,24 @@ sensor: state_topic: hi/me retain: false availability: + - platform: ads1118 + name: ads1118 adc + multiplexer: A0_A1 + gain: 1.024 + type: adc + - platform: ads1118 + name: ads1118 temperature + type: temperature + - platform: as5600 + name: AS5600 Position + raw_position: + name: AS5600 Raw Position + gain: + name: AS5600 Gain + magnitude: + name: AS5600 Magnitude + status: + name: AS5600 Status - platform: as7341 update_interval: 15s gain: X8 @@ -523,7 +622,9 @@ sensor: name: NIR i2c_id: i2c_bus - platform: atm90e26 - cs_pin: 5 + cs_pin: + allow_other_uses: true + number: 5 voltage: name: Line Voltage current: @@ -542,7 +643,9 @@ sensor: gain_voltage: 26400 gain_ct: 31251 - platform: atm90e32 - cs_pin: 5 + cs_pin: + allow_other_uses: true + number: 5 phase_a: voltage: name: EMON Line Voltage A @@ -590,6 +693,7 @@ sensor: internal: true address: 0x23 update_interval: 30s + qos: 2 retain: false availability: state_topic: livingroom/custom_state_topic @@ -601,20 +705,6 @@ sensor: update_interval: 30s mode: low_power i2c_id: i2c_bus - - platform: bme280 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - oversampling: none - humidity: - name: Outside Humidity - oversampling: 8x - address: 0x77 - iir_filter: 16x - update_interval: 15s - i2c_id: i2c_bus - platform: bme680 temperature: name: Outside Temperature @@ -659,7 +749,9 @@ sensor: index: 1 name: Living Room Temperature 2 - platform: dht - pin: GPIO26 + pin: + allow_other_uses: true + number: GPIO26 temperature: id: dht_temperature name: Living Room Temperature 3 @@ -676,7 +768,9 @@ sensor: update_interval: 15s i2c_id: i2c_bus - platform: duty_cycle - pin: GPIO25 + pin: + allow_other_uses: true + number: GPIO25 name: Duty Cycle Sensor - platform: ee895 co2: @@ -705,9 +799,15 @@ sensor: update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 - sel_pin: 5 - cf_pin: 14 - cf1_pin: 13 + sel_pin: + allow_other_uses: true + number: 5 + cf_pin: + allow_other_uses: true + number: 14 + cf1_pin: + allow_other_uses: true + number: 13 current: name: HLW8012 Current voltage: @@ -721,7 +821,7 @@ sensor: update_interval: 15s current_resistor: 0.001 ohm voltage_divider: 2351 - change_mode_every: 16 + change_mode_every: "never" initial_mode: VOLTAGE model: hlw8012 - platform: total_daily_energy @@ -749,6 +849,13 @@ sensor: oversampling: 8x update_interval: 15s i2c_id: i2c_bus + - platform: honeywell_hih_i2c + temperature: + name: Living Room Temperature 7 + humidity: + name: Living Room Humidity 7 + update_interval: 15s + i2c_id: i2c_bus - platform: honeywellabp pressure: name: Honeywell pressure @@ -756,7 +863,19 @@ sensor: max_pressure: 15 temperature: name: Honeywell temperature - cs_pin: GPIO5 + cs_pin: + allow_other_uses: true + number: GPIO5 + - platform: honeywellabp2_i2c + pressure: + name: Honeywell2 pressure + min_pressure: 0 + max_pressure: 16000 + transfer_function: A + temperature: + name: Honeywell temperature + i2c_id: i2c_bus + address: 0x28 - platform: hte501 temperature: name: Office Temperature 2 @@ -780,8 +899,12 @@ sensor: i2c_id: i2c_bus - platform: hx711 name: HX711 Value - dout_pin: GPIO23 - clk_pin: GPIO25 + dout_pin: + allow_other_uses: true + number: GPIO23 + clk_pin: + allow_other_uses: true + number: GPIO25 gain: 128 update_interval: 15s - platform: ina219 @@ -834,7 +957,8 @@ sensor: name: Internal Ttemperature update_interval: 15s i2c_id: i2c_bus - - platform: kalman_combinator + - platform: combination + type: kalman name: Kalman-filtered temperature process_std_dev: 0.00139 sources: @@ -843,31 +967,92 @@ sensor: return 0.4 + std::abs(x - 25) * 0.023; - source: scd4x_temperature error: 1.5 + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: scd30_temperature + coeffecient: !lambda |- + return 0.4 + std::abs(x - 25) * 0.023; + - source: scd4x_temperature + coeffecient: 1.5 + - platform: combination + type: max + name: Max of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: mean + name: Mean of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: median + name: Median of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: min + name: Min of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: most_recently_updated + name: Most recently updated of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: range + name: Range of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature + - platform: combination + type: sum + name: Sum of combined temperatures + sources: + - source: scd30_temperature + - source: scd4x_temperature - platform: htu21d temperature: name: Living Room Temperature 6 humidity: name: Living Room Humidity 6 + heater: + name: Living Room Heater 6 update_interval: 15s i2c_id: i2c_bus - platform: max6675 name: Living Room Temperature - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 update_interval: 15s - platform: max31855 name: Den Temperature - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 update_interval: 15s reference_temperature: name: MAX31855 Internal Temperature - platform: max31856 name: BBQ Temperature - cs_pin: GPIO17 + cs_pin: + allow_other_uses: true + number: GPIO17 update_interval: 15s mains_filter: 50Hz - platform: max31865 name: Water Tank Temperature - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 update_interval: 15s reference_resistance: 430 Ω rtd_nominal_resistance: 100 Ω @@ -913,6 +1098,23 @@ sensor: temperature: name: MPU6886 Temperature i2c_id: i2c_bus + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature + i2c_id: i2c_bus - platform: mmc5603 address: 0x30 field_strength_x: @@ -962,7 +1164,10 @@ sensor: i2c_id: i2c_bus - platform: pulse_counter name: Pulse Counter - pin: GPIO12 + pin: + ignore_strapping_warning: true + number: GPIO12 + allow_other_uses: true count_mode: rising_edge: INCREMENT falling_edge: DECREMENT @@ -971,7 +1176,10 @@ sensor: - platform: pulse_meter name: Pulse Meter id: pulse_meter_sensor - pin: GPIO12 + pin: + ignore_strapping_warning: true + number: GPIO12 + allow_other_uses: true internal_filter: 100ms timeout: 2 min on_value: @@ -994,9 +1202,15 @@ sensor: - platform: rotary_encoder name: Rotary Encoder id: rotary_encoder1 - pin_a: GPIO23 - pin_b: GPIO25 - pin_reset: GPIO25 + pin_a: + allow_other_uses: true + number: GPIO23 + pin_b: + allow_other_uses: true + number: GPIO25 + pin_reset: + allow_other_uses: true + number: GPIO25 filters: - or: - debounce: 0.1s @@ -1013,13 +1227,17 @@ sensor: value: !lambda "return -1;" on_clockwise: - logger.log: Clockwise - - display_menu.down: + - display_menu.down: test_lcd_menu + - display_menu.down: test_graphical_display_menu on_anticlockwise: - logger.log: Anticlockwise - - display_menu.up: + - display_menu.up: test_lcd_menu + - display_menu.up: test_graphical_display_menu - platform: pulse_width name: Pulse Width - pin: GPIO12 + pin: + allow_other_uses: true + number: GPIO12 - platform: sm300d2 uart_id: uart_0 co2: @@ -1079,6 +1297,21 @@ sensor: ambient_pressure_compensation: 961mBar temperature_offset: 4.2C i2c_id: i2c_bus + - platform: sfa30 + formaldehyde: + name: "SFA30 formaldehyde" + temperature: + name: "SFA30 temperature" + humidity: + name: "SFA30 humidity" + i2c_id: i2c_bus + address: 0x5D + update_interval: 30s + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s + i2c_id: i2c_bus - platform: sgp30 eco2: name: Workshop eCO2 @@ -1182,14 +1415,27 @@ sensor: name: tsl2591 calculated_lux id: tsl2591_cl i2c_id: i2c_bus + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + i2c_id: i2c_bus + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms - platform: tee501 name: Office Temperature 3 address: 0x48 i2c_id: i2c_bus - platform: ultrasonic - trigger_pin: GPIO25 + trigger_pin: + allow_other_uses: true + number: GPIO25 echo_pin: number: GPIO23 + allow_other_uses: true inverted: true name: Ultrasonic Sensor timeout: 5.5m @@ -1236,9 +1482,14 @@ sensor: pin: number: GPIO04 mode: INPUT + allow_other_uses: true - platform: zyaura - clock_pin: GPIO5 - data_pin: GPIO4 + clock_pin: + allow_other_uses: true + number: GPIO5 + data_pin: + allow_other_uses: true + number: GPIO4 co2: name: ZyAura CO2 temperature: @@ -1404,6 +1655,9 @@ sensor: still_energy: name: g8 still energy + - platform: ld2420 + moving_distance: + name: "Moving distance (cm)" - platform: sen21231 name: "Person Sensor" i2c_id: i2c_bus @@ -1423,6 +1677,12 @@ sensor: id: temp_etuve humidity: name: "Humidity hyt271" + - platform: iaqcore + i2c_id: i2c_bus + co2: + name: "iAQ Core CO2 Sensor" + tvoc: + name: "iAQ Core TVOC Sensor" - platform: tmp1075 name: "Temperature TMP1075" update_interval: 10s @@ -1446,6 +1706,102 @@ sensor: pressure: name: "BMP581 Pressure" oversampling: 128x + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + - platform: mmc5983 + i2c_id: i2c_bus + field_strength_x: + name: "Magnet X" + id: magnet_x + field_strength_y: + name: "Magnet Y" + id: magnet_y + field_strength_z: + name: "Magnet Z" + id: magnet_z + - platform: micronova + room_temperature: + name: Room Temperature + fumes_temperature: + name: Fumes Temperature + water_temperature: + name: Water temperature + water_pressure: + name: Water pressure + stove_power: + name: Stove Power + fan_speed: + fan_rpm_offset: 240 + name: Fan RPM + memory_address_sensor: + memory_location: 0x20 + memory_address: 0x7d + name: Adres sensor + - platform: ade7880 + i2c_id: i2c_bus + irq0_pin: + number: GPIO13 + allow_other_uses: true + irq1_pin: + number: GPIO5 + allow_other_uses: true + reset_pin: + number: GPIO16 + allow_other_uses: true + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 + +psram: esp32_touch: setup_mode: false @@ -1472,6 +1828,7 @@ binary_sensor: mcp23xxx: mcp23s17_hub # Use pin number 1 number: 1 + allow_other_uses: true # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP inverted: false @@ -1480,13 +1837,16 @@ binary_sensor: pin: mcp23xxx: mcp23s17_hub # Use pin number 1 + allow_other_uses: true number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP inverted: false interrupt: FALLING - platform: gpio - pin: GPIO9 + pin: + allow_other_uses: true + number: GPIO9 name: Living Room Window device_class: window filters: @@ -1500,6 +1860,8 @@ binary_sensor: - delayed_on_off: !lambda "return 10;" - delayed_on: !lambda "return 1000;" - delayed_off: !lambda "return 0;" + - settle: 40ms + - settle: !lambda "return 10;" on_press: then: - lambda: >- @@ -1555,11 +1917,13 @@ binary_sensor: - platform: gpio pin: number: GPIO9 + allow_other_uses: true mode: INPUT_PULLUP name: Living Room Window 2 - platform: gpio pin: number: GPIO9 + allow_other_uses: true mode: INPUT_OUTPUT_OPEN_DRAIN name: Living Room Button - platform: status @@ -1572,13 +1936,22 @@ binary_sensor: on_press: - if: condition: - display_menu.is_active: + display_menu.is_active: test_lcd_menu + then: + - display_menu.enter: test_lcd_menu + else: + - display_menu.left: test_lcd_menu + - display_menu.right: test_lcd_menu + - display_menu.show: test_lcd_menu + - if: + condition: + display_menu.is_active: test_graphical_display_menu then: - - display_menu.enter: + - display_menu.enter: test_graphical_display_menu else: - - display_menu.left: - - display_menu.right: - - display_menu.show: + - display_menu.left: test_graphical_display_menu + - display_menu.right: test_graphical_display_menu + - display_menu.show: test_graphical_display_menu - platform: template name: Garage Door Open id: garage_door @@ -1638,6 +2011,7 @@ binary_sensor: pin: mcp23xxx: mcp23017_hub number: 1 + allow_other_uses: true mode: INPUT inverted: true - platform: gpio @@ -1654,7 +2028,19 @@ binary_sensor: number: 7 mode: INPUT inverted: false - + - platform: gpio + name: Speed Fan Cycle binary sensor" + pin: + number: 18 + allow_other_uses: true + mode: + input: true + pulldown: true + on_press: + - fan.cycle_speed: + id: fan_speed + off_speed_cycle: false + - logger.log: "Cycle speed clicked" - platform: remote_receiver name: Raw Remote Receiver Test raw: @@ -1747,6 +2133,24 @@ binary_sensor: name: still out_pin_presence_status: name: out pin presence status + - platform: qwiic_pir + i2c_id: i2c_bus + name: "Qwiic PIR Motion Sensor" + - platform: dfrobot_sen0395 + id: mmwave_detected_uart + dfrobot_sen0395_id: mmwave + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + ndef_contains: pulse + name: MFC Tag 1 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + tag_id: pulse + name: MFC Tag 2 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + uid: 59-FC-AB-15 + name: MFC Tag 3 pca9685: frequency: 500 @@ -1765,42 +2169,66 @@ tlc59208f: i2c_id: i2c_bus my9231: - data_pin: GPIO12 - clock_pin: GPIO14 + data_pin: + allow_other_uses: true + number: GPIO12 + clock_pin: + allow_other_uses: true + number: GPIO14 num_channels: 6 num_chips: 2 bit_depth: 16 sm2235: - data_pin: GPIO4 - clock_pin: GPIO5 + data_pin: + allow_other_uses: true + number: GPIO4 + clock_pin: + allow_other_uses: true + number: GPIO5 max_power_color_channels: 9 max_power_white_channels: 9 sm2335: - data_pin: GPIO4 - clock_pin: GPIO5 + data_pin: + allow_other_uses: true + number: GPIO4 + clock_pin: + allow_other_uses: true + number: GPIO5 max_power_color_channels: 9 max_power_white_channels: 9 bp1658cj: - data_pin: GPIO3 - clock_pin: GPIO5 + data_pin: + allow_other_uses: true + number: GPIO3 + clock_pin: + allow_other_uses: true + number: GPIO5 max_power_color_channels: 4 max_power_white_channels: 6 bp5758d: - data_pin: GPIO3 - clock_pin: GPIO5 + data_pin: + allow_other_uses: true + number: GPIO3 + clock_pin: + allow_other_uses: true + number: GPIO5 output: - platform: gpio - pin: GPIO26 + pin: + allow_other_uses: true + number: GPIO26 id: gpio_26 power_supply: atx_power_supply inverted: false - platform: ledc - pin: 19 + pin: + allow_other_uses: true + number: 19 id: gpio_19 frequency: 1500Hz channel: 14 @@ -1870,6 +2298,7 @@ output: pin: pcf8574: pcf8574_hub number: 0 + # allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -1877,6 +2306,7 @@ output: pin: pca9554: pca9554_hub number: 0 + # allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio @@ -1950,14 +2380,22 @@ output: channel: 3 - platform: slow_pwm id: id24 - pin: GPIO26 + pin: + allow_other_uses: true + number: GPIO26 period: 15s - platform: ac_dimmer id: dimmer1 - gate_pin: GPIO5 - zero_cross_pin: GPIO26 + gate_pin: + allow_other_uses: true + number: GPIO5 + zero_cross_pin: + allow_other_uses: true + number: GPIO26 - platform: esp32_dac - pin: GPIO25 + pin: + allow_other_uses: true + number: GPIO25 id: dac_output - platform: mcp4725 id: mcp4725_dac_output @@ -2021,13 +2459,17 @@ output: current: 10 - platform: x9c id: test_x9c - cs_pin: GPIO25 - inc_pin: GPIO26 - ud_pin: GPIO27 + cs_pin: + allow_other_uses: true + number: GPIO25 + inc_pin: + allow_other_uses: true + number: GPIO26 + ud_pin: + allow_other_uses: true + number: GPIO27 initial_value: 0.5 -e131: - light: - platform: binary name: Desk Lamp @@ -2070,6 +2512,20 @@ light: state += 1; if (state == 4) state = 0; + - pulse: + transition_length: 10s + update_interval: 20s + min_brightness: 10% + max_brightness: 90% + - pulse: + name: pulse2 + transition_length: + on_length: 10s + off_length: 5s + update_interval: 15s + min_brightness: 10% + max_brightness: 90% + - platform: rgb name: Living Room Lights id: ${roomname}_lights @@ -2116,128 +2572,11 @@ light: brightness: pca_6 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds - - platform: fastled_clockless - id: addr1 - chipset: WS2811 - pin: GPIO23 - num_leds: 60 - rgb_order: BRG - max_refresh_rate: 20ms - power_supply: atx_power_supply - color_correct: [75%, 100%, 50%] - name: FastLED WS2811 Light - effects: - - addressable_color_wipe: - - addressable_color_wipe: - name: Color Wipe Effect With Custom Values - colors: - - red: 100% - green: 100% - blue: 100% - num_leds: 1 - - red: 0% - green: 0% - blue: 0% - num_leds: 1 - add_led_interval: 100ms - reverse: false - - addressable_scan: - - addressable_scan: - name: Scan Effect With Custom Values - move_interval: 100ms - - addressable_twinkle: - - addressable_twinkle: - name: Twinkle Effect With Custom Values - twinkle_probability: 5% - progress_interval: 4ms - - addressable_random_twinkle: - - addressable_random_twinkle: - name: Random Twinkle Effect With Custom Values - twinkle_probability: 5% - progress_interval: 32ms - - addressable_fireworks: - - addressable_fireworks: - name: Fireworks Effect With Custom Values - update_interval: 32ms - spark_probability: 10% - use_random_color: false - fade_out_rate: 120 - - addressable_flicker: - - addressable_flicker: - name: Flicker Effect With Custom Values - update_interval: 16ms - intensity: 5% - - addressable_lambda: - name: Test For Custom Lambda Effect - lambda: |- - if (initial_run) { - it[0] = current_color; - } - - - wled: - port: 11111 - - - adalight: - uart_id: adalight_uart - - - automation: - name: Custom Effect - sequence: - - light.addressable_set: - id: addr1 - red: 100% - green: 100% - blue: 0% - - delay: 100ms - - light.addressable_set: - id: addr1 - red: 0% - green: 100% - blue: 0% - - e131: - universe: 1 - - platform: fastled_spi - id: addr2 - chipset: WS2801 - data_pin: GPIO23 - clock_pin: GPIO22 - data_rate: 2MHz - num_leds: 60 - rgb_order: BRG - name: FastLED SPI Light - - platform: neopixelbus - id: addr3 - name: Neopixelbus Light - gamma_correct: 2.8 - color_correct: [0.0, 0.0, 0.0, 0.0] - default_transition_length: 10s - power_supply: atx_power_supply - effects: - - addressable_flicker: - name: Flicker Effect With Custom Values - update_interval: 16ms - intensity: 5% - type: GRBW - variant: SK6812 - method: ESP32_I2S_0 - num_leds: 60 - pin: GPIO23 - - platform: partition - name: Partition Light - segments: - - id: addr1 - from: 0 - to: 0 - - id: addr2 - from: 1 - to: 10 - - id: addr2 - from: 20 - to: 25 - - single_light_id: ${roomname}_lights remote_transmitter: - - pin: 32 + - pin: + allow_other_uses: true + number: 32 carrier_duty_percent: 100% climate: @@ -2284,6 +2623,10 @@ climate: name: Yashima Climate - platform: mitsubishi name: Mitsubishi + supports_dry: "true" + supports_fan_only: "true" + horizontal_default: "left" + vertical_default: "down" - platform: whirlpool name: Whirlpool Climate - platform: climate_ir_lg @@ -2368,6 +2711,16 @@ climate: heat_mode: extended - platform: whynter name: Whynter + - platform: noblex + name: AC Living + id: noblex_ac + sensor: ${sensorname}_sensor + receiver_id: rcvr + - platform: gree + name: GREE + model: generic + - platform: zhlt01 + name: ZH/LT-01 Climate script: - id: climate_custom @@ -2419,10 +2772,13 @@ switch: mcp23xxx: mcp23s17_hub # Use pin number 0 number: 1 + allow_other_uses: true mode: OUTPUT inverted: false - platform: gpio - pin: GPIO25 + pin: + allow_other_uses: true + number: GPIO25 name: Living Room Dehumidifier icon: "mdi:restart" inverted: true @@ -2586,6 +2942,26 @@ switch: 0x00, 0xFF, ] + - platform: template + name: Haier + turn_on_action: + remote_transmitter.transmit_haier: + code: + [ + 0xA6, + 0xDA, + 0x00, + 0x00, + 0x40, + 0x40, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x05, + ] - platform: template name: Living Room Lights id: livingroom_lights @@ -2665,6 +3041,12 @@ switch: name: UART Recurring Output data: [0xDE, 0xAD, 0xBE, 0xEF] send_every: 1s + - platform: uart + uart_id: uart_0 + name: "UART On/Off" + data: + turn_on: "TurnOn\r\n" + turn_off: "TurnOff\r\n" - platform: template assumed_state: true name: Stepper Switch @@ -2685,7 +3067,7 @@ switch: - platform: gpio name: "SN74HC595 Pin #0" pin: - sn74hc595: sn74hc595_hub + sn74hc595: sn74hc595_hub_2 # Use pin number 0 number: 0 inverted: false @@ -2701,6 +3083,9 @@ switch: name: "control ld2410 engineering mode" bluetooth: name: "control ld2410 bluetooth" + - platform: micronova + stove: + name: Stove on/off fan: - platform: binary @@ -2725,6 +3110,33 @@ fan: on_speed_set: then: - logger.log: Fan speed was changed! + - platform: speed + id: fan_speed_presets + icon: mdi:weather-windy + output: pca_6 + speed_count: 10 + name: Speed Fan w/ Presets + oscillation_output: gpio_19 + direction_output: gpio_26 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! + - platform: hbridge + id: fan_hbridge_presets + icon: mdi:weather-windy + speed_count: 4 + name: H-bridge Fan w/ Presets + pin_a: pca_6 + pin_b: pca_7 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! - platform: bedjet name: My Bedjet fan bedjet_id: my_bedjet_client @@ -2758,6 +3170,16 @@ interval: page_id: page1 then: - logger.log: Seeing page 1 + - interval: 60min + then: + - ble_client.connect: ble_blah + - ble_client.ble_write: + id: ble_blah + service_uuid: EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6 + characteristic_uuid: EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6 + value: !lambda |- + return {1, 0}; + - ble_client.disconnect: ble_blah color: - id: kbx_red @@ -2776,12 +3198,20 @@ display: id: my_lcd_gpio dimensions: 18x4 data_pins: - - GPIO19 - - GPIO21 - - GPIO22 - - GPIO23 - enable_pin: GPIO23 - rs_pin: GPIO25 + - allow_other_uses: true + number: GPIO19 + - allow_other_uses: true + number: GPIO21 + - allow_other_uses: true + number: GPIO22 + - allow_other_uses: true + number: GPIO23 + enable_pin: + allow_other_uses: true + number: GPIO23 + rs_pin: + allow_other_uses: true + number: GPIO25 lambda: |- it.print("Hello World!"); - platform: lcd_pcf8574 @@ -2802,13 +3232,19 @@ display: it.print("Hello World!"); i2c_id: i2c_bus - platform: max7219 - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 num_chips: 1 lambda: |- it.print("01234567"); - platform: tm1637 - clk_pin: GPIO23 - dio_pin: GPIO25 + clk_pin: + allow_other_uses: true + number: GPIO23 + dio_pin: + allow_other_uses: true + number: GPIO25 intensity: 3 lambda: |- it.print("1234"); @@ -2816,6 +3252,7 @@ display: clk_pin: mcp23xxx: mcp23017_hub number: 1 + allow_other_uses: true dio_pin: mcp23xxx: mcp23017_hub number: 2 @@ -2825,15 +3262,23 @@ display: lambda: |- it.print("1234"); - platform: pcd8544 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 contrast: 60 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1306_i2c model: SSD1306_128X64 - reset_pin: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 address: 0x3C id: display1 contrast: 60% @@ -2854,28 +3299,48 @@ display: i2c_id: i2c_bus - platform: ssd1306_spi model: SSD1306 128x64 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi model: SSD1322 256x64 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1325_spi model: SSD1325 128x64 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1327_i2c model: SSD1327 128X128 - reset_pin: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 address: 0x3D id: display1327 brightness: 60% @@ -2889,45 +3354,96 @@ display: i2c_id: i2c_bus - platform: ssd1327_spi model: SSD1327 128x128 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1331_spi - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1351_spi model: SSD1351 128x128 - cs_pin: GPIO23 - dc_pin: GPIO23 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 + dc_pin: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7789v model: TTGO TDisplay 135x240 - cs_pin: GPIO5 - dc_pin: GPIO16 - reset_pin: GPIO23 - backlight_pin: GPIO4 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO16 + reset_pin: + allow_other_uses: true + number: GPIO23 + backlight_pin: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7920 width: 128 height: 64 cs_pin: + allow_other_uses: true number: GPIO23 inverted: true lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7567_i2c + id: st7735_display_i2c + address: 0x3F + i2c_id: i2c_bus + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7567_spi + id: st7735_display_spi + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO16 + reset_pin: + allow_other_uses: true + number: GPIO23 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 + id: st7735_display model: INITR_BLACKTAB - cs_pin: GPIO5 - dc_pin: GPIO16 - reset_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO16 + reset_pin: + allow_other_uses: true + number: GPIO23 rotation: 0 device_width: 128 device_height: 160 @@ -2936,17 +3452,41 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9xxx + invert_colors: true + dimensions: 320x240 + transform: + swap_xy: true + mirror_x: true + mirror_y: false model: TFT 2.4 - cs_pin: GPIO5 - dc_pin: GPIO4 - reset_pin: GPIO22 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO4 + color_palette: GRAYSCALE + reset_pin: + allow_other_uses: true + number: GPIO22 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9xxx + dimensions: + width: 320 + height: 240 + offset_width: 20 + offset_height: 10 model: TFT 2.4 - cs_pin: GPIO5 - dc_pin: GPIO4 - reset_pin: GPIO22 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO4 + reset_pin: + allow_other_uses: true + number: GPIO22 auto_clear_enabled: false rotation: 90 lambda: |- @@ -2971,10 +3511,18 @@ display: it.print_battery(true); - platform: tm1621 id: tm1621_display - cs_pin: GPIO17 - data_pin: GPIO5 - read_pin: GPIO23 - write_pin: GPIO18 + cs_pin: + allow_other_uses: true + number: GPIO17 + data_pin: + allow_other_uses: true + number: GPIO5 + read_pin: + allow_other_uses: true + number: GPIO23 + write_pin: + allow_other_uses: true + number: GPIO18 lambda: |- it.printf(0, "%.1f", id(dht_temperature).state); it.display_celsius(true); @@ -2983,22 +3531,37 @@ display: tm1651: id: tm1651_battery - clk_pin: GPIO23 - dio_pin: GPIO23 + clk_pin: + allow_other_uses: true + number: GPIO23 + dio_pin: + allow_other_uses: true + number: GPIO23 remote_receiver: - pin: GPIO32 + id: rcvr + pin: + allow_other_uses: true + number: GPIO32 dump: all on_coolix: then: delay: !lambda "return x.first + x.second;" + on_rc_switch: + then: + delay: !lambda "return uint32_t(x.code) + x.protocol;" status_led: - pin: GPIO2 + pin: + allow_other_uses: true + number: GPIO2 + ignore_strapping_warning: true pn532_spi: id: pn532_bs - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 update_interval: 1s on_tag: - lambda: |- @@ -3016,11 +3579,60 @@ pn532_spi: pn532_i2c: i2c_id: i2c_bus +pn7150_i2c: + id: nfcc_pn7150_i2c + i2c_id: i2c_bus + irq_pin: + allow_other_uses: true + number: GPIO32 + ven_pin: + allow_other_uses: true + number: GPIO16 + +pn7160_i2c: + id: nfcc_pn7160_i2c + i2c_id: i2c_bus + dwl_req_pin: + allow_other_uses: true + number: GPIO17 + irq_pin: + allow_other_uses: true + number: GPIO35 + ven_pin: + allow_other_uses: true + number: GPIO16 + wkup_req_pin: + allow_other_uses: true + number: GPIO21 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + +pn7160_spi: + id: nfcc_pn7160_spi + cs_pin: + number: GPIO15 + dwl_req_pin: + allow_other_uses: true + number: GPIO17 + irq_pin: + allow_other_uses: true + number: GPIO35 + ven_pin: + allow_other_uses: true + number: GPIO16 + wkup_req_pin: + allow_other_uses: true + number: GPIO21 + emulation_message: https://www.home-assistant.io/tag/pulse_ce + tag_ttl: 1000ms + rdm6300: uart_id: uart_0 rc522_spi: - cs_pin: GPIO23 + cs_pin: + allow_other_uses: true + number: GPIO23 update_interval: 1s on_tag: - lambda: |- @@ -3067,11 +3679,39 @@ time: - platform: ds1307 id: ds1307_time update_interval: never - on_time: - seconds: 0 - then: ds1307.read_time i2c_id: i2c_bus - + on_time: + - seconds: 0 + then: ds1307.read_time + - at: "16:00:00" + then: + - if: + condition: + or: + - binary_sensor.is_on: close_sensor + - binary_sensor.is_on: open_sensor + then: + logger.log: "close_sensor or open_sensor is on" + - if: + condition: + and: + - binary_sensor.is_on: close_sensor + - binary_sensor.is_on: open_sensor + then: + logger.log: "close_sensor and open_sensor are both on" + - if: + condition: + xor: + - binary_sensor.is_on: close_sensor + - binary_sensor.is_on: open_sensor + then: + logger.log: "close_sensor or open_sensor is exclusively on" + - if: + condition: + not: + - binary_sensor.is_on: close_sensor + then: + logger.log: "close_sensor is not on" cover: - platform: template name: Template Cover @@ -3157,6 +3797,7 @@ pcf8574: pca9554: - id: pca9554_hub + pin_count: 8 address: 0x3F i2c_id: i2c_bus @@ -3184,9 +3825,15 @@ mcp23016: stepper: - platform: a4988 id: my_stepper - step_pin: GPIO23 - dir_pin: GPIO25 - sleep_pin: GPIO25 + step_pin: + allow_other_uses: true + number: GPIO23 + dir_pin: + allow_other_uses: true + number: GPIO25 + sleep_pin: + allow_other_uses: true + number: GPIO25 max_speed: 250 steps/s acceleration: 100 steps/s^2 deceleration: 200 steps/s^2 @@ -3245,6 +3892,10 @@ text_sensor: canbus_id: mcp2515_can can_id: 23 data: [0x10, 0x20, 0x30] + - canbus.send: + canbus_id: mcp2515_can + can_id: 23 + data: !lambda return {0x10, 0x20, 0x30}; - canbus.send: canbus_id: esp32_internal_can can_id: 23 @@ -3262,6 +3913,10 @@ text_sensor: - platform: template name: Template Text Sensor id: ${textname}_text + - platform: template + name: Template Text Sensor Timestamp + id: ${textname}_text_timestamp + device_class: timestamp - platform: wifi_info scan_results: name: Scan Results @@ -3290,11 +3945,29 @@ text_sensor: sn74hc595: - id: sn74hc595_hub - data_pin: GPIO21 - clock_pin: GPIO23 - latch_pin: GPIO22 - oe_pin: GPIO32 + data_pin: + allow_other_uses: true + number: GPIO21 + clock_pin: + allow_other_uses: true + number: GPIO23 + latch_pin: + allow_other_uses: true + number: GPIO22 + oe_pin: + allow_other_uses: true + number: GPIO32 + sr_count: 2 + - id: sn74hc595_hub_2 + latch_pin: + allow_other_uses: true + number: GPIO22 + oe_pin: + allow_other_uses: true + number: GPIO32 sr_count: 2 + spi_id: spi_bus + type: spi rtttl: output: gpio_19 @@ -3302,7 +3975,12 @@ rtttl: canbus: - platform: mcp2515 id: mcp2515_can - cs_pin: GPIO17 + cs_pin: + pca9554: pca9554_hub + number: 7 + mode: + output: true + inverted: true can_id: 4 bit_rate: 50kbps on_frame: @@ -3339,8 +4017,12 @@ canbus: } - platform: esp32_can id: esp32_internal_can - rx_pin: GPIO04 - tx_pin: GPIO05 + rx_pin: + allow_other_uses: true + number: GPIO04 + tx_pin: + allow_other_uses: true + number: GPIO05 can_id: 4 bit_rate: 50kbps on_frame: @@ -3445,7 +4127,12 @@ number: name: g8 move threshold still_threshold: name: g8 still threshold - + - platform: micronova + thermostat_temperature: + name: Micronova Thermostaat + step: 1 + power_level: + name: Micronova Power level select: - platform: template @@ -3530,6 +4217,41 @@ button: name: Midea Power Inverse on_press: midea_ac.power_toggle: + - platform: template + name: Update Mmwave Sensor Settings + on_press: + - dfrobot_sen0395.settings: + id: mmwave + factory_reset: true + detection_segments: + - [0cm, 5m] + - 600cm + - !lambda |- + return 7; + output_latency: + delay_after_detect: 0s + delay_after_disappear: 0s + sensitivity: 6 + - platform: template + name: Reset Mmwave Sensor + on_press: + - dfrobot_sen0395.reset: + - platform: template + name: Poller component suspend test + on_press: + - component.suspend: myteleinfo + - delay: 20s + - component.update: myteleinfo + - delay: 20s + - component.resume: myteleinfo + - delay: 20s + - component.resume: + id: myteleinfo + update_interval: 2s + - delay: 20s + - component.resume: + id: myteleinfo + update_interval: !lambda return 2500; - platform: ld2410 factory_reset: name: "factory reset" @@ -3537,12 +4259,27 @@ button: name: "restart" query_params: name: query params + - platform: uart + uart_id: uart_0 + name: UART button + data: "Pressed\r\n" + - platform: micronova + custom_button: + name: Custom Micronova Button + memory_location: 0xA0 + memory_address: 0x7D + memory_data: 0x0F ld2410: id: my_ld2410 uart_id: ld2410_uart +ld2420: + id: my_ld2420 + uart_id: ld2420_uart + lcd_menu: + id: test_lcd_menu display_id: my_lcd_gpio mark_back: 0x5e mark_selected: 0x3e @@ -3574,7 +4311,7 @@ lcd_menu: text: Show Main on_value: then: - - display_menu.show_main: + - display_menu.show_main: test_lcd_menu - type: select text: Enum Item immediate_edit: true @@ -3604,7 +4341,7 @@ lcd_menu: text: Hide on_value: then: - - display_menu.hide: + - display_menu.hide: test_lcd_menu - type: switch text: Switch switch: my_switch @@ -3624,6 +4361,91 @@ lcd_menu: then: lambda: 'ESP_LOGI("lcd_menu", "custom prev: %s", it->get_text().c_str());' +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + +graphical_display_menu: + id: test_graphical_display_menu + display: st7735_display + font: roboto + active: false + mode: rotary + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root enter");' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "root leave");' + items: + - type: back + text: "Back" + - type: label + - type: menu + text: "Submenu 1" + items: + - type: back + text: "Back" + - type: menu + text: "Submenu 21" + items: + - type: back + text: "Back" + - type: command + text: "Show Main" + on_value: + then: + - display_menu.show_main: test_graphical_display_menu + - type: select + text: "Enum Item" + immediate_edit: true + select: test_select + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "select value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: number + text: "Number" + number: test_number + on_enter: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number enter: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_leave: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number leave: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "number value: %s, %s", it->get_text().c_str(), it->get_value_text().c_str());' + - type: command + text: "Hide" + on_value: + then: + - display_menu.hide: test_graphical_display_menu + - type: switch + text: "Switch" + switch: my_switch + on_text: "Bright" + off_text: "Dark" + immediate_edit: false + on_value: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "switch value: %s", it->get_value_text().c_str());' + - type: custom + text: !lambda 'return "Custom";' + value_lambda: 'return "Val";' + on_next: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom next: %s", it->get_text().c_str());' + on_prev: + then: + lambda: 'ESP_LOGI("graphical_display_menu", "custom prev: %s", it->get_text().c_str());' + alarm_control_panel: - platform: template id: alarmcontrolpanel1 diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml new file mode 100644 index 000000000000..13de7f1929cb --- /dev/null +++ b/tests/test11.5.yaml @@ -0,0 +1,808 @@ +--- +# copy of test5.yaml configured to build on IDF 5 +esphome: + name: test11-5 + build_path: build/test11.5 + project: + name: esphome.test11_5_project + version: "1.0.0" + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + version: 5.0.2 + platform_version: 6.3.2 + advanced: + ignore_efuse_mac_crc: true + +wifi: + networks: + - ssid: "MySSID" + password: "password1" + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 + +network: + enable_ipv6: true + +api: + +ota: + +logger: + +debug: + +psram: + +uart: + - id: uart_1 + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + - id: uart_2 + tx_pin: + allow_other_uses: true + number: 17 + rx_pin: + allow_other_uses: true + number: 16 + baud_rate: 19200 + +i2c: + sda: + number: 21 + allow_other_uses: true + frequency: 100khz + +spi: + - id: spi_1 + clk_pin: + allow_other_uses: true + number: 12 + mosi_pin: + allow_other_uses: true + number: 13 + miso_pin: + allow_other_uses: true + number: 14 + - id: spi_2 + clk_pin: + allow_other_uses: true + number: 32 + mosi_pin: 33 + +modbus: + uart_id: uart_1 + flow_control_pin: + allow_other_uses: true + number: 5 + id: mod_bus1 + +modbus_controller: + - id: modbus_controller_test + address: 0x2 + modbus_id: mod_bus1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + idf_send_async: false + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + # yamllint disable rule:line-length + - lambda: |- + ESP_LOGD("Mqtt Test", "testing/sensor/testing_sensor/state=[%s]", x.c_str()); + # yamllint enable rule:line-length + +vbus: + - uart_id: uart_2 + +binary_sensor: + - platform: gpio + pin: GPIO0 + id: io0_button + icon: mdi:gesture-tap-button + + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_binsensortest + register_type: read + address: 0x3200 + bitmask: 0x80 # (bit 8) + lambda: "return x;" + + - platform: tm1638 + id: Button0 + key: 0 + filters: + - delayed_on: 10ms + on_press: + then: + - switch.turn_on: Led0 + on_release: + then: + - switch.turn_off: Led0 + - if: + condition: ble.enabled + then: + - ble.disable: + else: + - ble.enable: + + - platform: tm1638 + id: Button1 + key: 1 + on_press: + then: + - switch.turn_on: Led1 + on_release: + then: + - switch.turn_off: Led1 + + - platform: tm1638 + id: Button2 + key: 2 + on_press: + then: + - switch.turn_on: Led2 + on_release: + then: + - switch.turn_off: Led2 + + - platform: tm1638 + id: Button3 + key: 3 + on_press: + then: + - switch.turn_on: Led3 + on_release: + then: + - switch.turn_off: Led3 + + - platform: tm1638 + id: Button4 + key: 4 + on_press: + then: + - output.turn_on: Led4 + on_release: + then: + - output.turn_off: Led4 + + - platform: tm1638 + id: Button5 + key: 5 + on_press: + then: + - output.turn_on: Led5 + on_release: + then: + - output.turn_off: Led5 + + - platform: tm1638 + id: Button6 + key: 6 + on_press: + then: + - output.turn_on: Led6 + on_release: + then: + - output.turn_off: Led6 + + - platform: tm1638 + id: Button7 + key: 7 + on_press: + then: + - output.turn_on: Led7 + on_release: + then: + - output.turn_off: Led7 + + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 + + - platform: ezo_pmp + pump_state: + name: "Pump State" + is_paused: + name: "Is Paused" + + - platform: matrix_keypad + keypad_id: keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + + - platform: vbus + model: deltasol_bs_plus + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +tlc5947: + data_pin: + allow_other_uses: true + number: GPIO12 + clock_pin: + allow_other_uses: true + number: GPIO14 + lat_pin: + allow_other_uses: true + number: GPIO15 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gpio + pin: GPIO2 + id: built_in_led + + - platform: tlc5947 + id: output_red + channel: 0 + max_power: 0.8 + + - platform: mcp47a1 + id: output_mcp47a1 + + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_output_test + lambda: |- + return x * 1.0 ; + address: 0x9001 + value_type: U_WORD + + - platform: tm1638 + id: Led4 + led: 4 + + - platform: tm1638 + id: Led5 + led: 5 + + - platform: tm1638 + id: Led6 + led: 6 + + - platform: tm1638 + id: Led7 + led: 7 + + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 + +demo: + +esp32_ble: + enable_on_boot: false + +esp32_ble_server: + manufacturer: ESPHome + model: Test11 + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +number: + - platform: template + name: My template number + id: template_number_id + optimistic: true + max_value: 100 + min_value: 0 + step: 5 + unit_of_measurement: "%" + mode: slider + device_class: humidity + on_value: + - logger.log: + format: Number changed to %f + args: [x] + set_action: + - logger.log: + format: Template Number set to %f + args: [x] + - number.set: + id: template_number_id + value: 50 + - number.to_min: template_number_id + - number.to_min: + id: template_number_id + - number.to_max: template_number_id + - number.to_max: + id: template_number_id + - number.increment: template_number_id + - number.increment: + id: template_number_id + cycle: false + - number.decrement: template_number_id + - number.decrement: + id: template_number_id + cycle: false + - number.operation: + id: template_number_id + operation: Increment + cycle: false + - number.operation: + id: template_number_id + operation: !lambda "return NUMBER_OP_INCREMENT;" + cycle: !lambda "return false;" + + - id: modbus_numbertest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + name: ModbusNumber + address: 0x9002 + value_type: U_WORD + lambda: "return x * 1.0;" + write_lambda: |- + return x * 1.0 ; + multiply: 1.0 + +select: + - platform: template + name: My template select + id: template_select_id + optimistic: true + initial_option: two + restore_value: true + on_value: + - logger.log: + format: Select changed to %s (index %d)" + args: ["x.c_str()", "i"] + set_action: + - logger.log: + format: Template Select set to %s + args: ["x.c_str()"] + - select.set: + id: template_select_id + option: two + - select.first: template_select_id + - select.last: + id: template_select_id + - select.previous: template_select_id + - select.next: + id: template_select_id + cycle: false + - select.operation: + id: template_select_id + operation: Previous + cycle: false + - select.operation: + id: template_select_id + operation: !lambda "return SELECT_OP_PREVIOUS;" + cycle: !lambda "return true;" + - select.set_index: + id: template_select_id + index: 1 + - select.set_index: + id: template_select_id + index: !lambda "return 1 + 1;" + options: + - one + - two + - three + + - platform: modbus_controller + name: Modbus Select Register 1000 + address: 1000 + value_type: U_WORD + optionsmap: + "Zero": 0 + "One": 1 + "Two": 2 + "Three": 3 + +sensor: + - platform: adc + id: adc_sensor_p32 + name: ADC pin 32 + pin: + allow_other_uses: true + number: 32 + attenuation: 11db + update_interval: 1s + - platform: internal_temperature + name: Internal Temperature + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true + + - id: modbus_sensortest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + address: 0x331A + register_type: read + value_type: U_WORD + + - platform: t6615 + uart_id: uart_2 + co2: + name: CO2 Sensor + + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature + + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + + - platform: vbus + model: deltasol c + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time + + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + sensors: + - id: vcustom + name: VBus Custom Sensor + lambda: return x[0] / 10.0; + + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature + + - platform: ade7953_i2c + irq_pin: + allow_other_uses: true + number: 16 + voltage: + name: ADE7953 Voltage + current_a: + name: ADE7953 Current A + current_b: + name: ADE7953 Current B + power_factor_a: + name: "ADE7953 Power Factor A" + power_factor_b: + name: "ADE7953 Power Factor B" + apparent_power_a: + name: "ADE7953 Apparent Power A" + apparent_power_b: + name: "ADE7953 Apparent Power B" + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: "ADE7953 Reactive Power A" + reactive_power_b: + name: "ADE7953 Reactive Power B" + update_interval: 1s + + - platform: ade7953_spi + spi_id: spi_1 + cs_pin: 04 + irq_pin: + allow_other_uses: true + number: 16 + voltage: + name: ADE7953 Voltage + current_a: + name: ADE7953 Current A + current_b: + name: ADE7953 Current B + power_factor_a: + name: "ADE7953 Power Factor A" + power_factor_b: + name: "ADE7953 Power Factor B" + apparent_power_a: + name: "ADE7953 Apparent Power A" + apparent_power_b: + name: "ADE7953 Apparent Power B" + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: "ADE7953 Reactive Power A" + reactive_power_b: + name: "ADE7953 Reactive Power B" + update_interval: 1s + +script: + - id: automation_test + then: + - repeat: + count: 5 + then: + - logger.log: looping! + + - id: zero_repeat_test + then: + - repeat: + count: !lambda "return 0;" + then: + - logger.log: shouldn't see mee! + +switch: + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_switch_test + register_type: coil + address: 2 + bitmask: 1 + + - platform: tm1638 + id: Led0 + led: 0 + name: TM1638Led0 + + - platform: tm1638 + id: Led1 + led: 1 + name: TM1638Led1 + + - platform: tm1638 + id: Led2 + led: 2 + name: TM1638Led2 + + - platform: tm1638 + id: Led3 + led: 3 + name: TM1638Led3 + +display: + - platform: tm1638 + id: primarydisplay + stb_pin: + allow_other_uses: true + number: 5 # TM1638 STB + clk_pin: 18 # TM1638 CLK + dio_pin: 23 # TM1638 DIO + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); + +time: + - platform: pcf85063 + - platform: pcf8563 + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? + +sn74hc165: + id: sn74hc165_hub + data_pin: + allow_other_uses: true + number: GPIO12 + clock_pin: + allow_other_uses: true + number: GPIO14 + load_pin: + number: GPIO27 + clock_inhibit_pin: + number: GPIO26 + sr_count: 4 + +matrix_keypad: + id: keypad + rows: + - pin: + allow_other_uses: true + number: 21 + - pin: 19 + columns: + - pin: + allow_other_uses: true + number: 17 + - pin: + allow_other_uses: true + number: 16 + keys: "1234" + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: + allow_other_uses: true + number: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: + allow_other_uses: true + number: 15 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/test2.yaml b/tests/test2.yaml index d1508632b3e8..2fdef72c087f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -5,40 +5,70 @@ esphome: board: nodemcu-32s build_path: build/test2 +globals: + - id: my_global_string + type: std::string + restore_value: true + max_restore_data_length: 70 + initial_value: '"DefaultValue"' + substitutions: devicename: test2 ethernet: type: LAN8720 - mdc_pin: GPIO23 - mdio_pin: GPIO25 + mdc_pin: + allow_other_uses: true + number: GPIO23 + mdio_pin: + allow_other_uses: true + number: GPIO25 clk_mode: GPIO0_IN phy_addr: 0 - power_pin: GPIO25 + power_pin: + allow_other_uses: true + number: GPIO25 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 subnet: 255.255.255.0 domain: .local +network: + enable_ipv6: true + mdns: disabled: true api: i2c: - sda: 21 - scl: 22 + sda: + allow_other_uses: true + number: 21 + scl: + allow_other_uses: true + number: 22 scan: false spi: - clk_pin: GPIO21 - mosi_pin: GPIO22 - miso_pin: GPIO23 + clk_pin: + allow_other_uses: true + number: GPIO21 + mosi_pin: + allow_other_uses: true + number: GPIO22 + miso_pin: + allow_other_uses: true + number: GPIO23 uart: - tx_pin: GPIO22 - rx_pin: GPIO23 + tx_pin: + allow_other_uses: true + number: GPIO22 + rx_pin: + allow_other_uses: true + number: GPIO23 baud_rate: 115200 # Specifically added for testing debug with no after: definition. debug: @@ -55,27 +85,37 @@ ota: logger: level: DEBUG +debug: + deep_sleep: run_duration: default: 20s gpio_wakeup_reason: 10s touch_wakeup_reason: 15s sleep_duration: 50s - wakeup_pin: GPIO2 + wakeup_pin: + allow_other_uses: true + number: GPIO2 wakeup_pin_mode: INVERT_WAKEUP as3935_i2c: - irq_pin: GPIO12 + irq_pin: + allow_other_uses: true + number: GPIO12 mcp3008: - id: mcp3008_hub - cs_pin: GPIO12 + cs_pin: + allow_other_uses: true + number: GPIO12 output: - platform: ac_dimmer id: dimmer1 gate_pin: GPIO5 - zero_cross_pin: GPIO12 + zero_cross_pin: + allow_other_uses: true + number: GPIO12 sensor: - platform: homeassistant @@ -163,6 +203,18 @@ sensor: name: Xiaomi HHCCJCY01 Soil Conductivity battery_level: name: Xiaomi HHCCJCY01 Battery Level + - platform: xiaomi_hhccjcy10 + mac_address: DD:25:6D:E4:FF:8F + temperature: + name: "Xiaomi HHCCJCY10 Temperature" + moisture: + name: "Xiaomi HHCCJCY10 Moisture" + illuminance: + name: "Xiaomi HHCCJCY10 Illuminance" + conductivity: + name: "Xiaomi HHCCJCY10 Soil Conductivity" + battery_level: + name: "Xiaomi HHCCJCY10 Battery Level" - platform: xiaomi_lywsdcgq mac_address: 7A:80:8E:19:36:BA temperature: @@ -461,6 +513,7 @@ binary_sensor: - platform: ble_presence mac_address: AC:37:43:77:5F:4C name: ESP32 BLE Tracker Google Home Mini + timeout: 30s - platform: ble_presence service_uuid: 11aa name: BLE Test Service 16 Presence @@ -522,7 +575,9 @@ binary_sensor: name: Mi Motion Sensor 2 Button - platform: gpio id: gpio_set_retry_test - pin: GPIO9 + pin: + allow_other_uses: true + number: GPIO9 on_press: then: - lambda: |- @@ -589,7 +644,9 @@ xiaomi_rtcgq02lm: bindkey: "48403ebe2d385db8d0c187f81e62cb64" status_led: - pin: GPIO2 + pin: + allow_other_uses: true + number: GPIO2 text_sensor: - platform: version @@ -692,9 +749,13 @@ script: stepper: - platform: uln2003 id: my_stepper - pin_a: GPIO23 + pin_a: + allow_other_uses: true + number: GPIO23 pin_b: GPIO27 - pin_c: GPIO25 + pin_c: + allow_other_uses: true + number: GPIO25 pin_d: GPIO26 sleep_when_done: false step_mode: HALF_STEP @@ -706,10 +767,22 @@ stepper: interval: interval: 5s + startup_delay: 10s then: - logger.log: Interval Run display: + - platform: st7789v + model: LILYGO_T-EMBED_170X320 + spi_mode: mode0 + height: 320 + width: 170 + offset_height: 35 + offset_width: 0 + dc_pin: GPIO13 + reset_pin: + allow_other_uses: true + number: GPIO9 image: - id: binary_image @@ -726,11 +799,23 @@ image: - id: rgb24_image file: pnglogo.png type: RGB24 - use_transparency: yes + use_transparency: true - id: rgb565_image file: pnglogo.png type: RGB565 - use_transparency: no + use_transparency: false + - id: web_svg_image + file: https://raw.githubusercontent.com/esphome/esphome-docs/a62d7ab193c1a464ed791670170c7d518189109b/images/logo.svg + resize: 256x48 + type: TRANSPARENT_BINARY + - id: web_tiff_image + file: https://upload.wikimedia.org/wikipedia/commons/b/b6/SIPI_Jelly_Beans_4.1.07.tiff + type: RGB24 + resize: 48x48 + - id: web_redirect_image + file: https://avatars.githubusercontent.com/u/3060199?s=48&v=4 + type: RGB24 + resize: 48x48 - id: mdi_alert file: mdi:alert-circle-outline @@ -769,7 +854,23 @@ switch: value: !lambda |- return {0x13, 0x37}; - esp32_ble_server: id: ble manufacturer_data: [0x72, 0x4, 0x00, 0x23] + +text: + - platform: template + name: My Text + id: my_text + min_length: 0 + max_length: 20 + mode: text + pattern: "[a-z]+" + optimistic: true + restore_value: true + initial_value: "Hello World" + - platform: copy + name: My Text Copy + id: my_text_copy + source_id: my_text + mode: password diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index 46bc01420482..2bddd6f4d7a8 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -21,20 +21,40 @@ wifi: ssid: "MySSID" password: "password1" +network: + enable_ipv6: true + +web_server: + port: 80 + version: 2 + i2c: - sda: 4 - scl: 5 + sda: + allow_other_uses: true + number: 4 + scl: + allow_other_uses: true + number: 5 scan: false spi: - clk_pin: GPIO12 - mosi_pin: GPIO13 - miso_pin: GPIO14 + clk_pin: + allow_other_uses: true + number: GPIO12 + mosi_pin: + allow_other_uses: true + number: GPIO13 + miso_pin: + allow_other_uses: true + number: GPIO14 ota: + version: 2 logger: +debug: + sensor: - platform: apds9960 type: proximity @@ -43,7 +63,9 @@ sensor: name: VL53L0x Distance address: 0x29 update_interval: 60s - enable_pin: GPIO13 + enable_pin: + allow_other_uses: true + number: GPIO13 timeout: 200us - platform: apds9960 type: clear @@ -160,8 +182,10 @@ sensor: - id: custom_sensor name: Custom Sensor - - platform: ade7953 - irq_pin: GPIO16 + - platform: ade7953_i2c + irq_pin: + allow_other_uses: true + number: GPIO16 voltage: name: ADE7953 Voltage id: ade7953_voltage @@ -171,13 +195,62 @@ sensor: current_b: name: ADE7953 Current B id: ade7953_current_b + power_factor_a: + name: "ADE7953 Power Factor A" + power_factor_b: + name: "ADE7953 Power Factor B" + apparent_power_a: + name: "ADE7953 Apparent Power A" + apparent_power_b: + name: "ADE7953 Apparent Power B" active_power_a: name: ADE7953 Active Power A - id: ade7953_active_power_a active_power_b: name: ADE7953 Active Power B - id: ade7953_active_power_b - + reactive_power_a: + name: "ADE7953 Reactive Power A" + reactive_power_b: + name: "ADE7953 Reactive Power B" + update_interval: 1s + + - platform: ade7953_spi + cs_pin: + allow_other_uses: true + number: GPIO04 + irq_pin: + allow_other_uses: true + number: GPIO16 + voltage: + name: ADE7953 Voltage + current_a: + name: ADE7953 Current A + current_b: + name: ADE7953 Current B + power_factor_a: + name: "ADE7953 Power Factor A" + power_factor_b: + name: "ADE7953 Power Factor B" + apparent_power_a: + name: "ADE7953 Apparent Power A" + apparent_power_b: + name: "ADE7953 Apparent Power B" + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B + reactive_power_a: + name: "ADE7953 Reactive Power A" + reactive_power_b: + name: "ADE7953 Reactive Power B" + update_interval: 1s + + - platform: ens160 + eco2: + name: "ENS160 eCO2" + tvoc: + name: "ENS160 Total Volatile Organic Compounds" + aqi: + name: "ENS160 Air Quality Index" - platform: tmp102 name: TMP102 Temperature - platform: hm3301 @@ -217,6 +290,62 @@ sensor: id: adc128s102_channel_0 channel: 0 + - platform: ade7880 + irq0_pin: + number: GPIO13 + allow_other_uses: true + irq1_pin: + number: GPIO5 + allow_other_uses: true + reset_pin: + number: GPIO16 + allow_other_uses: true + frequency: 60Hz + phase_a: + name: Channel A + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3116628 + voltage_gain: -757178 + power_gain: -1344457 + phase_angle: 188 + phase_b: + name: Channel B + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3133655 + voltage_gain: -755235 + power_gain: -1345638 + phase_angle: 188 + phase_c: + name: Channel C + voltage: Voltage + current: Current + active_power: Active Power + power_factor: Power Factor + forward_active_energy: Forward Active Energy + reverse_active_energy: Reverse Active Energy + calibration: + current_gain: 3111158 + voltage_gain: -743813 + power_gain: -1351437 + phase_angle: 180 + neutral: + name: Neutral + current: Current + calibration: + current_gain: 3189 + apds9960: address: 0x20 update_interval: 60s @@ -306,8 +435,12 @@ text_sensor: name: Custom Text Sensor sm2135: - data_pin: GPIO12 - clock_pin: GPIO14 + data_pin: + allow_other_uses: true + number: GPIO12 + clock_pin: + allow_other_uses: true + number: GPIO14 rgb_current: 20mA cw_current: 60mA @@ -336,7 +469,9 @@ switch: interlock: *interlock - platform: gpio id: gpio_switch3 - pin: GPIO1 + pin: + allow_other_uses: true + number: GPIO1 interlock: *interlock - platform: custom lambda: |- @@ -368,7 +503,7 @@ switch: - platform: template name: open_vent id: open_vent - optimistic: True + optimistic: true on_turn_on: then: - grove_tb6612fng.run: @@ -377,7 +512,6 @@ switch: direction: BACKWARD id: test_motor - custom_component: lambda: |- auto s = new CustomComponent(); @@ -387,10 +521,18 @@ custom_component: stepper: - platform: uln2003 id: my_stepper - pin_a: GPIO12 - pin_b: GPIO13 - pin_c: GPIO14 - pin_d: GPIO15 + pin_a: + allow_other_uses: true + number: GPIO12 + pin_b: + allow_other_uses: true + number: GPIO13 + pin_c: + allow_other_uses: true + number: GPIO14 + pin_d: + allow_other_uses: true + number: GPIO15 sleep_when_done: false step_mode: HALF_STEP max_speed: 250 steps/s @@ -398,8 +540,12 @@ stepper: deceleration: inf - platform: a4988 id: my_stepper2 - step_pin: GPIO1 - dir_pin: GPIO2 + step_pin: + allow_other_uses: true + number: GPIO1 + dir_pin: + allow_other_uses: true + number: GPIO2 max_speed: 0.1 steps/s acceleration: 10 steps/s^2 deceleration: 10 steps/s^2 @@ -503,11 +649,14 @@ cover: output: - platform: esp8266_pwm id: out - pin: D3 + pin: + number: D3 frequency: 50Hz - platform: esp8266_pwm id: out2 - pin: D4 + pin: + allow_other_uses: true + number: D4 - platform: custom type: binary lambda: |- @@ -519,7 +668,9 @@ output: - platform: sigma_delta_output id: sddac update_interval: 60s - pin: D4 + pin: + allow_other_uses: true + number: D4 turn_on_action: then: - logger.log: "Turned on" @@ -540,7 +691,9 @@ output: outputs: - id: custom_float - platform: slow_pwm - pin: GPIO5 + pin: + allow_other_uses: true + number: GPIO5 id: my_slow_pwm period: 15s restart_cycle_on_state_change: false @@ -566,7 +719,6 @@ mcp23017: mcp23008: id: mcp23008_hub - light: - platform: hbridge name: Icicle Lights @@ -583,13 +735,18 @@ servo: ttp229_lsf: ttp229_bsf: - sdo_pin: D2 - scl_pin: D1 - + sdo_pin: + allow_other_uses: true + number: D2 + scl_pin: + allow_other_uses: true + number: D1 display: - platform: max7219digit - cs_pin: GPIO15 + cs_pin: + allow_other_uses: true + number: GPIO15 num_chips: 4 rotate_chip: 0 intensity: 10 @@ -598,7 +755,6 @@ display: lambda: |- it.printdigit("hello"); - http_request: useragent: esphome/device timeout: 10s @@ -616,10 +772,20 @@ button: name: Restart Button (Factory Default Settings) cd74hc4067: - pin_s0: GPIO12 - pin_s1: GPIO13 - pin_s2: GPIO14 - pin_s3: GPIO15 + pin_s0: + allow_other_uses: true + number: GPIO12 + pin_s1: + allow_other_uses: true + number: GPIO13 + pin_s2: + allow_other_uses: true + number: GPIO14 + pin_s3: + allow_other_uses: true + number: GPIO15 adc128s102: - cs_pin: GPIO12 + cs_pin: + allow_other_uses: true + number: GPIO12 diff --git a/tests/test3.yaml b/tests/test3.yaml index 471b7d97b6ce..61d814385b0c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -215,60 +215,110 @@ wifi: ssid: "MySSID" password: "password1" +network: + enable_ipv6: true + uart: - id: uart_1 tx_pin: number: GPIO1 inverted: true - rx_pin: GPIO3 + allow_other_uses: true + rx_pin: + allow_other_uses: true + number: GPIO3 baud_rate: 115200 - id: uart_2 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_3 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 4800 - id: uart_4 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_5 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_6 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_7 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 38400 - id: uart_8 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 4800 parity: NONE stop_bits: 2 # Specifically added for testing debug with no options at all. debug: - id: uart_9 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_10 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_11 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 - id: uart_12 - tx_pin: GPIO4 - rx_pin: GPIO5 + tx_pin: + allow_other_uses: true + number: GPIO4 + rx_pin: + allow_other_uses: true + number: GPIO5 baud_rate: 9600 modbus: @@ -287,6 +337,8 @@ logger: level: DEBUG esp8266_store_log_strings_in_flash: true +debug: + improv_serial: next_url: https://esphome.io/?name={{device_name}}&version={{esphome_version}}&ip={{ip_address}} @@ -342,6 +394,10 @@ sensor: moisture: name: hydreon_rain id: hydreon_rain + temperature: + name: hydreon_temperature + disable_led: true + - platform: hydreon_rgxx model: RG_15 uart_id: uart_6 @@ -353,6 +409,8 @@ sensor: name: hydreon_total_acc r_int: name: hydreon_r_int + resolution: low + - platform: adc pin: VCC id: my_sensor @@ -470,7 +528,6 @@ sensor: name: PZEMDC Power energy: name: PZEMDC Energy - - platform: pmsx003 uart_id: uart_9 type: PMSX003 @@ -578,7 +635,11 @@ sensor: current: name: CSE7766 Current power: - name: CSE776 Power + name: CSE7766 Power + apparent_power: + name: CSE7766 Apparent Power + power_factor: + name: CSE7766 Power Factor - platform: fingerprint_grow fingerprint_count: @@ -687,6 +748,31 @@ sensor: temperature: name: Kuntze temperature + - platform: haier + haier_id: haier_climate + compressor_current: + name: Haier AC compressor current + compressor_frequency: + name: Haier AC compressor frequency + expansion_valve_open_degree: + name: Haier AC expansion valve open degree + humidity: + name: Haier AC indoor humidity + indoor_coil_temperature: + name: Haier AC indoor coil temperature + outdoor_coil_temperature: + name: Haier AC outdoor coil temperature + outdoor_defrost_temperature: + name: Haier AC outdoor defrost temperature + outdoor_in_air_temperature: + name: Haier AC outdoor in air temperature + outdoor_out_air_temperature: + name: Haier AC outdoor out air temperature + outdoor_temperature: + name: Haier AC outdoor temperature + power: + name: Haier AC power + time: - platform: homeassistant @@ -720,6 +806,7 @@ binary_sensor: name: rg9_emsat lens_bad: name: rg9_lens_bad + - platform: template id: pzemac_reset_energy on_press: @@ -738,13 +825,34 @@ binary_sensor: - platform: gpio id: bin1 - pin: 1 + pin: + allow_other_uses: true + number: 1 - platform: gpio id: bin2 - pin: 2 + pin: + allow_other_uses: true + number: 2 - platform: gpio id: bin3 - pin: 3 + pin: + allow_other_uses: true + number: 3 + + - platform: haier + haier_id: haier_climate + compressor_status: + name: Haier AC compressor status + defrost_status: + name: Haier AC defrost status + four_way_valve_status: + name: Haier AC four-way valve status + indoor_electric_heating_status: + name: Haier AC indoor electric heating status + indoor_fan_status: + name: Haier AC indoor fan status + outdoor_fan_status: + name: Haier AC outdoor fan status globals: - id: my_global_string @@ -752,11 +860,15 @@ globals: initial_value: '""' remote_receiver: - pin: GPIO12 + pin: + allow_other_uses: true + number: GPIO12 dump: [] status_led: - pin: GPIO2 + pin: + allow_other_uses: true + number: GPIO2 text_sensor: - platform: daly_bms @@ -809,13 +921,19 @@ script: switch: - platform: gpio id: gpio_switch1 - pin: 1 + pin: + allow_other_uses: true + number: 1 - platform: gpio id: gpio_switch2 - pin: 2 + pin: + allow_other_uses: true + number: 2 - platform: gpio id: gpio_switch3 - pin: 3 + pin: + allow_other_uses: true + number: 3 - platform: nextion id: r0 @@ -826,6 +944,7 @@ climate: - platform: bang_bang name: Bang Bang Climate sensor: ha_hello_world + humidity_sensor: ha_hello_world default_target_temperature_low: 18°C default_target_temperature_high: 24°C idle_action: @@ -840,6 +959,7 @@ climate: - platform: thermostat name: Thermostat Climate sensor: ha_hello_world + humidity_sensor: ha_hello_world preset: - name: Default Preset default_target_temperature_low: 18°C @@ -927,6 +1047,7 @@ climate: id: pid_climate name: PID Climate Controller sensor: ha_hello_world + humidity_sensor: ha_hello_world default_target_temperature: 21°C heat_output: my_slow_pwm control_parameters: @@ -944,29 +1065,48 @@ climate: kd_multiplier: 0.0 deadband_output_averaging_samples: 1 - platform: haier + id: haier_climate protocol: hOn name: Haier AC uart_id: uart_12 wifi_signal: true + answer_timeout: 200ms beeper: true - outdoor_temperature: - name: Haier AC outdoor temperature visual: min_temperature: 16 °C max_temperature: 30 °C - temperature_step: 1 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY + - "OFF" + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH + - "OFF" + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: 'Alarm activated. Code: %d. Message: "%s"' + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: 'Alarm deactivated. Code: %d. Message: "%s"' + args: [code, message] sprinkler: - id: yard_sprinkler_ctrlr @@ -1013,13 +1153,18 @@ sprinkler: output: - platform: esp8266_pwm id: out - pin: D3 + pin: + number: D3 frequency: 50Hz - platform: esp8266_pwm id: out2 - pin: D4 + pin: + allow_other_uses: true + number: D4 - platform: slow_pwm - pin: GPIO5 + pin: + allow_other_uses: true + number: GPIO5 id: my_slow_pwm period: 15s restart_cycle_on_state_change: false @@ -1029,7 +1174,9 @@ e131: light: - platform: neopixelbus name: Neopixelbus Light - pin: GPIO1 + pin: + allow_other_uses: true + number: GPIO1 type: GRBW variant: SK6812 method: ESP8266_UART0 @@ -1061,6 +1208,12 @@ light: max_brightness: 500 firmware: "51.6" uart_id: uart_11 + nrst_pin: + number: 5 + allow_other_uses: true + boot0_pin: + number: 4 + allow_other_uses: true sim800l: uart_id: uart_4 @@ -1086,8 +1239,12 @@ dfplayer: logger.log: Playback finished event tm1651: id: tm1651_battery - clk_pin: D6 - dio_pin: D5 + clk_pin: + allow_other_uses: true + number: D6 + dio_pin: + allow_other_uses: true + number: D5 rf_bridge: uart_id: uart_5 @@ -1140,9 +1297,22 @@ display: lambda: 'ESP_LOGD("display","Display shows new page %u", x);' fingerprint_grow: - sensing_pin: 4 + sensing_pin: + allow_other_uses: true + number: 4 + sensor_power_pin: + allow_other_uses: true + number: 5 + inverted: true + idle_period_to_sleep: 5s password: 0x12FE37DC new_password: 0xA65B9840 + on_finger_scan_start: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_start + on_finger_scan_invalid: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_invalid on_finger_scan_matched: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_matched @@ -1152,6 +1322,9 @@ fingerprint_grow: on_finger_scan_unmatched: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_misplaced on_enrollment_scan: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_enrollment_scan @@ -1174,7 +1347,9 @@ dsmr: decryption_key: 00112233445566778899aabbccddeeff uart_id: uart_6 max_telegram_length: 1000 - request_pin: D5 + request_pin: + allow_other_uses: true + number: D5 request_interval: 20s receive_timeout: 100ms @@ -1186,6 +1361,13 @@ qr_code: - id: homepage_qr value: https://esphome.io/index.html +lightwaverf: + read_pin: + number: 13 + write_pin: + allow_other_uses: true + number: 14 + alarm_control_panel: - platform: template id: alarmcontrolpanel1 diff --git a/tests/test4.yaml b/tests/test4.yaml index 54caebf1fe9b..993ce126a843 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -10,38 +10,97 @@ substitutions: ethernet: type: LAN8720 - mdc_pin: GPIO23 - mdio_pin: GPIO25 + mdc_pin: + allow_other_uses: true + number: GPIO23 + mdio_pin: + allow_other_uses: true + number: GPIO25 clk_mode: GPIO0_IN phy_addr: 0 - power_pin: GPIO25 + power_pin: + allow_other_uses: true + number: GPIO25 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 subnet: 255.255.255.0 domain: .local +network: + enable_ipv6: true + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + topic_prefix: + api: i2c: - sda: 21 - scl: 22 + sda: + allow_other_uses: true + number: 21 + scl: + allow_other_uses: true + number: 22 scan: false spi: - clk_pin: GPIO21 - mosi_pin: GPIO22 - miso_pin: GPIO23 + - id: spi_id_1 + clk_pin: + allow_other_uses: true + number: GPIO21 + mosi_pin: + allow_other_uses: true + number: GPIO22 + miso_pin: + allow_other_uses: true + number: GPIO23 + interface: hardware + - id: spi_id_2 + clk_pin: + number: GPIO32 + mosi_pin: + number: GPIO33 + interface: hardware uart: - id: uart115200 - tx_pin: GPIO22 - rx_pin: GPIO23 + tx_pin: + allow_other_uses: true + number: GPIO22 + rx_pin: + allow_other_uses: true + number: GPIO23 baud_rate: 115200 - id: uart9600 - tx_pin: GPIO22 - rx_pin: GPIO23 + tx_pin: + allow_other_uses: true + number: GPIO25 + rx_pin: + allow_other_uses: true + number: GPIO26 baud_rate: 9600 + - id: uart_a02yyuw + tx_pin: + allow_other_uses: true + number: GPIO22 + rx_pin: + allow_other_uses: true + number: GPIO23 + baud_rate: 9600 + - id: uart_he60r + tx_pin: + number: GPIO18 + allow_other_uses: true + rx_pin: + number: GPIO36 + allow_other_uses: true + baud_rate: 1200 + parity: EVEN ota: safe_mode: true @@ -50,6 +109,8 @@ ota: logger: level: DEBUG +debug: + web_server: ota: false auth: @@ -65,8 +126,9 @@ tuya: time_id: sntp_time uart_id: uart115200 status_pin: - number: 14 + number: GPIO5 inverted: true + allow_other_uses: true select: - platform: tuya @@ -81,12 +143,21 @@ pipsolar: id: inverter0 uart_id: uart115200 +pylontech: + - id: pylontech0 + uart_id: uart115200 + - id: pylontech1 + uart_id: uart115200 + sx1509: - id: sx1509_hub address: 0x3E mcp3204: - cs_pin: GPIO23 + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO23 dac7678: address: 0x4A @@ -94,6 +165,30 @@ dac7678: internal_reference: true sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + voltage: + id: pyl01_voltage + current: + id: pyl01_current + coulomb: + id: pyl01_soc + mos_temperature: + id: pyl01_mos_temperature + - platform: pylontech + pylontech_id: pylontech1 + battery: 1 + voltage: + id: pyl13_voltage + temperature_low: + id: pyl13_temperature_low + temperature_high: + id: pyl13_temperature_high + voltage_low: + id: pyl13_voltage_low + voltage_high: + id: pyl13_voltage_high - platform: homeassistant entity_id: sensor.hello_world id: ha_hello_world @@ -269,6 +364,12 @@ sensor: id: a01nyub_sensor name: "a01nyub Distance" uart_id: uart9600 + state_topic: "esphome/sensor/a01nyub_sensor/state" + - platform: a02yyuw + id: a02yyuw_sensor + name: "a02yyuw Distance" + uart_id: uart_a02yyuw + state_topic: "esphome/sensor/a02yyuw_sensor/state" # # platform sensor.apds9960 requires component apds9960 @@ -385,12 +486,16 @@ binary_sensor: y_max: 100 on_press: - logger.log: Touched + - platform: gt911 + id: touch_key_911 + index: 0 - platform: gpio name: MaxIn Pin 4 pin: max6956: max6956_1 number: 4 + mode: input: true pullup: true @@ -405,6 +510,15 @@ binary_sensor: input: true inverted: false + - platform: gpio + name: XL9535 Pin 17 + pin: + xl9535: xl9535_hub + number: 17 + mode: + input: true + inverted: false + climate: - platform: tuya id: tuya_climate @@ -441,7 +555,9 @@ light: id: led_matrix_32x8 name: led_matrix_32x8 chipset: WS2812B - pin: GPIO15 + pin: + allow_other_uses: true + number: GPIO15 num_leds: 256 rgb_order: GRB default_transition_length: 0s @@ -465,6 +581,12 @@ cover: - platform: copy source_id: tuya_cover name: Tuya Cover copy + - platform: he60r + uart_id: uart_he60r + id: garage_door + name: Garage Door + open_duration: 14s + close_duration: 14s display: - platform: addressable_light @@ -485,60 +607,64 @@ display: it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + auto touch = id(ft63_touchscreen)->get_touch(); + if (touch) { ESP_LOGD("touch", "%d/%d", touch.value().x, touch.value().y); } rotation: 0° update_interval: 16ms - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.13in-ttgo-b1 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90in - full_update_every: 30 - reset_duration: 200ms - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90inv2 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 1.54in-m5coreink-m09 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: inkplate6 id: inkplate_display greyscale: false partial_updating: false update_interval: 60s - - ckv_pin: GPIO1 - sph_pin: GPIO1 - gmod_pin: GPIO1 - gpio0_enable_pin: GPIO1 - oe_pin: GPIO1 - spv_pin: GPIO1 - powerup_pin: GPIO1 - wakeup_pin: GPIO1 - vcom_pin: GPIO1 + display_data_1_pin: + number: GPIO5 + allow_other_uses: true + display_data_2_pin: + number: GPIO18 + allow_other_uses: true + display_data_3_pin: + number: GPIO19 + allow_other_uses: true + display_data_5_pin: + number: GPIO25 + allow_other_uses: true + display_data_4_pin: + number: GPIO23 + allow_other_uses: true + display_data_6_pin: + number: GPIO26 + allow_other_uses: true + display_data_7_pin: + number: GPIO27 + allow_other_uses: true + ckv_pin: + number: GPIO1 + allow_other_uses: true + sph_pin: + number: GPIO1 + allow_other_uses: true + gmod_pin: + number: GPIO1 + allow_other_uses: true + gpio0_enable_pin: + number: GPIO1 + allow_other_uses: true + oe_pin: + number: GPIO1 + allow_other_uses: true + spv_pin: + number: GPIO1 + allow_other_uses: true + powerup_pin: + number: GPIO1 + allow_other_uses: true + wakeup_pin: + number: GPIO1 + allow_other_uses: true + vcom_pin: + number: GPIO1 + allow_other_uses: true number: - platform: tuya @@ -552,6 +678,17 @@ number: name: Tuya Number Copy text_sensor: + - platform: pylontech + pylontech_id: pylontech0 + battery: 1 + base_state: + id: pyl0_base_state + voltage_state: + id: pyl0_voltage_state + current_state: + id: pyl0_current_state + temperature_state: + id: pyl0_temperature_state - platform: pipsolar pipsolar_id: inverter0 device_mode: @@ -615,20 +752,55 @@ output: id: dac7678_1_ch7 esp32_camera: name: ESP-32 Camera - data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] - vsync_pin: GPIO22 - href_pin: GPIO26 - pixel_clock_pin: GPIO21 + data_pins: + - number: GPIO17 + allow_other_uses: true + - number: GPIO35 + allow_other_uses: true + - number: GPIO34 + - number: GPIO5 + allow_other_uses: true + - number: GPIO39 + allow_other_uses: true + - number: GPIO18 + allow_other_uses: true + - number: GPIO36 + allow_other_uses: true + - number: GPIO19 + allow_other_uses: true + vsync_pin: + allow_other_uses: true + number: GPIO22 + href_pin: + allow_other_uses: true + number: GPIO26 + pixel_clock_pin: + allow_other_uses: true + number: GPIO21 external_clock: - pin: GPIO27 + pin: + allow_other_uses: true + number: GPIO27 frequency: 20MHz i2c_pins: - sda: GPIO25 - scl: GPIO23 - reset_pin: GPIO15 - power_down_pin: GPIO1 + sda: + allow_other_uses: true + number: GPIO25 + scl: + allow_other_uses: true + number: GPIO23 + reset_pin: + allow_other_uses: true + number: GPIO15 + power_down_pin: + allow_other_uses: true + number: GPIO1 resolution: 640x480 jpeg_quality: 10 + on_image: + then: + - lambda: |- + ESP_LOGD("main", "image len=%d, data=%c", image.length, image.data[0]); esp32_camera_web_server: - port: 8080 @@ -657,8 +829,12 @@ button: touchscreen: - platform: ektf2232 - interrupt_pin: GPIO36 - rts_pin: GPIO5 + interrupt_pin: + allow_other_uses: true + number: GPIO36 + rts_pin: + allow_other_uses: true + number: GPIO5 display: inkplate_display on_touch: - logger.log: @@ -667,17 +843,20 @@ touchscreen: - platform: xpt2046 id: xpt_touchscreen - cs_pin: 17 - interrupt_pin: 16 + spi_id: spi_id_2 + cs_pin: + allow_other_uses: true + number: GPIO17 + interrupt_pin: + number: GPIO16 display: inkplate_display update_interval: 50ms - report_interval: 1s threshold: 400 - calibration_x_min: 3860 - calibration_x_max: 280 - calibration_y_min: 340 - calibration_y_max: 3860 - swap_x_y: false + calibration: + x_min: 3860 + x_max: 280 + y_min: 340 + y_max: 3860 on_touch: - logger.log: format: Touch at (%d, %d) @@ -685,7 +864,27 @@ touchscreen: - platform: lilygo_t5_47 id: lilygo_touchscreen - interrupt_pin: GPIO36 + interrupt_pin: + allow_other_uses: true + number: GPIO36 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] + - platform: gt911 + interrupt_pin: + number: GPIO3 + display: inkplate_display + + - platform: ft63x6 + id: ft63_touchscreen + interrupt_pin: + allow_other_uses: true + number: GPIO39 + reset_pin: + allow_other_uses: true + number: GPIO5 display: inkplate_display on_touch: - logger.log: @@ -693,16 +892,25 @@ touchscreen: args: [touch.x, touch.y] i2s_audio: - i2s_lrclk_pin: GPIO26 - i2s_bclk_pin: GPIO27 - i2s_mclk_pin: GPIO25 + i2s_lrclk_pin: + allow_other_uses: true + number: GPIO26 + i2s_bclk_pin: + allow_other_uses: true + number: GPIO27 + i2s_mclk_pin: + allow_other_uses: true + number: GPIO25 media_player: - platform: i2s_audio name: None dac_type: external - i2s_dout_pin: GPIO25 - mute_pin: GPIO14 + i2s_dout_pin: + allow_other_uses: true + number: GPIO25 + mute_pin: + number: GPIO14 on_state: - media_player.play: - media_player.play_media: http://localhost/media.mp3 @@ -731,12 +939,16 @@ prometheus: microphone: - platform: i2s_audio id: mic_id_adc - adc_pin: GPIO35 + adc_pin: + allow_other_uses: true + number: GPIO35 adc_type: internal - platform: i2s_audio id: mic_id_external - i2s_din_pin: GPIO23 + i2s_din_pin: + allow_other_uses: true + number: GPIO23 adc_type: external pdm: false @@ -744,12 +956,16 @@ speaker: - platform: i2s_audio id: speaker_id dac_type: external - i2s_dout_pin: GPIO25 + i2s_dout_pin: + allow_other_uses: true + number: GPIO25 mode: mono - voice_assistant: microphone: mic_id_external + speaker: speaker_id + on_listening: + - logger.log: "Voice assistant microphone listening" on_start: - logger.log: "Voice assistant started" on_stt_end: diff --git a/tests/test5.yaml b/tests/test5.yaml index a2530d799aff..afd335909863 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -22,28 +22,47 @@ wifi: gateway: 192.168.1.1 subnet: 255.255.255.0 +network: + enable_ipv6: true + api: ota: logger: +debug: + +psram: + uart: - id: uart_1 tx_pin: 1 rx_pin: 3 baud_rate: 9600 - id: uart_2 - tx_pin: 17 - rx_pin: 16 + tx_pin: + allow_other_uses: true + number: 17 + inverted: true + rx_pin: + allow_other_uses: true + number: 16 baud_rate: 19200 i2c: + sda: + allow_other_uses: true + number: 21 + scl: + number: 22 frequency: 100khz modbus: uart_id: uart_1 - flow_control_pin: 5 + flow_control_pin: + allow_other_uses: true + number: 5 id: mod_bus1 modbus_controller: @@ -57,6 +76,7 @@ mqtt: discovery: true discovery_prefix: homeassistant idf_send_async: false + log_topic: on_message: topic: testing/sensor/testing_sensor/state qos: 0 @@ -80,7 +100,7 @@ binary_sensor: id: modbus_binsensortest register_type: read address: 0x3200 - bitmask: 0x80 # (bit 8) + bitmask: 0x80 # (bit 8) lambda: "return x;" - platform: tm1638 @@ -204,9 +224,15 @@ binary_sensor: lambda: return x[0] & 1; tlc5947: - data_pin: GPIO12 - clock_pin: GPIO14 - lat_pin: GPIO15 + data_pin: + number: GPIO12 + allow_other_uses: true + clock_pin: + allow_other_uses: true + number: GPIO14 + lat_pin: + allow_other_uses: true + number: GPIO15 gp8403: - id: gp8403_5v @@ -388,8 +414,15 @@ select: "Three": 3 sensor: + - platform: adc + id: adc_sensor_p32 + name: ADC pin 32 + pin: 32 + attenuation: 11db + update_interval: 1s - platform: internal_temperature name: Internal Temperature + state_topic: - platform: selec_meter total_active_energy: name: SelecEM2M Total Active Energy @@ -441,14 +474,28 @@ sensor: co2: name: CO2 Sensor - - platform: bmp3xx + - platform: ms8607 + temperature: + name: Temperature + humidity: + name: Humidity + pressure: + name: Pressure + - platform: ms8607 + id: ms8607_more_config temperature: - name: BMP Temperature - oversampling: 16x + name: Indoor Temperature + accuracy_decimals: 1 pressure: - name: BMP Pressure + name: Indoor Pressure + internal: true + humidity: + name: Indoor Humidity + address: 0x41 + i2c_id: + i2c_id: address: 0x77 - iir_filter: 2X + update_interval: 10min - platform: sen5x id: sen54 @@ -525,6 +572,16 @@ sensor: time: name: System Time + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + - platform: vbus model: custom command: 0x100 @@ -549,6 +606,13 @@ script: then: - logger.log: looping! + - id: zero_repeat_test + then: + - repeat: + count: !lambda "return 0;" + then: + - logger.log: shouldn't see mee! + switch: - platform: modbus_controller modbus_controller_id: modbus_controller_test @@ -580,9 +644,11 @@ switch: display: - platform: tm1638 id: primarydisplay - stb_pin: 5 #TM1638 STB - clk_pin: 18 #TM1638 CLK - dio_pin: 23 #TM1638 DIO + stb_pin: + allow_other_uses: true + number: 5 # TM1638 STB + clk_pin: 18 # TM1638 CLK + dio_pin: 23 # TM1638 DIO update_interval: 5s intensity: 5 lambda: |- @@ -625,8 +691,12 @@ text_sensor: sn74hc165: id: sn74hc165_hub - data_pin: GPIO12 - clock_pin: GPIO14 + data_pin: + allow_other_uses: true + number: GPIO12 + clock_pin: + allow_other_uses: true + number: GPIO14 load_pin: GPIO27 clock_inhibit_pin: GPIO26 sr_count: 4 @@ -634,12 +704,19 @@ sn74hc165: matrix_keypad: id: keypad rows: - - pin: 21 + - pin: + allow_other_uses: true + number: 21 - pin: 19 columns: - - pin: 17 - - pin: 16 + - pin: + allow_other_uses: true + number: 17 + - pin: + allow_other_uses: true + number: 16 keys: "1234" + has_pulldowns: true key_collector: - id: reader @@ -657,7 +734,9 @@ light: chipset: ws2812 - platform: esp32_rmt_led_strip id: led_strip2 - pin: 15 + pin: + allow_other_uses: true + number: 15 num_leds: 60 rmt_channel: 2 rgb_order: RGB diff --git a/tests/test6.yaml b/tests/test6.yaml index 6224563a77e7..2c5aa30aadba 100644 --- a/tests/test6.yaml +++ b/tests/test6.yaml @@ -16,12 +16,17 @@ wifi: - ssid: "MySSID" password: "password1" +network: + enable_ipv6: true + api: ota: logger: +debug: + binary_sensor: - platform: gpio pin: GPIO5 @@ -37,7 +42,14 @@ switch: output: pin_4 id: pin_4_switch -#light: +spi: # Pins are for SPI1 on the RP2040 Pico-W + miso_pin: 8 + clk_pin: 10 + mosi_pin: 11 + id: spi_0 + interface: hardware + +# light: # - platform: rp2040_pio_led_strip # id: led_strip # pin: GPIO13 @@ -56,7 +68,9 @@ switch: # bit1_high: .69us # bit1_low: .4us - sensor: - platform: internal_temperature name: Internal Temperature + - platform: adc + pin: VCC + name: VSYS diff --git a/tests/test7.yaml b/tests/test7.yaml index 8d48c9a601dc..b22fbfbcb40d 100644 --- a/tests/test7.yaml +++ b/tests/test7.yaml @@ -3,6 +3,9 @@ wifi: ssid: 'ssid' +network: + enable_ipv6: true + esp32: board: lolin_c3_mini framework: @@ -28,6 +31,8 @@ esphome: logger: +debug: + http_request: useragent: esphome/tagreader timeout: 10s diff --git a/tests/test8.1.yaml b/tests/test8.1.yaml new file mode 100644 index 000000000000..ab3d0d44aadc --- /dev/null +++ b/tests/test8.1.yaml @@ -0,0 +1,78 @@ +# Tests for ESP32-S3 boards - IDf +--- +wifi: + ssid: "ssid" + +network: + enable_ipv6: true + +esp32: + board: esp32s3box + variant: ESP32S3 + framework: + type: esp-idf + +esphome: + name: esp32-s3-test + +logger: + +debug: + +psram: + +spi: + - id: spi_id_1 + type: single + clk_pin: + number: GPIO7 + allow_other_uses: false + mosi_pin: GPIO6 + interface: hardware +spi_device: + id: spidev + data_rate: 2MHz + spi_id: spi_id_1 + mode: 3 + bit_order: lsb_first + +display: + - platform: ili9xxx + id: displ8 + model: ili9342 + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: + number: GPIO48 + allow_other_uses: true + +i2c: + scl: GPIO18 + sda: GPIO8 + +touchscreen: + - platform: tt21100 + display: displ8 + interrupt_pin: + number: GPIO3 + ignore_strapping_warning: true + allow_other_uses: false + reset_pin: + number: GPIO48 + allow_other_uses: true + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 + +sensor: + - platform: debug + free: + name: "Heap Free" + block: + name: "Max Block Free" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" diff --git a/tests/test8.2.yaml b/tests/test8.2.yaml new file mode 100644 index 000000000000..ae892559e588 --- /dev/null +++ b/tests/test8.2.yaml @@ -0,0 +1,75 @@ +# Tests for ESP32-C3 boards - IDf +--- +wifi: + ssid: "ssid" + +network: + enable_ipv6: true + +esp32: + board: lolin_c3_mini + variant: ESP32C3 + framework: + type: esp-idf + +esphome: + name: esp32-c3-test + +logger: + +debug: + +psram: + +spi: + - id: spi_id_1 + clk_pin: + number: GPIO7 + allow_other_uses: false + mosi_pin: GPIO6 + interface: any + +spi_device: + id: spidev + data_rate: 2MHz + spi_id: spi_id_1 + mode: 3 + bit_order: lsb_first + +display: + - platform: ili9xxx + id: displ8 + model: ili9342 + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: + number: GPIO21 + +i2c: + scl: GPIO18 + sda: GPIO8 + +touchscreen: + - platform: tt21100 + display: displ8 + interrupt_pin: + number: GPIO3 + allow_other_uses: false + reset_pin: + number: GPIO20 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 + +sensor: + - platform: debug + free: + name: "Heap Free" + block: + name: "Max Block Free" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" diff --git a/tests/test8.yaml b/tests/test8.yaml index 8d031b033fc8..5a8ae77468eb 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -3,6 +3,9 @@ wifi: ssid: "ssid" +network: + enable_ipv6: true + esp32: board: esp32s3box variant: ESP32S3 @@ -14,6 +17,10 @@ esphome: logger: +debug: + +psram: + light: - platform: neopixelbus type: GRB @@ -25,17 +32,42 @@ light: name: neopixel-enable internal: false restore_mode: ALWAYS_OFF + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz spi: + id: spi_id_1 clk_pin: GPIO7 mosi_pin: GPIO6 + interface: any + +spi_device: + id: spidev + data_rate: 2MHz + spi_id: spi_id_1 + mode: 3 + bit_order: lsb_first + +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 display: - platform: ili9xxx + id: displ8 model: ili9342 cs_pin: GPIO5 dc_pin: GPIO4 - reset_pin: GPIO48 + reset_pin: + number: GPIO48 + allow_other_uses: true + lambda: |- + it.printf(10, 100, id(roboto), Color(0x123456), COLOR_OFF, display::TextAlign::BASELINE, "%f", id(heap_free).state); i2c: scl: GPIO18 @@ -43,10 +75,38 @@ i2c: touchscreen: - platform: tt21100 - interrupt_pin: GPIO3 - reset_pin: GPIO48 + display: displ8 + interrupt_pin: + number: GPIO3 + ignore_strapping_warning: true + allow_other_uses: false + reset_pin: + number: GPIO48 + allow_other_uses: true binary_sensor: - platform: tt21100 name: Home Button index: 1 + +sensor: + - platform: debug + free: + id: heap_free + name: "Heap Free" + block: + name: "Max Block Free" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + +# Purposely test that `animation:` does auto-load `image:` +# Keep the `image:` undefined. +# image: + +animation: + - id: rgb565_animation + file: pnglogo.png + type: RGB565 + use_transparency: false diff --git a/tests/test9.1.yaml b/tests/test9.1.yaml new file mode 100644 index 000000000000..f7455b766880 --- /dev/null +++ b/tests/test9.1.yaml @@ -0,0 +1,28 @@ +# Tests for rtl87xx boards using LibreTiny +--- +wifi: + ssid: "ssid" + +rtl87xx: + board: generic-rtl8710bn-2mb-788k + +esphome: + name: rtl87xx-test + +logger: + +ota: + +captive_portal: + +binary_sensor: + - platform: gpio + name: Home Button + pin: GPIO11 + +sensor: + - platform: adc + id: adc_sensor + name: ADC + pin: PA19 + update_interval: 1s diff --git a/tests/test9.yaml b/tests/test9.yaml new file mode 100644 index 000000000000..d660b4f24af3 --- /dev/null +++ b/tests/test9.yaml @@ -0,0 +1,34 @@ +# Tests for bk7xx boards using LibreTiny +--- +wifi: + ssid: "ssid" + +bk72xx: + board: cb2s + +esphome: + name: bk72xx-test + +logger: + +ota: + +captive_portal: + +binary_sensor: + - platform: gpio + name: Home Button + pin: GPIO24 + +sensor: + - platform: adc + id: adc_sensor + name: ADC + pin: GPIO23 + update_interval: 1s + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant diff --git a/tests/test_build_components/build_components_base.bk72xx.yaml b/tests/test_build_components/build_components_base.bk72xx.yaml new file mode 100644 index 000000000000..9fd9431826aa --- /dev/null +++ b/tests/test_build_components/build_components_base.bk72xx.yaml @@ -0,0 +1,15 @@ +esphome: + name: componenttestespbk72xx + friendly_name: $component_name + +bk72xx: + board: cb3s + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-ard.yaml b/tests/test_build_components/build_components_base.esp32-ard.yaml new file mode 100644 index 000000000000..31b7067acc18 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-ard.yaml @@ -0,0 +1,17 @@ +esphome: + name: componenttestesp32ard + friendly_name: $component_name + +esp32: + board: nodemcu-32s + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-c3-ard.yaml b/tests/test_build_components/build_components_base.esp32-c3-ard.yaml new file mode 100644 index 000000000000..8aad447693cf --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-c3-ard.yaml @@ -0,0 +1,17 @@ +esphome: + name: componenttestesp32c3ard + friendly_name: $component_name + +esp32: + board: lolin_c3_mini + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-c3-idf.yaml b/tests/test_build_components/build_components_base.esp32-c3-idf.yaml new file mode 100644 index 000000000000..18584497f47c --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-c3-idf.yaml @@ -0,0 +1,17 @@ +esphome: + name: componenttestesp32c3idf + friendly_name: $component_name + +esp32: + board: lolin_c3_mini + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-idf.yaml b/tests/test_build_components/build_components_base.esp32-idf.yaml new file mode 100644 index 000000000000..a62a995e6867 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-idf.yaml @@ -0,0 +1,17 @@ +esphome: + name: componenttestesp32idf + friendly_name: $component_name + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-ard.yaml b/tests/test_build_components/build_components_base.esp32-s2-ard.yaml new file mode 100644 index 000000000000..b8f263912767 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s2-ard.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestesp32s2ard + friendly_name: $component_name + +esp32: + board: esp32-s2-saola-1 + variant: ESP32S2 + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-idf.yaml b/tests/test_build_components/build_components_base.esp32-s2-idf.yaml new file mode 100644 index 000000000000..62f0f4f7bcaf --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s2-idf.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestesp32s2ard + friendly_name: $component_name + +esp32: + board: esp32-s2-saola-1 + variant: ESP32S2 + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-ard.yaml b/tests/test_build_components/build_components_base.esp32-s3-ard.yaml new file mode 100644 index 000000000000..25cad038b6b3 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s3-ard.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestesp32s3ard + friendly_name: $component_name + +esp32: + board: esp32s3box + variant: ESP32S3 + framework: + type: arduino + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-idf.yaml b/tests/test_build_components/build_components_base.esp32-s3-idf.yaml new file mode 100644 index 000000000000..b1d08fcdf84a --- /dev/null +++ b/tests/test_build_components/build_components_base.esp32-s3-idf.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestesp32s3ard + friendly_name: $component_name + +esp32: + board: esp32s3box + variant: ESP32S3 + framework: + type: esp-idf + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp8266.yaml b/tests/test_build_components/build_components_base.esp8266.yaml new file mode 100644 index 000000000000..ecf9acd2ba70 --- /dev/null +++ b/tests/test_build_components/build_components_base.esp8266.yaml @@ -0,0 +1,15 @@ +esphome: + name: componenttestesp8266 + friendly_name: $component_name + +esp8266: + board: d1_mini + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.host.yaml b/tests/test_build_components/build_components_base.host.yaml new file mode 100644 index 000000000000..5492cfddd28f --- /dev/null +++ b/tests/test_build_components/build_components_base.host.yaml @@ -0,0 +1,15 @@ +esphome: + name: componenttesthost + friendly_name: $component_name + +host: + mac_address: "62:23:45:AF:B3:DD" + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.rp2040.yaml b/tests/test_build_components/build_components_base.rp2040.yaml new file mode 100644 index 000000000000..335642374bb8 --- /dev/null +++ b/tests/test_build_components/build_components_base.rp2040.yaml @@ -0,0 +1,18 @@ +esphome: + name: componenttestrp2040 + friendly_name: $component_name + +rp2040: + board: rpipicow + framework: + # Waiting for https://github.com/platformio/platform-raspberrypi/pull/36 + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + +logger: + level: VERY_VERBOSE + +packages: + component_under_test: !include + file: $component_test_file + vars: + component_test_file: $component_test_file diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 41d0f3dadb82..d61c4a442ac0 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -8,6 +8,7 @@ not be part of a unit test suite. """ + import sys import pytest diff --git a/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml new file mode 100644 index 000000000000..aaca55b80756 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml @@ -0,0 +1,18 @@ +--- +substitutions: + name: original + +wifi: !include + file: includes/broken_included.yaml.txt + vars: + name: my_custom_ssid + +esphome: + # should be substituted as 'original', + # not overwritten by vars in the !include above + name: ${name} + name_add_mac_suffix: true + platform: esp8266 + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} + + libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} diff --git a/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt new file mode 100644 index 000000000000..6e53395c8655 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt @@ -0,0 +1,5 @@ +--- +# yamllint disable-line + ssid: ${name} +# yamllint disable-line + fdf: error diff --git a/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml new file mode 100644 index 000000000000..d065901ed97b --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/missing_comp.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + board: esp32dev + +wifi: + ap: ~ + +image: + - id: its_a_bug + file: "mdi:bug" diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 9a15bf0b9c83..2860486efe14 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -1,7 +1,7 @@ import pytest from hypothesis import given -from hypothesis.provisional import ip_addresses +from hypothesis.strategies import ip_addresses from strategies import mac_addr_strings from esphome import core, const @@ -116,14 +116,16 @@ def test_init(self, kwargs, expected): assert actual == expected - def test_init__microseconds_with_fraction(self): - with pytest.raises(ValueError, match="Maximum precision is microseconds"): - core.TimePeriod(microseconds=1.1) + def test_init__nanoseconds_with_fraction(self): + with pytest.raises(ValueError, match="Maximum precision is nanoseconds"): + core.TimePeriod(nanoseconds=1.1) @pytest.mark.parametrize( "kwargs, expected", ( ({}, "0s"), + ({"nanoseconds": 1}, "1ns"), + ({"nanoseconds": 1.0001}, "1ns"), ({"microseconds": 1}, "1us"), ({"microseconds": 1.0001}, "1us"), ({"milliseconds": 2}, "2ms"), diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 331c500c04f9..6f4b5a40bccb 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -1,4 +1,4 @@ -from typing import Iterator +from collections.abc import Iterator import math diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index ad234250ce0d..497b3966fb83 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -1,5 +1,5 @@ import pytest -from mock import Mock +from unittest.mock import Mock from esphome import cpp_helpers as ch from esphome import const diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 67fabd7af869..26ebdcf6af0a 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -1,7 +1,7 @@ import pytest from hypothesis import given -from hypothesis.provisional import ip_addresses +from hypothesis.strategies import ip_addresses from esphome import helpers @@ -258,9 +258,10 @@ def test_snake_case(text, expected): "text, expected", ( ("foo_bar", "foo_bar"), - ('!"§$%&/()=?foo_bar', "foo_bar"), - ('foo_!"§$%&/()=?bar', "foo_bar"), - ('foo_bar!"§$%&/()=?', "foo_bar"), + ('!"§$%&/()=?foo_bar', "___________foo_bar"), + ('foo_!"§$%&/()=?bar', "foo____________bar"), + ('foo_bar!"§$%&/()=?', "foo_bar___________"), + ('foo-bar!"§$%&/()=?', "foo-bar___________"), ), ) def test_sanitize(text, expected): diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 669cf06ca49f..9260629ec3da 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -1,7 +1,10 @@ """Tests for the wizard.py file.""" +import os + import esphome.wizard as wz import pytest +from esphome.core import CORE from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS @@ -110,6 +113,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): # Given del default_config["platform"] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -130,6 +134,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266( default_config["board"] = [*ESP8266_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -150,6 +155,7 @@ def test_wizard_write_defaults_platform_from_board_esp32( default_config["board"] = [*ESP32_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -170,6 +176,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx( default_config["board"] = [*BK72XX_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -190,6 +197,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx( default_config["board"] = [*RTL87XX_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 8ee991f5b330..91787262473a 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -1,5 +1,6 @@ from esphome import yaml_util from esphome.components import substitutions +from esphome.core import EsphomeError def test_include_with_vars(fixture_path): @@ -11,3 +12,33 @@ def test_include_with_vars(fixture_path): assert actual["esphome"]["libraries"][0] == "Wire" assert actual["esphome"]["board"] == "nodemcu" assert actual["wifi"]["ssid"] == "my_custom_ssid" + + +def test_loading_a_broken_yaml_file(fixture_path): + """Ensure we fallback to pure python to give good errors.""" + yaml_file = fixture_path / "yaml_util" / "broken_includetest.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "broken_included.yaml" in str(err) + + +def test_loading_a_yaml_file_with_a_missing_component(fixture_path): + """Ensure we show the filename for a yaml file with a missing component.""" + yaml_file = fixture_path / "yaml_util" / "missing_comp.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing_comp.yaml" in str(err) + + +def test_loading_a_missing_file(fixture_path): + """We throw EsphomeError when loading a missing file.""" + yaml_file = fixture_path / "yaml_util" / "missing.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "missing.yaml" in str(err)