From 8a055675af8e428bf7b8338cd0b22d01c252c6dc Mon Sep 17 00:00:00 2001 From: lkomurcu <44089359+lkomurcu@users.noreply.github.com> Date: Sun, 19 Jun 2022 21:39:40 +0200 Subject: [PATCH 001/838] Move gas mbus config option being a define to being a build flag since its used in external libraries. (#3575) --- esphome/components/dsmr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 7a7681082e..624a0f35a1 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -79,7 +79,7 @@ async def to_code(config): cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)) cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds)) - cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) + cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID])) # DSMR Parser cg.add_library("glmnet/Dsmr", "0.5") From d0c646c72162ca0421df84bd941d54735282db52 Mon Sep 17 00:00:00 2001 From: ShellAddicted Date: Mon, 20 Jun 2022 01:17:58 +0200 Subject: [PATCH 002/838] Fix: Make MQTT over TLS actually work (#3580) --- esphome/components/mqtt/mqtt_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 157504fb41..aa0cf56c51 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -187,7 +187,7 @@ void MQTTClientComponent::start_connect_() { this->mqtt_backend_.set_credentials(username, password); - this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port); + this->mqtt_backend_.set_server(this->credentials_.address.c_str(), this->credentials_.port); if (!this->last_will_.topic.empty()) { this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, this->last_will_.payload.c_str()); From ca13c4c1a657cf614379125e15a679213923abc7 Mon Sep 17 00:00:00 2001 From: kahrendt Date: Sun, 19 Jun 2022 19:27:00 -0400 Subject: [PATCH 003/838] Fix wrong type for voc_state*_ in sgp4x component (#3581) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sgp4x/sensor.py | 4 +++- esphome/components/sgp4x/sgp4x.cpp | 4 ++-- esphome/components/sgp4x/sgp4x.h | 6 +++--- platformio.ini | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index 4855d7f066..1f6c5006a5 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -140,5 +140,7 @@ async def to_code(config): ) ) cg.add_library( - None, None, "https://github.com/Sensirion/arduino-gas-index-algorithm.git" + None, + None, + "https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1", ) diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index a6f57e0342..257c30075f 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -170,8 +170,8 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { // much if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { voc_algorithm_.get_states(this->voc_state0_, this->voc_state1_); - if ((uint32_t) abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF || - (uint32_t) abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) { + if (std::abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF || + std::abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) { this->seconds_since_last_store_ = 0; this->voc_baselines_storage_.state0 = this->voc_state0_; this->voc_baselines_storage_.state1 = this->voc_state1_; diff --git a/esphome/components/sgp4x/sgp4x.h b/esphome/components/sgp4x/sgp4x.h index 3060972fc3..3a8d8200a7 100644 --- a/esphome/components/sgp4x/sgp4x.h +++ b/esphome/components/sgp4x/sgp4x.h @@ -49,7 +49,7 @@ static const uint16_t SPG41_SELFTEST_TIME = 320; // 320 ms for self test static const uint16_t SGP40_MEASURE_TIME = 30; static const uint16_t SGP41_MEASURE_TIME = 55; // Store anyway if the baseline difference exceeds the max storage diff value -const uint32_t MAXIMUM_STORAGE_DIFF = 50; +const float MAXIMUM_STORAGE_DIFF = 50.0f; class SGP4xComponent; @@ -120,8 +120,8 @@ class SGP4xComponent : public PollingComponent, public sensor::Sensor, public se sensor::Sensor *voc_sensor_{nullptr}; VOCGasIndexAlgorithm voc_algorithm_; optional voc_tuning_params_; - int32_t voc_state0_; - int32_t voc_state1_; + float voc_state0_; + float voc_state1_; int32_t voc_index_ = 0; sensor::Sensor *nox_sensor_{nullptr}; diff --git a/platformio.ini b/platformio.ini index fb33087a8a..733e942a84 100644 --- a/platformio.ini +++ b/platformio.ini @@ -40,7 +40,7 @@ lib_deps = wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 ; This is using the repository until a new release is published to PlatformIO - https://github.com/Sensirion/arduino-gas-index-algorithm.git ; Sensirion Gas Index Algorithm Arduino Library + https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = From 8f67acadd817807f07a47fd883d307ffa7c15002 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 20 Jun 2022 04:17:20 +0400 Subject: [PATCH 004/838] Media Player: added play_media action (#3579) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/media_player/__init__.py | 31 ++++++++++++++++++++ esphome/components/media_player/automation.h | 19 ++++++++++-- tests/test4.yaml | 6 ++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 3456417370..80f5fc558a 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -20,6 +20,9 @@ MediaPlayer = media_player_ns.class_("MediaPlayer") PlayAction = media_player_ns.class_( "PlayAction", automation.Action, cg.Parented.template(MediaPlayer) ) +PlayMediaAction = media_player_ns.class_( + "PlayMediaAction", automation.Action, cg.Parented.template(MediaPlayer) +) ToggleAction = media_player_ns.class_( "ToggleAction", automation.Action, cg.Parented.template(MediaPlayer) ) @@ -44,11 +47,14 @@ CONF_VOLUME = "volume" CONF_ON_IDLE = "on_idle" CONF_ON_PLAY = "on_play" CONF_ON_PAUSE = "on_pause" +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()) +IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) +IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) async def setup_media_player_core_(var, config): @@ -103,6 +109,25 @@ MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( MEDIA_PLAYER_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(MediaPlayer)}) +@automation.register_action( + "media_player.play_media", + PlayMediaAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(MediaPlayer), + cv.Required(CONF_MEDIA_URL): cv.templatable(cv.url), + }, + key=CONF_MEDIA_URL, + ), +) +async def media_player_play_media_action(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + media_url = await cg.templatable(config[CONF_MEDIA_URL], args, cg.std_string) + cg.add(var.set_media_url(media_url)) + return var + + @automation.register_action("media_player.play", PlayAction, MEDIA_PLAYER_ACTION_SCHEMA) @automation.register_action( "media_player.toggle", ToggleAction, MEDIA_PLAYER_ACTION_SCHEMA @@ -117,6 +142,12 @@ MEDIA_PLAYER_ACTION_SCHEMA = maybe_simple_id({cv.GenerateID(): cv.use_id(MediaPl @automation.register_action( "media_player.volume_down", VolumeDownAction, MEDIA_PLAYER_ACTION_SCHEMA ) +@automation.register_condition( + "media_player.is_idle", IsIdleCondition, MEDIA_PLAYER_ACTION_SCHEMA +) +@automation.register_condition( + "media_player.is_playing", IsPlayingCondition, MEDIA_PLAYER_ACTION_SCHEMA +) async def media_player_action(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index c6deba7dc6..261e93775c 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -17,7 +17,7 @@ namespace media_player { #define MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(TRIGGER_CLASS, TRIGGER_STATE) \ class TRIGGER_CLASS : public Trigger<> { \ public: \ - TRIGGER_CLASS(MediaPlayer *player) { \ + explicit TRIGGER_CLASS(MediaPlayer *player) { \ player->add_on_state_callback([this, player]() { \ if (player->state == MediaPlayerState::MEDIA_PLAYER_STATE_##TRIGGER_STATE) \ this->trigger(); \ @@ -32,6 +32,11 @@ MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(ToggleAction, TOGGLE) MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeUpAction, VOLUME_UP) MEDIA_PLAYER_SIMPLE_COMMAND_ACTION(VolumeDownAction, VOLUME_DOWN) +template class PlayMediaAction : public Action, public Parented { + TEMPLATABLE_VALUE(std::string, media_url) + void play(Ts... x) override { this->parent_->make_call().set_media_url(this->media_url_.value(x...)).perform(); } +}; + template class VolumeSetAction : public Action, public Parented { TEMPLATABLE_VALUE(float, volume) void play(Ts... x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); } @@ -39,7 +44,7 @@ template class VolumeSetAction : public Action, public Pa class StateTrigger : public Trigger<> { public: - StateTrigger(MediaPlayer *player) { + explicit StateTrigger(MediaPlayer *player) { player->add_on_state_callback([this]() { this->trigger(); }); } }; @@ -48,5 +53,15 @@ MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED) +template class IsIdleCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_IDLE; } +}; + +template class IsPlayingCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING; } +}; + } // namespace media_player } // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index c82673b196..0dfbeed550 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -615,12 +615,18 @@ media_player: mute_pin: GPIO14 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% From 4c37c17df129698e6962bfab52358cc0a16f8f41 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 20 Jun 2022 19:31:17 -0400 Subject: [PATCH 005/838] Fix 2 small issues in BLEClient (#3544) --- esphome/components/ble_client/ble_client.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 7bef0d652c..d06c3c4cad 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -113,6 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } case ESP_GATTC_OPEN_EVT: { ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); + this->conn_id = param->open.conn_id; if (param->open.status != ESP_GATT_OK) { ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); this->set_states_(espbt::ClientState::IDLE); @@ -122,7 +123,10 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } case ESP_GATTC_CONNECT_EVT: { ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); - this->conn_id = param->connect.conn_id; + if (this->conn_id != param->connect.conn_id) { + ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", + this->address_str().c_str(), param->connect.conn_id, this->conn_id); + } auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); @@ -183,9 +187,10 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es descr->uuid.to_string().c_str()); break; } - uint8_t notify_en = 1; - auto status = esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en), - ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + uint16_t notify_en = 1; + auto status = + esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en), + (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); } From 020b2c05c828ca0faea7676ff73e8df37674cbb4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Jun 2022 10:34:10 +1200 Subject: [PATCH 006/838] Bump version to 2022.6.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index cdb923a107..f7b593b541 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.6.1" +__version__ = "2022.6.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From df73170e5ae387960baeb8a27c14d962bad00119 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Sun, 24 Jul 2022 22:06:18 +0200 Subject: [PATCH 007/838] modbus: fix queue deduplicator erasing custom commands (#3650) --- .../modbus_controller/modbus_controller.cpp | 12 ++++++++++-- .../components/modbus_controller/modbus_controller.h | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 91e0dcc45f..b7fde157d8 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -108,8 +108,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) { // check if this commmand is already qeued. // not very effective but the queue is never really large for (auto &item : command_queue_) { - if (item->register_address == command.register_address && item->register_count == command.register_count && - item->register_type == command.register_type && item->function_code == command.function_code) { + if (item->is_equal(command)) { ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", static_cast(command.register_type), command.register_address, command.register_count); // update the payload of the queued command @@ -489,6 +488,15 @@ bool ModbusCommandItem::send() { return true; } +bool ModbusCommandItem::is_equal(const ModbusCommandItem &other) { + // for custom commands we have to check for identical payloads, since + // address/count/type fields will be set to zero + return this->function_code == ModbusFunctionCode::CUSTOM + ? this->payload == other.payload + : other.register_address == this->register_address && other.register_count == this->register_count && + other.register_type == this->register_type && other.function_code == this->function_code; +} + void number_to_payload(std::vector &data, int64_t value, SensorValueType value_type) { switch (value_type) { case SensorValueType::U_WORD: diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 6aecf7f8a4..f67242a68e 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -395,6 +395,8 @@ class ModbusCommandItem { ModbusController *modbusdevice, const std::vector &values, std::function &data)> &&handler = nullptr); + + bool is_equal(const ModbusCommandItem &other); }; /** Modbus controller class. From 311980e0e478612a01020758d2b71bec2cd02fdc Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 31 Jul 2022 18:08:19 -0700 Subject: [PATCH 008/838] Update inkbird_ibsth1_mini.cpp (#3664) --- esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 76013e28ff..94c22ae84d 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -55,7 +55,7 @@ bool InkbirdIbstH1Mini::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); return false; } - if (mnf_data.data[6] != 8) { + if ((mnf_data.data[6] != 8) && (mnf_data.data[6] != 6)) { ESP_LOGVV(TAG, "parse_device(): unexpected data"); return false; } From eb878710c1e6f750c0ca532ee4d258447bac30d6 Mon Sep 17 00:00:00 2001 From: Bryan Berg Date: Mon, 1 Aug 2022 16:32:02 -0700 Subject: [PATCH 009/838] Add CO device class to binary_sensor (#3656) --- esphome/components/binary_sensor/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 40f95d72f9..63fd34c7d5 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -29,6 +29,7 @@ from esphome.const import ( DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_DOOR, @@ -63,6 +64,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_DOOR, From 50f32a3aa59cf31cf55abc73543ee7f09fa7d96e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 1 Aug 2022 22:07:32 -0700 Subject: [PATCH 010/838] Use application/json instead of text/json (#3671) --- esphome/components/web_server/web_server.cpp | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 18374d606b..f0a7efd12f 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -353,7 +353,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM if (obj->get_object_id() != match.id) continue; std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } request->send(404); @@ -377,7 +377,7 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const if (obj->get_object_id() != match.id) continue; std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } request->send(404); @@ -406,7 +406,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET) { std::string data = this->switch_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle(); }); request->send(200); @@ -462,7 +462,7 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con if (obj->get_object_id() != match.id) continue; std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } request->send(404); @@ -490,7 +490,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc if (request->method() == HTTP_GET) { std::string data = this->fan_json(obj, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); @@ -551,7 +551,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET) { std::string data = this->light_json(obj, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); @@ -630,7 +630,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET) { std::string data = this->cover_json(obj, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); continue; } @@ -687,7 +687,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET) { std::string data = this->number_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } if (match.method != "set") { @@ -741,7 +741,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET) { std::string data = this->select_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } @@ -788,7 +788,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url if (request->method() == HTTP_GET) { std::string data = this->climate_json(obj, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); return; } @@ -926,7 +926,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat if (request->method() == HTTP_GET) { std::string data = this->lock_json(obj, obj->state, DETAIL_STATE); - request->send(200, "text/json", data.c_str()); + request->send(200, "application/json", data.c_str()); } else if (match.method == "lock") { this->defer([obj]() { obj->lock(); }); request->send(200); From e5eaf7a3fecfbb9818890aed7ef7a56cce41ae0e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 1 Aug 2022 22:09:17 -0700 Subject: [PATCH 011/838] Use correct struct members. (#3672) --- esphome/components/remote_base/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 165da95d67..5ccfc500cf 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -744,7 +744,8 @@ def rc6_binary_sensor(var, config): var.set_data( cg.StructInitializer( RC6Data, - ("device", config[CONF_DEVICE]), + ("mode", 0), + ("toggle", 0), ("address", config[CONF_ADDRESS]), ("command", config[CONF_COMMAND]), ) From bf8eddb13b4a5846c1e6bd21fb574e97e7bce684 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 8 Aug 2022 08:24:12 +1200 Subject: [PATCH 012/838] Bump version to 2022.6.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f7b593b541..f95628735d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.6.2" +__version__ = "2022.6.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From a97e3d827dc190da1fbc87f08b6b15b17a688255 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 10 Aug 2022 15:26:02 +1200 Subject: [PATCH 013/838] Bump version to 2022.8.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 6de2581fc8..0593c19672 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.7.0-dev" +__version__ = "2022.8.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From e2c8e69d125a248169bb92fe3a86dbca0f91f611 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Thu, 11 Aug 2022 04:32:55 +0300 Subject: [PATCH 014/838] Improve Web view for Climate components (#3706) --- esphome/components/web_server/server_index.h | 1136 +++++++++--------- esphome/components/web_server/web_server.cpp | 27 +- 2 files changed, 594 insertions(+), 569 deletions(-) diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 719a804d0c..39c170f15b 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,568 +6,580 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, - 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0xac, 0x56, 0x01, 0x16, 0x08, 0x91, 0x54, 0x6d, 0x06, 0x05, 0xf2, 0xca, 0x55, - 0xe5, 0x5b, 0x65, 0xd7, 0xe6, 0x92, 0xaa, 0xbc, 0xc8, 0x74, 0x09, 0x22, 0x93, 0x22, 0x5c, 0x20, 0x40, 0x03, 0x49, - 0x2d, 0xa6, 0xd0, 0xa7, 0x9f, 0xfa, 0x69, 0xce, 0x99, 0xf5, 0xa1, 0x5f, 0xe6, 0xf4, 0xcb, 0x7c, 0xc4, 0x7c, 0xcf, - 0xfd, 0x81, 0xe9, 0x4f, 0x98, 0x88, 0xc8, 0x05, 0x09, 0x90, 0x5a, 0xec, 0xf6, 0xdc, 0x53, 0x8b, 0x80, 0x5c, 0x23, - 0x23, 0x23, 0x63, 0x4f, 0x68, 0x6f, 0x63, 0x9c, 0x8d, 0xf8, 0xe5, 0x9c, 0x59, 0x53, 0x3e, 0x4b, 0xfa, 0x7b, 0xf2, - 0x7f, 0x16, 0x8d, 0xfb, 0x7b, 0x49, 0x9c, 0x7e, 0xb2, 0x72, 0x96, 0x84, 0xf1, 0x28, 0x4b, 0xad, 0x69, 0xce, 0x26, - 0xe1, 0x38, 0xe2, 0x51, 0x10, 0xcf, 0xa2, 0x53, 0x66, 0xed, 0xf4, 0xf7, 0x66, 0x8c, 0x47, 0xd6, 0x68, 0x1a, 0xe5, - 0x05, 0xe3, 0xe1, 0xfb, 0xc3, 0xaf, 0x5a, 0x8f, 0xfb, 0x7b, 0xc5, 0x28, 0x8f, 0xe7, 0xdc, 0xc2, 0x21, 0xc3, 0x59, - 0x36, 0x5e, 0x24, 0xac, 0x7f, 0x16, 0xe5, 0xd6, 0x3e, 0x0b, 0xdf, 0x9c, 0xfc, 0xc2, 0x46, 0xdc, 0x1f, 0xb3, 0x49, - 0x9c, 0xb2, 0xb7, 0x79, 0x36, 0x67, 0x39, 0xbf, 0xf4, 0x2e, 0xd6, 0x57, 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x53, - 0xc6, 0xdf, 0x9c, 0xa7, 0xaa, 0xcf, 0x53, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0xe3, 0xd7, 0xb4, 0x39, 0xb8, 0x9c, 0x9d, - 0x64, 0x49, 0xe1, 0x1d, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, 0xb4, 0xf4, 0x3e, 0xad, - 0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x24, 0x61, 0x5e, 0xc1, 0x42, 0x87, 0x79, - 0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0x60, 0x9f, 0x51, 0xc9, 0x92, 0xe9, 0x56, 0xc1, 0x46, 0xdb, - 0x03, 0x74, 0x4d, 0xe2, 0xd3, 0x85, 0x7e, 0x3f, 0xcf, 0x63, 0xae, 0x9e, 0xcf, 0xa2, 0x64, 0xc1, 0x82, 0xb8, 0x74, - 0x03, 0x76, 0xc4, 0x87, 0x61, 0xec, 0x3d, 0xa1, 0x41, 0x61, 0xc8, 0xe5, 0x24, 0xcb, 0x1d, 0xc4, 0x55, 0x8c, 0x63, - 0xf3, 0xab, 0x2b, 0x87, 0x87, 0xcb, 0xd2, 0x75, 0x0f, 0x98, 0x3f, 0x8a, 0x92, 0xc4, 0xc1, 0x89, 0xb7, 0xb6, 0x0a, - 0x9c, 0x31, 0xf6, 0xf8, 0x51, 0x3c, 0x74, 0x7b, 0xf1, 0xc4, 0xe1, 0xcc, 0xad, 0xfa, 0x65, 0x13, 0x8b, 0x33, 0x87, - 0xbb, 0xee, 0xa7, 0xeb, 0xfb, 0xe4, 0x8c, 0x2f, 0x72, 0x80, 0xbd, 0xf4, 0xde, 0xa8, 0x99, 0x2f, 0xb0, 0xfe, 0x19, - 0x75, 0xec, 0x01, 0xec, 0x05, 0xb7, 0x3e, 0x84, 0xe7, 0x71, 0x3a, 0xce, 0xce, 0xfd, 0x83, 0x69, 0x04, 0x3f, 0xde, - 0x65, 0x19, 0xdf, 0xda, 0x72, 0xce, 0xb2, 0x78, 0x6c, 0xb5, 0xc3, 0xd0, 0xac, 0xbc, 0x7c, 0x72, 0x70, 0x70, 0x75, - 0xd5, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0x67, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x39, 0xe7, 0x6c, 0x7c, 0xc0, 0x2f, - 0x13, 0x28, 0x65, 0x8c, 0x17, 0x36, 0xac, 0xf1, 0x69, 0x36, 0x02, 0xb4, 0xa5, 0x06, 0xe2, 0xa1, 0x69, 0xce, 0xe6, - 0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0xd8, 0x5e, 0xc7, 0xf5, 0x62, 0x16, - 0xa6, 0xec, 0xdc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0xa0, 0xd7, 0x25, 0x2d, 0x21, 0x5f, 0x8c, 0x80, - 0x40, 0x68, 0x81, 0x4b, 0x44, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x9b, 0xa3, 0xa2, 0x78, 0xc7, 0x8a, 0x45, 0xc2, 0x37, - 0x43, 0xd8, 0x0b, 0xbe, 0x11, 0x86, 0x5f, 0xb9, 0x7c, 0x9a, 0x67, 0xe7, 0xd6, 0xb3, 0x3c, 0x87, 0xe6, 0x36, 0x4c, - 0x29, 0x1a, 0x58, 0x71, 0x61, 0xa5, 0x19, 0xb7, 0xf4, 0x60, 0xb8, 0x81, 0xbe, 0xf5, 0xbe, 0x60, 0xd6, 0xf1, 0x22, - 0x2d, 0xa2, 0x09, 0x83, 0xa6, 0xc7, 0x56, 0x96, 0x5b, 0xc7, 0x30, 0xe8, 0x31, 0x6c, 0x59, 0xc1, 0xe1, 0xd4, 0xf8, - 0xb6, 0xdb, 0xa3, 0xb9, 0xa0, 0xf0, 0x90, 0x5d, 0xf0, 0x90, 0x95, 0x40, 0x98, 0x56, 0xa1, 0x97, 0xe1, 0xb8, 0xcb, - 0x04, 0x0a, 0x58, 0x18, 0x33, 0x24, 0x59, 0xc7, 0x6c, 0xac, 0x37, 0xe7, 0xc3, 0xd6, 0x96, 0xc6, 0x35, 0xe0, 0xc4, - 0x81, 0xb6, 0x45, 0xa3, 0xad, 0x27, 0x16, 0x5e, 0x43, 0x91, 0xeb, 0x31, 0x5f, 0xa2, 0xef, 0xe0, 0x32, 0x1d, 0xd5, - 0xc7, 0x86, 0xca, 0x92, 0x67, 0x07, 0x3c, 0x8f, 0xd3, 0x53, 0x00, 0x42, 0xce, 0x64, 0x36, 0x29, 0x4b, 0xb1, 0xf9, - 0x4f, 0x58, 0xc8, 0xc2, 0x3e, 0x8e, 0x9e, 0x33, 0xc7, 0x2e, 0xa8, 0x87, 0x1d, 0x86, 0x88, 0x7a, 0x20, 0x30, 0x36, - 0x60, 0x01, 0xdb, 0xb6, 0x6d, 0xef, 0x2b, 0xd7, 0x3b, 0x47, 0x0a, 0xf2, 0x7d, 0x9f, 0xc8, 0x57, 0x74, 0x8e, 0xc3, - 0x0e, 0x02, 0xed, 0x27, 0x2c, 0x3d, 0xe5, 0xd3, 0x01, 0x3b, 0x6a, 0x0f, 0x03, 0x0e, 0x50, 0x8d, 0x17, 0x23, 0xe6, - 0x20, 0x3d, 0x7a, 0x05, 0x1e, 0x9f, 0x6d, 0x07, 0xa6, 0xc0, 0x8d, 0xd9, 0xa0, 0x35, 0xd6, 0xb6, 0xc6, 0x55, 0x24, - 0xaa, 0x00, 0x43, 0x3a, 0xb7, 0xe1, 0x84, 0x9d, 0xb0, 0xdc, 0x80, 0x43, 0x37, 0xeb, 0xd5, 0x76, 0x70, 0x01, 0x3b, - 0x04, 0xfd, 0xac, 0xc9, 0x22, 0x1d, 0xf1, 0x18, 0x18, 0x97, 0xbd, 0x0d, 0xe0, 0x8a, 0x9d, 0xd3, 0x1b, 0x67, 0xbb, - 0xa5, 0xeb, 0xc4, 0xee, 0x36, 0x3b, 0x2a, 0xb6, 0x3b, 0x43, 0x0f, 0xa1, 0xd4, 0xc8, 0x97, 0x0b, 0x8f, 0x61, 0x81, - 0x70, 0x46, 0x98, 0x3e, 0x9e, 0x1f, 0x06, 0xcc, 0x5f, 0xa5, 0xe3, 0x90, 0xfb, 0xb3, 0x68, 0x8e, 0xab, 0x61, 0x44, - 0x03, 0x51, 0x3a, 0x42, 0xe8, 0x6a, 0xfb, 0x82, 0x18, 0xf3, 0x2b, 0x12, 0x70, 0x01, 0x21, 0x70, 0x66, 0x9f, 0x45, - 0xa3, 0x29, 0x1c, 0xf1, 0x0a, 0x71, 0x63, 0x75, 0x1c, 0x46, 0x39, 0x8b, 0x38, 0x7b, 0x96, 0x30, 0x7c, 0xc3, 0x1d, - 0x80, 0x9e, 0xb6, 0xeb, 0x15, 0xea, 0xdc, 0x25, 0x31, 0x7f, 0x9d, 0xc1, 0x3c, 0x3d, 0x41, 0x24, 0x40, 0xc5, 0xc5, - 0xd6, 0x56, 0x8c, 0x24, 0xb2, 0xcf, 0x61, 0xb7, 0x4e, 0x16, 0xc0, 0x04, 0xec, 0x14, 0x5b, 0xd8, 0x80, 0x6d, 0x2f, - 0xf6, 0x39, 0x20, 0xf1, 0x49, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, 0x14, 0xe4, 0x47, 0xf3, 0x39, 0x4b, 0xc7, 0x4f, - 0xa6, 0x71, 0x32, 0x06, 0x6c, 0x94, 0xb0, 0xde, 0x8c, 0x85, 0xb0, 0x4e, 0x58, 0x4c, 0x70, 0xf3, 0x8a, 0x68, 0xfb, - 0x90, 0x90, 0x79, 0x68, 0xdb, 0x3d, 0xe4, 0x40, 0x72, 0x15, 0xc8, 0x83, 0x68, 0xe3, 0xde, 0x01, 0xeb, 0x2f, 0x5c, - 0xbe, 0x1d, 0xc6, 0x7a, 0x1b, 0x25, 0x82, 0x9f, 0x20, 0xa7, 0x01, 0xfc, 0x33, 0xe0, 0x81, 0x3d, 0x64, 0x5c, 0xdf, - 0x49, 0xae, 0x93, 0x32, 0xb5, 0x42, 0x40, 0xc0, 0x08, 0x39, 0x88, 0xc4, 0xc1, 0xdb, 0x2c, 0xb9, 0x9c, 0xc4, 0x49, - 0x72, 0xb0, 0x98, 0xcf, 0xb3, 0x9c, 0x7b, 0x5f, 0x87, 0x4b, 0x9e, 0x55, 0x6b, 0xa5, 0x43, 0x5e, 0x9c, 0xc7, 0x1c, - 0x11, 0xea, 0x2e, 0x47, 0x11, 0x6c, 0xf5, 0x97, 0x59, 0x96, 0xb0, 0x28, 0x85, 0x65, 0xb0, 0x81, 0x6d, 0x07, 0xe9, - 0x22, 0x49, 0x7a, 0x27, 0x30, 0xec, 0xa7, 0x1e, 0x55, 0x0b, 0x8e, 0x1f, 0xd0, 0xf3, 0x7e, 0x9e, 0x47, 0x97, 0xd0, - 0x10, 0xdb, 0x00, 0x2d, 0xc2, 0x6e, 0x7d, 0x7d, 0xf0, 0xe6, 0xb5, 0x2f, 0x08, 0x3f, 0x9e, 0x5c, 0x02, 0xa0, 0x65, - 0xc5, 0x35, 0x27, 0x79, 0x36, 0x6b, 0x4c, 0x8d, 0x78, 0x88, 0x43, 0xd6, 0xbb, 0x06, 0x84, 0x98, 0x46, 0x86, 0x5d, - 0x62, 0x26, 0x04, 0xaf, 0x89, 0x9e, 0x65, 0x25, 0x9e, 0x81, 0x01, 0x3e, 0x04, 0xa2, 0x18, 0xa6, 0xbc, 0x19, 0x5a, - 0x9e, 0x5f, 0x2e, 0xe3, 0x90, 0xe0, 0x9c, 0xa3, 0xfc, 0x45, 0x18, 0x47, 0x11, 0xcc, 0xbe, 0x14, 0x03, 0x96, 0x0a, - 0xe2, 0xb8, 0x2c, 0xbd, 0x44, 0x13, 0x31, 0x72, 0x3c, 0x64, 0x28, 0x1c, 0x8e, 0xd1, 0xd5, 0x15, 0x83, 0x17, 0xd7, - 0xfb, 0x26, 0x5c, 0x46, 0x6a, 0x3d, 0x28, 0xa1, 0xf0, 0x7c, 0x05, 0x82, 0x4f, 0xa0, 0x24, 0x3b, 0x03, 0x39, 0x08, - 0x70, 0x7e, 0xed, 0x81, 0xfc, 0x4f, 0x10, 0x8a, 0x8d, 0x8e, 0x07, 0x12, 0xf4, 0xc9, 0x34, 0x4a, 0x4f, 0xd9, 0x38, - 0x48, 0x58, 0x29, 0x39, 0xef, 0xbe, 0x05, 0x7b, 0x0c, 0xe4, 0x54, 0x58, 0xcf, 0x0f, 0x5f, 0xbd, 0x94, 0x3b, 0x57, - 0x63, 0xc6, 0xb0, 0x49, 0x0b, 0x10, 0xab, 0xc0, 0xb6, 0x25, 0x3b, 0x7e, 0xc6, 0x15, 0xf7, 0x16, 0x25, 0x71, 0xf1, - 0x7e, 0x0e, 0x2a, 0x06, 0x7b, 0x0b, 0xc3, 0xc0, 0xf4, 0x21, 0x4c, 0x45, 0xe5, 0x30, 0x9f, 0xa8, 0x18, 0xeb, 0x22, - 0xe8, 0x2c, 0x56, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x54, 0x79, 0x3c, 0xb2, 0xa2, 0xf1, 0xf8, 0x45, 0x1a, 0xf3, - 0x38, 0x4a, 0xe2, 0xdf, 0x08, 0x93, 0x4b, 0xa4, 0x31, 0xde, 0x93, 0x9b, 0x00, 0x6b, 0xa7, 0x1e, 0x89, 0xab, 0x98, - 0xec, 0x06, 0x21, 0x43, 0x70, 0xcb, 0x24, 0x3c, 0x1a, 0x4a, 0xf0, 0x12, 0x7f, 0xbe, 0x28, 0xa6, 0x88, 0x58, 0x39, - 0x30, 0x32, 0xf2, 0xec, 0xa4, 0x60, 0xf9, 0x19, 0x1b, 0x6b, 0x0a, 0x28, 0x60, 0x55, 0xd4, 0x1c, 0x94, 0x17, 0x9a, - 0xd1, 0x51, 0x32, 0x94, 0xc1, 0x50, 0x3d, 0x93, 0xcd, 0x32, 0x49, 0xcc, 0x5a, 0xc3, 0xd1, 0x5c, 0xc0, 0x11, 0x4a, - 0x85, 0xe4, 0x04, 0x45, 0xa8, 0x56, 0x38, 0x05, 0x2e, 0x04, 0x52, 0xc1, 0x3c, 0xe6, 0x4a, 0x92, 0x3d, 0x5b, 0x90, - 0x48, 0x28, 0xa0, 0x23, 0x1c, 0x64, 0x82, 0xb4, 0x70, 0xe1, 0x54, 0x01, 0x97, 0x97, 0xe0, 0x0a, 0x2e, 0xa2, 0xd4, - 0x1c, 0x24, 0x80, 0xf0, 0x1b, 0x21, 0x0b, 0x7d, 0x6c, 0x41, 0x64, 0xe0, 0xeb, 0x9d, 0x07, 0xc4, 0xca, 0x75, 0x57, - 0x0b, 0xf1, 0xae, 0x01, 0x1b, 0x27, 0x46, 0x7a, 0xf2, 0x36, 0xb8, 0x9f, 0x66, 0xfb, 0xa3, 0x11, 0x2b, 0x8a, 0x2c, - 0xdf, 0xda, 0xda, 0xa0, 0xf6, 0xd7, 0x29, 0x5a, 0x80, 0x49, 0x57, 0xf3, 0x3a, 0xbb, 0x20, 0x09, 0x6e, 0x8a, 0x15, - 0x25, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0xe0, 0xd9, 0x9e, 0x44, 0x03, 0x60, 0x7d, 0x55, 0xf1, 0x13, 0xfa, 0x4c, 0x1d, - 0x33, 0x6b, 0xf5, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa1, 0x6b, 0x87, 0xc6, 0xdd, 0x90, 0x02, - 0x72, 0x79, 0x4a, 0x22, 0xdb, 0xd8, 0x46, 0xd0, 0xda, 0x4a, 0x8f, 0x50, 0xaf, 0x56, 0x93, 0x13, 0xa0, 0x47, 0x6c, - 0xd8, 0x93, 0xf5, 0x61, 0x21, 0x30, 0x97, 0xb3, 0x5f, 0x17, 0xac, 0xe0, 0x82, 0x74, 0x61, 0xdc, 0x1c, 0xc6, 0x2d, - 0x57, 0xb4, 0xc3, 0x9a, 0xee, 0xb8, 0x0e, 0xb6, 0x37, 0x73, 0x94, 0x63, 0x05, 0x52, 0xf2, 0xcd, 0xe4, 0x84, 0xb0, - 0x32, 0xf7, 0xea, 0xea, 0x1b, 0x35, 0x48, 0xb5, 0x95, 0x5a, 0x07, 0x6a, 0xec, 0x89, 0xad, 0x9a, 0x8c, 0x6d, 0x57, - 0x0a, 0xd4, 0x8d, 0x4e, 0xaf, 0x46, 0x07, 0x70, 0xe6, 0xda, 0x9a, 0xa4, 0x2b, 0x65, 0xfb, 0xad, 0xc2, 0xe9, 0x1b, - 0x31, 0x32, 0x69, 0xa3, 0xec, 0x76, 0xea, 0x51, 0x27, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x8c, 0x61, 0x51, 0x67, 0x0c, - 0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x89, 0x8a, 0x85, 0x6a, 0xaf, 0x95, 0x01, 0xc1, 0xc3, 0x23, 0x50, 0x4e, 0xd6, - 0xda, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8c, 0x7a, 0xd7, 0x35, 0x90, 0x06, 0x04, 0x34, 0x19, 0x36, 0xc5, 0x1b, 0x77, - 0x85, 0xd6, 0x54, 0x3f, 0x5f, 0x31, 0x68, 0x11, 0xa0, 0x5f, 0x97, 0x6b, 0xb6, 0x88, 0xe4, 0xa6, 0x24, 0x67, 0x85, - 0x1f, 0x51, 0x26, 0xf6, 0x84, 0x04, 0x3c, 0x2c, 0x1e, 0xb6, 0xbf, 0xb1, 0x71, 0xb2, 0x15, 0x53, 0x6b, 0xe4, 0xc8, - 0x53, 0x00, 0xcf, 0x24, 0x04, 0x80, 0x5d, 0xd2, 0xcf, 0xda, 0xc1, 0x42, 0xb4, 0x1d, 0x20, 0x1d, 0xf8, 0x93, 0x24, - 0xe2, 0x4e, 0x67, 0xa7, 0xed, 0x02, 0x1d, 0x02, 0x13, 0x07, 0x19, 0x01, 0xea, 0x7d, 0xb5, 0x14, 0x86, 0x4b, 0x89, - 0x5d, 0xee, 0x83, 0x52, 0x34, 0x8d, 0x27, 0xdc, 0xc9, 0x50, 0x88, 0xb8, 0x25, 0x4b, 0x40, 0xc8, 0xe8, 0x73, 0x05, - 0x5c, 0x82, 0x0b, 0xee, 0x22, 0xaa, 0x35, 0x43, 0x53, 0x90, 0x12, 0x97, 0x22, 0x29, 0xa8, 0x20, 0x30, 0x98, 0x4a, - 0x4f, 0x51, 0x14, 0xc8, 0xb7, 0x78, 0x20, 0x06, 0x0d, 0x56, 0x34, 0xca, 0x78, 0x10, 0xaf, 0x16, 0x82, 0x18, 0xf6, - 0x79, 0xf6, 0x32, 0x3b, 0x67, 0xf9, 0x93, 0x08, 0x61, 0x0f, 0x44, 0xf7, 0x12, 0x38, 0x3d, 0x31, 0x74, 0xd6, 0x53, - 0xb4, 0x72, 0x46, 0x8b, 0x86, 0x8d, 0x98, 0xc5, 0x28, 0x08, 0x41, 0xca, 0x11, 0xee, 0x53, 0x3c, 0x52, 0x74, 0xf6, - 0x50, 0x94, 0x30, 0x4d, 0x5b, 0xfb, 0x2f, 0xeb, 0xb4, 0x05, 0x23, 0xcc, 0x15, 0xb5, 0xd6, 0x4f, 0xac, 0xeb, 0x49, - 0xd9, 0xec, 0x48, 0xda, 0x32, 0x84, 0x19, 0xc8, 0x8f, 0xab, 0xab, 0x4a, 0x49, 0x07, 0x61, 0xaa, 0xb9, 0x39, 0x6a, - 0x4e, 0xe2, 0x48, 0xb8, 0x25, 0x08, 0x23, 0x54, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x59, 0xe4, 0xc5, 0x3d, 0xa7, 0x21, - 0x8e, 0x00, 0x8a, 0x59, 0x4d, 0x22, 0x0d, 0x78, 0xa0, 0x2b, 0x50, 0x28, 0x29, 0x69, 0xe4, 0x55, 0x4d, 0x04, 0xc4, - 0xe9, 0x98, 0xe5, 0xc2, 0x40, 0x93, 0x32, 0x14, 0x26, 0x4c, 0x81, 0xa0, 0xd9, 0x18, 0x38, 0xbc, 0x5a, 0x00, 0xa8, - 0x27, 0xfe, 0x34, 0x2b, 0xb8, 0xae, 0x33, 0xa1, 0x8f, 0xaf, 0xae, 0x62, 0x61, 0x2f, 0x22, 0x01, 0xe4, 0x6c, 0x96, - 0x9d, 0xb1, 0x35, 0x50, 0xf7, 0xd4, 0x60, 0x26, 0xc8, 0xc6, 0x30, 0x20, 0x44, 0x41, 0xb4, 0xcc, 0x93, 0x78, 0xc4, - 0xb4, 0x94, 0x9a, 0xf9, 0xa0, 0xd0, 0xb1, 0x0b, 0xe0, 0x11, 0xcc, 0xed, 0xf7, 0xfb, 0x6d, 0xaf, 0xe3, 0x96, 0x02, - 0xe1, 0xcb, 0x15, 0x8c, 0xde, 0x20, 0x1f, 0xa5, 0x0a, 0xbe, 0x8e, 0x17, 0x70, 0xd7, 0x10, 0x8a, 0x5c, 0xd8, 0x49, - 0x9e, 0x64, 0xc4, 0xae, 0x37, 0x86, 0x41, 0x39, 0x53, 0x8c, 0x1b, 0x55, 0x5c, 0x71, 0x6c, 0xdf, 0x69, 0xb4, 0x69, - 0x72, 0x52, 0x27, 0x4c, 0x6d, 0x8c, 0xdc, 0xf3, 0x42, 0x5b, 0xc0, 0xe6, 0xf6, 0xa0, 0x96, 0x48, 0xd5, 0x40, 0xeb, - 0x00, 0xa1, 0xb0, 0x74, 0x9d, 0x95, 0x25, 0x55, 0x9d, 0x25, 0x13, 0xd7, 0x07, 0xe8, 0x0d, 0x93, 0x60, 0xae, 0x43, - 0xc1, 0x81, 0x64, 0x08, 0x1c, 0x2d, 0x32, 0xb1, 0x5f, 0x4f, 0x60, 0x7b, 0x4e, 0xa2, 0xd1, 0x27, 0x0d, 0x6e, 0x85, - 0xf6, 0x26, 0x19, 0x38, 0x8d, 0x92, 0xd0, 0x60, 0x57, 0xe6, 0xba, 0x15, 0x87, 0xae, 0x1d, 0x14, 0x30, 0xc8, 0x56, - 0xc8, 0xbe, 0xb9, 0xd1, 0x4d, 0x6a, 0x97, 0xe4, 0xa1, 0xec, 0x27, 0x4d, 0x25, 0x37, 0x90, 0x1c, 0x57, 0xdc, 0x80, - 0x2b, 0xc2, 0x83, 0xad, 0x69, 0x40, 0x02, 0x74, 0x57, 0x8e, 0xe3, 0xe2, 0x7a, 0x14, 0xfc, 0xa9, 0x60, 0x3e, 0x35, - 0x66, 0xba, 0x15, 0x52, 0xcd, 0xe1, 0xa4, 0x1a, 0xac, 0x41, 0x93, 0xca, 0x83, 0x62, 0x35, 0xdf, 0xa0, 0xa2, 0x42, - 0x14, 0x7f, 0x2a, 0xaa, 0x50, 0x05, 0x43, 0x30, 0x0a, 0x2f, 0x97, 0x04, 0x97, 0xad, 0xb2, 0x16, 0xc9, 0x53, 0x63, - 0x12, 0xa9, 0x9a, 0xe4, 0x32, 0x50, 0xb0, 0xe8, 0xb4, 0xfa, 0x52, 0x13, 0x57, 0x2c, 0x37, 0x0d, 0x35, 0x33, 0xc9, - 0x95, 0x35, 0xe1, 0x14, 0x68, 0x77, 0x29, 0xed, 0xdd, 0x5c, 0x4f, 0xa1, 0xd6, 0x53, 0xf8, 0x86, 0x0d, 0x65, 0xd2, - 0x76, 0x3e, 0x00, 0x75, 0xbf, 0x56, 0x89, 0xfa, 0xa9, 0x8f, 0x8c, 0xd9, 0xd5, 0x4c, 0x17, 0x18, 0x8a, 0x24, 0x93, - 0x74, 0x20, 0xe9, 0x0d, 0xd9, 0x46, 0x65, 0x19, 0x65, 0xae, 0x38, 0x20, 0x35, 0xab, 0x34, 0xf3, 0x52, 0xb7, 0xa1, - 0xbf, 0x97, 0xa5, 0xc4, 0x13, 0x17, 0x98, 0x89, 0xbd, 0x9b, 0x70, 0xe3, 0xa5, 0x61, 0x26, 0xb4, 0x5f, 0xa1, 0xec, - 0xd4, 0x30, 0x94, 0x4a, 0x16, 0x88, 0x63, 0xe3, 0x6b, 0xa5, 0x19, 0x64, 0xfe, 0x1a, 0x7d, 0x0a, 0x40, 0x49, 0x60, - 0xf3, 0x35, 0x96, 0xbc, 0x28, 0xac, 0xe3, 0x71, 0x83, 0xf0, 0x58, 0xb1, 0xd0, 0x1a, 0xcb, 0xd7, 0xf2, 0x2c, 0xf6, - 0x6b, 0x26, 0xa1, 0x89, 0xc9, 0x62, 0x50, 0x04, 0xb6, 0x72, 0x44, 0x54, 0xb2, 0x2d, 0x19, 0x24, 0x64, 0x90, 0xae, - 0x22, 0xbd, 0x36, 0x92, 0x81, 0xeb, 0x54, 0x70, 0xb4, 0x74, 0x18, 0x46, 0x0e, 0x1a, 0xee, 0xb4, 0x17, 0x2b, 0x88, - 0x6c, 0xea, 0x9b, 0x44, 0x8a, 0x68, 0x9c, 0x16, 0xa8, 0xc2, 0x99, 0x32, 0xdd, 0x71, 0x60, 0x39, 0xc0, 0xf6, 0x57, - 0x48, 0x6f, 0xad, 0xda, 0xe9, 0xfa, 0x95, 0xc1, 0x77, 0x75, 0x95, 0x20, 0x3d, 0x08, 0x85, 0x17, 0xf6, 0x6c, 0xa0, - 0x78, 0xef, 0xfe, 0x4b, 0x6c, 0x45, 0xfa, 0x67, 0x55, 0x52, 0x59, 0x0a, 0x35, 0xca, 0xad, 0xef, 0x13, 0x33, 0x5d, - 0x8b, 0xaa, 0xe2, 0xc0, 0xe0, 0xea, 0x07, 0x4a, 0x60, 0x57, 0x4b, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x05, - 0x19, 0x2f, 0x1b, 0xeb, 0x4c, 0xc8, 0xad, 0x2d, 0xd3, 0x66, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x38, 0x17, 0x97, - 0x29, 0x69, 0x30, 0x4f, 0x94, 0x38, 0x5a, 0x31, 0x40, 0xdb, 0x3d, 0xb4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e, - 0x45, 0x58, 0xda, 0xc8, 0x9f, 0xa4, 0xd2, 0x01, 0xeb, 0x42, 0x15, 0x92, 0x8c, 0x70, 0x53, 0x17, 0x2d, 0x46, 0x53, - 0x86, 0x2e, 0x70, 0xa5, 0x4f, 0x98, 0xbc, 0x67, 0x03, 0xd7, 0x2d, 0x06, 0x66, 0xeb, 0x61, 0x2f, 0x9b, 0xdd, 0x6b, - 0xea, 0x3f, 0xec, 0x11, 0xf0, 0xb6, 0x99, 0xaa, 0x2b, 0x1b, 0xef, 0x92, 0x45, 0xa2, 0x87, 0x6d, 0xdd, 0xd8, 0x52, - 0xd7, 0xef, 0x35, 0xcc, 0xeb, 0xca, 0x30, 0xaf, 0x09, 0xd5, 0x86, 0x1c, 0x56, 0x66, 0x0e, 0x33, 0x0d, 0x79, 0xb1, - 0x83, 0x6e, 0x4f, 0x38, 0x85, 0xc0, 0x88, 0xd0, 0xfa, 0xa0, 0xa2, 0x06, 0x42, 0x25, 0x57, 0x52, 0x35, 0x5b, 0x24, - 0x63, 0x09, 0x2c, 0x98, 0xb0, 0x5c, 0xd2, 0xd1, 0x79, 0x9c, 0x24, 0x55, 0xe9, 0x9f, 0xca, 0xe0, 0xc5, 0xb0, 0xb7, - 0xb1, 0x76, 0xb1, 0xa2, 0x85, 0x02, 0xc1, 0xd5, 0x4a, 0xd8, 0x7b, 0xc7, 0xad, 0xf6, 0x5d, 0x78, 0x1c, 0xb9, 0xe9, - 0x8d, 0x80, 0x7a, 0xf4, 0xb0, 0x6a, 0xd2, 0xde, 0x7f, 0x86, 0x2e, 0x35, 0x63, 0x3d, 0x28, 0xce, 0xa8, 0xf8, 0x77, - 0xe9, 0x53, 0xbf, 0x73, 0x79, 0xb7, 0x8a, 0xae, 0xa6, 0x43, 0x45, 0x39, 0x3e, 0x4c, 0x17, 0x4b, 0x5b, 0x39, 0x02, - 0x72, 0x3d, 0x2c, 0x72, 0x01, 0x13, 0x35, 0x58, 0x50, 0x8a, 0x55, 0x6b, 0x61, 0xf7, 0xf2, 0x36, 0x67, 0x0e, 0xb9, - 0xc2, 0x45, 0xff, 0x27, 0xd9, 0x6c, 0x8e, 0x9a, 0x59, 0x83, 0xa8, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa6, 0xac, - 0xd6, 0x87, 0x4e, 0x64, 0x8d, 0x9e, 0xb4, 0xa1, 0x0c, 0x06, 0xd5, 0x42, 0x17, 0xd5, 0xf5, 0xe6, 0x26, 0x8b, 0x59, - 0x47, 0xe3, 0x3e, 0xc9, 0x6d, 0xad, 0x4d, 0x7a, 0x1a, 0x07, 0xc4, 0x93, 0x24, 0xc1, 0x9b, 0x04, 0x50, 0x56, 0xc8, - 0x59, 0x96, 0x0d, 0xf4, 0x2d, 0xcb, 0x12, 0xf7, 0xef, 0xdb, 0xde, 0x7e, 0xcd, 0xb2, 0xf6, 0xf6, 0xaf, 0x37, 0x91, - 0xab, 0x3a, 0x69, 0x41, 0x1e, 0x0d, 0xa1, 0x68, 0x45, 0xa7, 0x0c, 0x97, 0xb3, 0x6c, 0xcc, 0x02, 0x1b, 0xba, 0xa7, - 0x76, 0xa9, 0xa4, 0x32, 0x1c, 0x8e, 0x94, 0x39, 0xcb, 0x77, 0x75, 0x4f, 0x6a, 0xb0, 0x0f, 0x24, 0xa0, 0xd5, 0x85, - 0xef, 0xc2, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0x21, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xc9, 0x48, 0x1a, 0xab, 0x21, - 0xa4, 0xbe, 0x1b, 0x7c, 0x17, 0xdc, 0xde, 0xa3, 0xb2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0xf2, 0x88, 0xbd, - 0x34, 0xa7, 0x03, 0x89, 0x53, 0x00, 0x66, 0x0e, 0x41, 0x92, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0x83, 0x97, 0xca, 0xa4, - 0xce, 0xc0, 0x84, 0x00, 0x23, 0x3f, 0x89, 0x79, 0x0b, 0xe3, 0x91, 0xb6, 0xb7, 0x14, 0x15, 0xe8, 0x57, 0x24, 0xbf, - 0x74, 0xa9, 0xac, 0x41, 0xef, 0x63, 0x78, 0x0c, 0xcd, 0x36, 0x37, 0x97, 0xce, 0xab, 0x88, 0x4f, 0xfd, 0x3c, 0x4a, - 0xc7, 0xd9, 0xcc, 0x71, 0xb7, 0x6d, 0xdb, 0xf5, 0x0b, 0xb2, 0x44, 0xbe, 0x70, 0xcb, 0xcd, 0x63, 0x6f, 0xca, 0x42, - 0x7b, 0x60, 0x6f, 0x7f, 0xf4, 0xde, 0xb2, 0xf0, 0x78, 0x6f, 0x73, 0x39, 0x65, 0x65, 0xff, 0xd8, 0xbb, 0xd0, 0x3e, - 0x77, 0xef, 0x2d, 0x72, 0x19, 0xe8, 0x15, 0xf6, 0x2f, 0x24, 0x18, 0x40, 0x6e, 0xe4, 0x7f, 0x07, 0x2e, 0xf7, 0x9e, - 0x02, 0x22, 0xd2, 0x4f, 0x7b, 0x75, 0x65, 0x67, 0xe4, 0x31, 0xb0, 0x37, 0xb4, 0xb1, 0xba, 0xb5, 0x55, 0x89, 0xf9, - 0xaa, 0xd4, 0x1b, 0xb1, 0xb0, 0x66, 0xa9, 0x7b, 0xef, 0x29, 0xb4, 0x52, 0x3f, 0xc8, 0x23, 0x46, 0x42, 0x73, 0x55, - 0x4f, 0x70, 0x8c, 0x23, 0xbe, 0xfe, 0x58, 0x1f, 0x09, 0x2f, 0x85, 0x1f, 0x83, 0xf6, 0x12, 0x81, 0xf8, 0x06, 0x03, - 0xc7, 0x3b, 0x0c, 0x77, 0xf6, 0x9c, 0x41, 0xe0, 0x6c, 0xb4, 0x5a, 0x57, 0x3f, 0xed, 0x1c, 0xfd, 0x1c, 0xb5, 0x7e, - 0xdb, 0x6f, 0xfd, 0x38, 0x74, 0xaf, 0x9c, 0x9f, 0x76, 0x06, 0x47, 0xf2, 0xed, 0xe8, 0xe7, 0xfe, 0x4f, 0xc5, 0xf0, - 0x73, 0x51, 0xb8, 0xe9, 0xba, 0x3b, 0xa7, 0x60, 0x29, 0x85, 0x3b, 0xad, 0x56, 0x1f, 0x9e, 0x16, 0xf0, 0x84, 0x3f, - 0x2f, 0xe1, 0xc7, 0xd5, 0x91, 0xf5, 0x1f, 0x7e, 0x4a, 0xff, 0xe3, 0x4f, 0xf9, 0x10, 0xc7, 0x3c, 0xfa, 0xf9, 0xa7, - 0xc2, 0xbe, 0xd7, 0x0f, 0x77, 0x86, 0xdb, 0xae, 0xa3, 0x6b, 0x3e, 0x0f, 0xab, 0x47, 0x68, 0x75, 0xf4, 0xb3, 0x7c, - 0xb3, 0xef, 0x1d, 0xef, 0xf5, 0xc3, 0xe1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x13, 0xe7, 0x99, - 0xc3, 0xe8, 0xf7, 0xe0, 0xe7, 0x19, 0xfc, 0xb4, 0xe1, 0xe7, 0x29, 0xfc, 0xfc, 0x19, 0xba, 0x09, 0xff, 0xdb, 0x15, - 0xf9, 0x42, 0xae, 0x30, 0x60, 0x11, 0xc1, 0x2e, 0xb8, 0x9b, 0x3b, 0xb1, 0x37, 0x21, 0xa4, 0xc1, 0x39, 0xf4, 0x7d, - 0x1f, 0xdd, 0xa4, 0xce, 0xf2, 0xe3, 0x26, 0x6c, 0x3a, 0x52, 0xce, 0x66, 0xc0, 0x3c, 0xe1, 0x39, 0x28, 0x02, 0x2e, - 0x62, 0xab, 0x05, 0x06, 0x57, 0xbd, 0x45, 0x38, 0x61, 0x0e, 0x28, 0x05, 0x87, 0x0c, 0x1f, 0xba, 0xae, 0xf7, 0x4c, - 0xc6, 0x0c, 0xf1, 0x9c, 0x0b, 0xd2, 0x4a, 0x33, 0xa1, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, 0xc7, 0x3a, 0x3d, - 0x85, 0xba, 0x4d, 0x11, 0x68, 0xfb, 0x8e, 0x45, 0x9f, 0xf0, 0x48, 0x3e, 0x37, 0x82, 0xc0, 0x2b, 0x9a, 0x7c, 0x53, - 0x69, 0x34, 0x74, 0x44, 0x61, 0x8e, 0x7d, 0xc9, 0x60, 0x86, 0x15, 0x15, 0x91, 0x93, 0xd0, 0x14, 0x9a, 0x2d, 0x4c, - 0xfe, 0x36, 0xca, 0xf9, 0x66, 0xa5, 0xd8, 0x86, 0x35, 0x4d, 0xb6, 0xa9, 0xe9, 0xdf, 0x61, 0x0a, 0x54, 0x2d, 0x29, - 0xfe, 0x61, 0x8e, 0x1f, 0xa6, 0xb4, 0xac, 0xd7, 0x0e, 0x07, 0x0b, 0xbd, 0x00, 0xbe, 0x23, 0xfa, 0x39, 0x6f, 0x51, - 0x8c, 0xc1, 0x5f, 0xe9, 0x66, 0xf0, 0xc4, 0x7c, 0xe8, 0xa2, 0x59, 0x96, 0xda, 0xb9, 0x95, 0x22, 0xbb, 0x7f, 0x81, - 0x27, 0x23, 0x2d, 0xbd, 0x83, 0x50, 0x9d, 0x98, 0xc3, 0x9c, 0xb1, 0xef, 0xa2, 0xe4, 0x13, 0xcb, 0x9d, 0x0b, 0xaf, - 0xd3, 0xfd, 0x82, 0x3a, 0x7b, 0xa8, 0x9b, 0xbd, 0xae, 0xc2, 0x68, 0x4a, 0x2d, 0x50, 0x21, 0xc2, 0x56, 0xc7, 0x43, - 0x8e, 0x41, 0x28, 0xc8, 0xbd, 0x2c, 0xec, 0x12, 0x85, 0xdb, 0x7b, 0xc5, 0xd9, 0x69, 0xdf, 0x0e, 0x6c, 0x1b, 0x34, - 0xfe, 0x43, 0x72, 0x5b, 0x09, 0xc5, 0x02, 0x14, 0xb2, 0xbd, 0xb8, 0xc7, 0xb7, 0xb7, 0x2b, 0x87, 0x13, 0x06, 0xd2, - 0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x34, 0x84, 0x01, 0x47, 0xd0, 0x0c, 0xbb, 0xf4, 0x46, 0x7b, 0xb1, 0x9c, 0x06, 0x7d, - 0x21, 0x7e, 0x12, 0x15, 0xfc, 0x05, 0xfa, 0x23, 0xc2, 0x11, 0x2a, 0xfb, 0x3e, 0xbb, 0x60, 0x23, 0xa5, 0x67, 0x00, - 0xa2, 0x22, 0xb7, 0xe7, 0x8e, 0x42, 0xa3, 0x19, 0xcc, 0x1d, 0x86, 0x87, 0x03, 0x1b, 0xce, 0x12, 0x9c, 0xca, 0x30, - 0x3a, 0xea, 0x0c, 0x07, 0x69, 0x08, 0xbc, 0x56, 0xe3, 0x56, 0x16, 0x2d, 0x6a, 0x45, 0xdd, 0xe1, 0xc0, 0x39, 0x05, - 0x25, 0x1d, 0x74, 0x71, 0x07, 0xdf, 0xd0, 0x43, 0x91, 0x87, 0xef, 0xd8, 0xe9, 0xb3, 0x8b, 0xb9, 0x63, 0xef, 0xed, - 0xd8, 0xdb, 0x58, 0xea, 0xd9, 0x40, 0x5e, 0x30, 0x77, 0x78, 0xe9, 0x9a, 0x9d, 0x77, 0x87, 0x08, 0x2a, 0x16, 0xe2, - 0xe4, 0x97, 0x03, 0xbb, 0x2f, 0xa6, 0x6e, 0xc3, 0xa0, 0xa9, 0xdc, 0x7e, 0xdc, 0xd1, 0x43, 0x5a, 0xaa, 0xea, 0xaa, - 0xa0, 0x83, 0xb2, 0x6e, 0xe0, 0x4c, 0xcd, 0x45, 0xb4, 0x70, 0x32, 0x89, 0x05, 0x30, 0x78, 0xb0, 0x19, 0x4c, 0x6a, - 0x74, 0xdb, 0x1d, 0x0e, 0x2e, 0x83, 0x7b, 0xf6, 0x3d, 0xf5, 0x72, 0xc6, 0x02, 0xb0, 0x2e, 0x68, 0xfa, 0x33, 0x94, - 0x22, 0xf0, 0x73, 0xce, 0x60, 0x91, 0x97, 0x54, 0x34, 0x96, 0x45, 0x0b, 0x2c, 0x3a, 0x0c, 0x10, 0x54, 0x2f, 0xd7, - 0xda, 0x9f, 0xd8, 0x93, 0x71, 0x48, 0xb0, 0x6f, 0x6d, 0xc1, 0xd6, 0x6c, 0x77, 0x86, 0x18, 0x6f, 0xc8, 0x79, 0xf1, - 0x5d, 0xcc, 0x41, 0x24, 0xec, 0xf4, 0x6d, 0x77, 0x60, 0x5b, 0xb8, 0xb5, 0xbd, 0x6c, 0x3b, 0x14, 0x18, 0x8e, 0xb7, - 0xdf, 0xb2, 0x60, 0xda, 0x0f, 0xdb, 0x03, 0xa7, 0x10, 0xa2, 0x23, 0xc1, 0xb8, 0xa5, 0xe0, 0xe0, 0x6d, 0x6f, 0x0a, - 0x0c, 0x1d, 0x29, 0x77, 0xd3, 0xde, 0x56, 0x85, 0x50, 0xf4, 0x71, 0x7b, 0xec, 0x06, 0x31, 0xfc, 0x70, 0x5a, 0x48, - 0x34, 0x53, 0xdd, 0x57, 0x4b, 0x66, 0x37, 0x18, 0x2b, 0x8d, 0x3c, 0x09, 0xb3, 0x6d, 0x07, 0x3d, 0xb4, 0xc0, 0x69, - 0xf7, 0x06, 0x00, 0xc3, 0xb6, 0xa3, 0x28, 0x6d, 0x47, 0x91, 0x9a, 0xd2, 0xcf, 0x8f, 0xaa, 0xed, 0x60, 0x83, 0x88, - 0xf9, 0x95, 0xf4, 0x01, 0xb0, 0x82, 0xc4, 0x2b, 0x86, 0x2a, 0xe6, 0xf5, 0xbc, 0x16, 0xdf, 0x5a, 0x2a, 0x56, 0xc4, - 0x3c, 0x83, 0x43, 0xf1, 0x52, 0x9b, 0x61, 0x42, 0xdd, 0x9e, 0x23, 0x32, 0x34, 0xc9, 0x87, 0x6d, 0x20, 0x7a, 0xe5, - 0x60, 0x4f, 0xcd, 0x63, 0x91, 0x84, 0x55, 0x73, 0xef, 0x08, 0x48, 0x7b, 0x18, 0xbe, 0x16, 0x11, 0xc7, 0x9e, 0xf2, - 0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x08, 0x17, 0x47, 0x18, 0x11, 0x3a, 0xf0, 0x47, 0x8b, 0x1c, 0xf8, 0x01, 0x7f, 0x0d, - 0x9a, 0x41, 0x28, 0x9b, 0xa2, 0xa1, 0x87, 0x21, 0x60, 0x8f, 0x16, 0xde, 0x70, 0x9b, 0x1b, 0xd5, 0xa8, 0x51, 0x92, - 0xf2, 0x42, 0x81, 0xe1, 0x1e, 0x97, 0xa6, 0x3d, 0x32, 0x06, 0x19, 0x31, 0x76, 0x30, 0xe6, 0xef, 0x8f, 0xb0, 0x1a, - 0x27, 0x28, 0xdc, 0x92, 0x4e, 0x5b, 0xc5, 0xfe, 0x0e, 0xfc, 0x14, 0x38, 0x38, 0xd6, 0x81, 0x9d, 0xb5, 0xb5, 0x95, - 0xc8, 0x45, 0xed, 0xa5, 0x3d, 0x8a, 0x44, 0xa0, 0x3f, 0xb8, 0xf0, 0x53, 0xa8, 0x46, 0x14, 0x51, 0x11, 0x69, 0xa0, - 0x66, 0x54, 0xad, 0x82, 0xef, 0xc8, 0xf4, 0xc0, 0x73, 0x74, 0x5b, 0x93, 0xa2, 0xa8, 0x1b, 0x0b, 0x5f, 0xbe, 0xeb, - 0x52, 0x68, 0x0b, 0x03, 0x90, 0x82, 0xd0, 0x04, 0xc1, 0xb8, 0xe4, 0x94, 0xac, 0xe8, 0xef, 0xa3, 0xe1, 0x2b, 0x9f, - 0x1e, 0x65, 0xdb, 0xdb, 0x43, 0x11, 0xb7, 0x20, 0xc2, 0xe1, 0x86, 0x77, 0x35, 0xae, 0x00, 0xa8, 0x4f, 0xe7, 0xc4, - 0x75, 0xc7, 0xb4, 0x22, 0x4d, 0x97, 0x7c, 0x9f, 0x1c, 0x66, 0x00, 0x0c, 0xee, 0x38, 0x47, 0xfe, 0xe0, 0x2f, 0x43, - 0x30, 0x8f, 0xfd, 0xcf, 0xdd, 0x1d, 0xc5, 0x68, 0x7a, 0x32, 0xa6, 0xb8, 0xa4, 0x18, 0x6b, 0xc7, 0x23, 0xdf, 0x68, - 0x90, 0x7b, 0x29, 0xac, 0x00, 0xa4, 0x39, 0xf0, 0x84, 0x8a, 0x82, 0x90, 0xa2, 0x02, 0xdb, 0xc7, 0xc3, 0xcf, 0xf1, - 0x64, 0xbf, 0x03, 0x0d, 0x6f, 0xa0, 0xdf, 0x9e, 0xc2, 0xdb, 0x5f, 0xf4, 0xdb, 0x97, 0x2c, 0xf8, 0xa5, 0x94, 0xae, - 0xfb, 0xda, 0x14, 0x0f, 0xd5, 0x14, 0xa5, 0xd8, 0x22, 0x03, 0x87, 0xcc, 0x5d, 0xf5, 0xd9, 0x70, 0xb7, 0x04, 0x64, - 0x28, 0xd6, 0x05, 0x3a, 0x5a, 0x74, 0x8a, 0xc8, 0x75, 0x4d, 0x54, 0x18, 0xb9, 0x04, 0xe6, 0x82, 0x2b, 0xba, 0x25, - 0xe2, 0xec, 0xb7, 0xdd, 0x65, 0xad, 0x2d, 0xe9, 0x77, 0x6c, 0x36, 0xe7, 0x97, 0x07, 0x24, 0xe8, 0x03, 0x99, 0x36, - 0x20, 0x62, 0xe7, 0xed, 0x5e, 0xbc, 0xc7, 0x7b, 0x31, 0x70, 0xf5, 0x42, 0x91, 0x18, 0x9e, 0x55, 0xef, 0x2d, 0x7a, - 0x29, 0x4d, 0x62, 0xf2, 0x6a, 0xcb, 0xeb, 0xca, 0xe5, 0x6d, 0x6f, 0xc3, 0x02, 0x7b, 0x46, 0x57, 0x2e, 0xba, 0x96, - 0xa5, 0xc0, 0x09, 0x40, 0xf4, 0xb8, 0x4e, 0x72, 0x44, 0x71, 0x98, 0xcd, 0x86, 0x8c, 0x83, 0xb9, 0x6b, 0x47, 0xc5, - 0x31, 0xb1, 0xbb, 0x4c, 0xd8, 0x81, 0x95, 0x11, 0x95, 0xb7, 0x3a, 0xc2, 0x3b, 0x2c, 0xfa, 0x6b, 0xff, 0xf6, 0x47, - 0x8f, 0x6d, 0x77, 0x5c, 0x90, 0x20, 0xb5, 0xb1, 0x1e, 0x55, 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x63, 0xa9, 0xcc, 0xb7, - 0xb7, 0xcb, 0x7a, 0xa8, 0x56, 0x9d, 0xe0, 0x5a, 0x34, 0xe5, 0xa2, 0x99, 0x0d, 0xc2, 0x01, 0x89, 0x09, 0x14, 0x68, - 0x6e, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0xe4, 0x4f, 0x21, 0xf3, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xd2, 0x1f, 0x19, - 0x62, 0xd4, 0x93, 0x94, 0x15, 0x10, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x45, 0xb0, 0xf2, 0x67, 0x2a, 0x87, 0x46, 0x68, - 0x20, 0x51, 0x68, 0xa8, 0x25, 0x4a, 0xf9, 0xcc, 0xc3, 0x18, 0xa4, 0xfd, 0x93, 0x9a, 0xef, 0x2b, 0x57, 0x4a, 0x47, - 0x7e, 0x54, 0x0c, 0x03, 0xaa, 0x5f, 0x48, 0x0e, 0x36, 0x0d, 0xdf, 0x03, 0x19, 0x55, 0x86, 0x27, 0x31, 0xc2, 0xa7, - 0x71, 0xce, 0xc8, 0x52, 0xd8, 0x94, 0x30, 0x4b, 0xd5, 0x36, 0x52, 0xed, 0x22, 0xd3, 0x09, 0xe5, 0xc2, 0xfc, 0x53, - 0x23, 0x76, 0x91, 0x85, 0x2b, 0xad, 0x41, 0xfd, 0x78, 0x63, 0x02, 0x94, 0x5d, 0x5d, 0x65, 0xc2, 0xc6, 0x8d, 0x48, - 0xdf, 0xd0, 0x15, 0xd3, 0x81, 0x5a, 0x54, 0xe0, 0x44, 0xa4, 0xf1, 0x50, 0x0c, 0x85, 0x46, 0x38, 0xa4, 0x28, 0x72, - 0xe1, 0x1a, 0x87, 0xbe, 0x18, 0x68, 0xdb, 0x28, 0x0d, 0x9d, 0x04, 0x98, 0x80, 0x58, 0xbb, 0xa1, 0x4d, 0xa5, 0x83, - 0x34, 0x48, 0xa8, 0x14, 0xed, 0x1c, 0x58, 0x7f, 0x18, 0x49, 0x0c, 0x80, 0xfe, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, - 0x6e, 0x00, 0xcd, 0x75, 0x80, 0x3b, 0xe1, 0x0b, 0x05, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x84, 0xc8, 0xab, 0x35, - 0x29, 0x6b, 0xc4, 0x93, 0xcf, 0xd0, 0xe0, 0x53, 0xd6, 0xf5, 0x6b, 0xb9, 0x0e, 0x5d, 0xf0, 0x14, 0xb6, 0x55, 0x3d, - 0xbf, 0x0a, 0x39, 0x19, 0xd7, 0x20, 0x2b, 0x24, 0xd3, 0x5f, 0x31, 0x92, 0xf7, 0x5f, 0xf9, 0x55, 0x2d, 0x35, 0x86, - 0xb2, 0xf7, 0xeb, 0x9a, 0x61, 0x79, 0x39, 0xaf, 0xdc, 0x14, 0x04, 0xdc, 0x92, 0x25, 0xc1, 0x52, 0x4a, 0x08, 0xd0, - 0xb0, 0x3d, 0x92, 0x4a, 0x41, 0x51, 0x6a, 0xf7, 0xce, 0x53, 0xd0, 0x02, 0x8c, 0xa0, 0x96, 0x4a, 0xa6, 0x91, 0xc8, - 0x97, 0x42, 0x14, 0x88, 0xf2, 0x60, 0x04, 0x76, 0x6a, 0x33, 0xd2, 0x75, 0xe1, 0xfa, 0xf1, 0x0c, 0x53, 0x7b, 0x08, - 0xf4, 0xd8, 0xdb, 0x00, 0x55, 0xa2, 0x2e, 0xc3, 0x72, 0xa2, 0xd0, 0xac, 0x26, 0x59, 0x40, 0x8d, 0x69, 0x83, 0x94, - 0x6c, 0x83, 0x2e, 0x57, 0x80, 0x7e, 0x24, 0x8e, 0x67, 0xb5, 0x03, 0x42, 0xd6, 0xa0, 0x82, 0x21, 0x4f, 0xa9, 0x90, - 0xc2, 0xbc, 0xd7, 0xa5, 0x22, 0x3c, 0x9f, 0x03, 0x2e, 0xb5, 0xe0, 0xcc, 0xcb, 0x68, 0xe0, 0x83, 0xf8, 0x24, 0xc1, - 0xc4, 0x17, 0x5c, 0x15, 0xe8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x28, 0x15, 0x37, 0x29, 0x83, 0x6d, 0x45, 0xae, 0x0d, - 0x3f, 0x24, 0xcb, 0xd6, 0x5d, 0x1e, 0xea, 0x2e, 0x44, 0x02, 0xd8, 0xe9, 0x25, 0x7a, 0xbe, 0x65, 0xbd, 0x74, 0x18, - 0x9c, 0x69, 0x89, 0x83, 0xc0, 0x6f, 0x6f, 0x27, 0xc3, 0x32, 0x25, 0xb2, 0x6b, 0x92, 0xba, 0x80, 0x1c, 0x86, 0x6a, - 0xae, 0x1d, 0x98, 0xa5, 0xd2, 0xc7, 0xf3, 0x72, 0x86, 0xdb, 0xa5, 0x34, 0xe4, 0x66, 0xbc, 0x9a, 0xe6, 0x73, 0x2b, - 0xc9, 0xa6, 0xfd, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xd4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59, - 0xa7, 0xb8, 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x8c, 0xa6, 0x61, 0x23, 0x60, 0x62, 0x82, 0x8a, 0x5f, 0x37, 0x89, 0x98, - 0xce, 0x96, 0xe0, 0x3a, 0x42, 0xef, 0xa1, 0x9c, 0xe0, 0xae, 0xa6, 0xd9, 0xe7, 0xe1, 0xfc, 0x7a, 0xe2, 0xde, 0x37, - 0x88, 0xfb, 0xcb, 0x90, 0x1b, 0x84, 0x1e, 0xcb, 0x84, 0x1f, 0xe9, 0xfb, 0x28, 0x54, 0xd5, 0x93, 0xd3, 0xb0, 0x62, - 0x59, 0xe2, 0xc9, 0x08, 0x75, 0x18, 0x51, 0xd1, 0x1a, 0x23, 0xbb, 0xba, 0xca, 0xcd, 0xb3, 0x40, 0x4e, 0x53, 0x8f, - 0xd7, 0xfd, 0xb4, 0x15, 0x39, 0x1b, 0x9e, 0xc8, 0xfd, 0x57, 0x35, 0x4f, 0x64, 0x45, 0xe7, 0x38, 0xd2, 0x35, 0x81, - 0xdc, 0x27, 0xa7, 0xab, 0x87, 0x54, 0xc8, 0x16, 0xbd, 0x6c, 0xe3, 0x8c, 0xea, 0x80, 0xa4, 0x9e, 0x51, 0x81, 0x55, - 0x8d, 0xbd, 0xb5, 0xd5, 0x11, 0xe9, 0x96, 0x4a, 0xb0, 0xc1, 0xd6, 0xc2, 0x68, 0xc6, 0x28, 0xe8, 0x94, 0x14, 0x19, - 0xa8, 0x51, 0x7e, 0x0d, 0x63, 0xd8, 0xa7, 0x06, 0x20, 0x38, 0xd7, 0x57, 0x7f, 0x59, 0x4a, 0xb2, 0x10, 0x90, 0xb8, - 0x4b, 0x06, 0x6c, 0x4d, 0x10, 0x33, 0xd2, 0xc9, 0x7b, 0xa0, 0xbc, 0x01, 0x43, 0x1b, 0x01, 0xec, 0x02, 0x71, 0xe8, - 0x41, 0xc5, 0xb6, 0x09, 0x29, 0x3a, 0x36, 0xf0, 0x1c, 0x80, 0x9d, 0x57, 0xae, 0xd1, 0x77, 0x55, 0x0a, 0x18, 0x92, - 0x81, 0x1b, 0xb0, 0xca, 0x2d, 0xb7, 0xff, 0x1c, 0xcc, 0x06, 0x78, 0x7d, 0x26, 0x9b, 0x6f, 0x62, 0x9e, 0x60, 0x15, - 0xbb, 0xf0, 0x2b, 0xcd, 0x5a, 0xc4, 0x9d, 0x0e, 0x1b, 0xf5, 0x0a, 0x13, 0xa2, 0xf6, 0x00, 0x6b, 0xdf, 0xa3, 0x87, - 0x45, 0xbc, 0xbf, 0xc2, 0x77, 0x3d, 0x6e, 0xb9, 0xaf, 0x97, 0x45, 0x2b, 0x5d, 0x45, 0x8d, 0x81, 0xc9, 0xba, 0x9d, - 0x8c, 0x6b, 0x2f, 0x0f, 0x84, 0x2f, 0xb8, 0x5a, 0x23, 0xab, 0x5c, 0x8a, 0x8d, 0x45, 0xd2, 0xd3, 0x3e, 0x05, 0xd8, - 0x37, 0x9b, 0xbd, 0x00, 0x33, 0xef, 0x2b, 0x54, 0x49, 0x48, 0x69, 0x76, 0x83, 0x25, 0x09, 0x6d, 0x45, 0x46, 0x9d, - 0x0f, 0x1c, 0x6d, 0x73, 0x2b, 0x8e, 0x60, 0x38, 0x27, 0x61, 0x3a, 0x56, 0x1e, 0x36, 0x19, 0xb8, 0xf2, 0x8e, 0x98, - 0xb6, 0x09, 0xf0, 0x6f, 0x06, 0x7c, 0x7b, 0x25, 0xb9, 0xb6, 0xd0, 0x30, 0x3c, 0x41, 0x84, 0x55, 0x9e, 0x08, 0x34, - 0x14, 0x60, 0x8d, 0x6b, 0x2d, 0x0f, 0x50, 0xe1, 0x6b, 0x67, 0x13, 0x00, 0x12, 0x59, 0x41, 0xce, 0x8a, 0xa3, 0x1b, - 0x56, 0xb9, 0xde, 0x4f, 0x8d, 0x82, 0xc4, 0xc5, 0x83, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x35, 0x67, 0x52, 0xc4, 0xb4, - 0x13, 0x04, 0xfd, 0xa3, 0xcc, 0xc9, 0x69, 0x3a, 0xa1, 0x7d, 0xce, 0x9d, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x89, - 0x2d, 0x5e, 0xc7, 0x4d, 0x29, 0x17, 0x26, 0x39, 0xe6, 0xa6, 0x48, 0xc5, 0x66, 0x8a, 0xdd, 0xb9, 0xf5, 0x83, 0x16, - 0xd2, 0x41, 0xdb, 0x14, 0x39, 0xd8, 0xac, 0xe2, 0xf7, 0x04, 0xc6, 0x73, 0x81, 0xf8, 0xf2, 0x15, 0x25, 0xe9, 0x30, - 0xc7, 0x5c, 0x60, 0xf5, 0x62, 0x0a, 0xf2, 0x77, 0x8e, 0x4e, 0xb3, 0x37, 0xf0, 0x41, 0xe2, 0x0d, 0x38, 0x66, 0x8d, - 0x7d, 0xe7, 0x52, 0x51, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0x95, 0xfb, 0x46, 0xd0, 0xd5, 0x5b, 0x1d, - 0xce, 0x37, 0x9e, 0x1b, 0xbb, 0x11, 0xc4, 0x60, 0x2d, 0x14, 0x43, 0x4f, 0xb2, 0xf0, 0x1c, 0xb6, 0x67, 0x7b, 0xbb, - 0x57, 0xec, 0xf1, 0xca, 0x45, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0x9e, 0x89, 0x9a, 0x58, 0x44, 0x64, 0xcb, 0xd6, - 0x61, 0x81, 0x01, 0x00, 0x68, 0x69, 0x72, 0xaf, 0x9a, 0x08, 0x95, 0xf1, 0x5c, 0x5a, 0x4f, 0x15, 0x44, 0x55, 0x8d, - 0xdf, 0xae, 0xcf, 0x40, 0x21, 0xb8, 0x37, 0x3a, 0x1e, 0x06, 0x21, 0x60, 0x17, 0x05, 0x2f, 0xd0, 0x07, 0xb4, 0x57, - 0x25, 0x42, 0x31, 0x73, 0xb2, 0x1e, 0x33, 0x8c, 0x54, 0xd0, 0x85, 0x4a, 0xd8, 0x2a, 0xcd, 0xf0, 0xab, 0x83, 0xd0, - 0x8c, 0x32, 0xee, 0xbf, 0xaa, 0xd6, 0x0c, 0xf2, 0x83, 0x79, 0xab, 0x84, 0xfa, 0x76, 0x25, 0x22, 0x53, 0x81, 0x89, - 0x87, 0x59, 0x4a, 0xbf, 0x5f, 0xd6, 0x49, 0x3f, 0x2f, 0x97, 0xe7, 0x9c, 0x24, 0x5f, 0xe7, 0x0e, 0x92, 0x4f, 0xba, - 0xfb, 0x95, 0xf0, 0x43, 0x0d, 0xa3, 0x26, 0xfc, 0xea, 0x5b, 0x1a, 0xe6, 0x9e, 0x72, 0x6f, 0xf5, 0xbb, 0xc8, 0x74, - 0x51, 0x9e, 0x83, 0x22, 0xa4, 0x1f, 0xc1, 0x34, 0x34, 0x68, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x04, 0x71, 0x7d, 0xcc, - 0xa9, 0x76, 0x28, 0x63, 0x8c, 0x68, 0x5a, 0x52, 0x90, 0x24, 0x70, 0x50, 0x7e, 0x03, 0x03, 0x62, 0x12, 0x12, 0xd2, - 0x20, 0x74, 0xd6, 0x66, 0x22, 0x2a, 0x73, 0xf1, 0x76, 0xe5, 0xb2, 0x26, 0x50, 0x84, 0x9e, 0x60, 0xa6, 0x52, 0x2a, - 0x08, 0xa4, 0xca, 0xb7, 0xd1, 0xa9, 0x39, 0x43, 0x73, 0xd7, 0x14, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0xf2, - 0xa1, 0xaf, 0x13, 0x23, 0x5e, 0x66, 0xd0, 0x35, 0x1c, 0xfe, 0x1a, 0x2b, 0x29, 0x42, 0x26, 0x7c, 0xaf, 0x60, 0x13, - 0x21, 0x99, 0x82, 0x9e, 0x09, 0xf8, 0x43, 0xbd, 0xb2, 0x97, 0xee, 0xe5, 0x95, 0x49, 0x8b, 0xca, 0x56, 0xa2, 0x66, - 0x2d, 0x8e, 0xe2, 0xed, 0x14, 0xce, 0xb3, 0x47, 0x09, 0x04, 0x24, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x28, 0x1d, 0x02, - 0x48, 0x70, 0xfa, 0x09, 0x2c, 0xb4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0x90, 0x9a, 0x73, 0x92, 0x7c, - 0x73, 0x94, 0xda, 0xdb, 0x4a, 0x7b, 0xc6, 0xec, 0x00, 0xdb, 0x76, 0xb7, 0xf3, 0xa3, 0x74, 0xbb, 0x33, 0x34, 0x18, - 0x17, 0x86, 0xff, 0x93, 0x12, 0xd3, 0x40, 0x0a, 0x29, 0x1b, 0x3f, 0xa1, 0x0c, 0xc3, 0xff, 0x96, 0x24, 0x80, 0x07, - 0xb5, 0xdd, 0x58, 0x31, 0xee, 0x15, 0x45, 0xc9, 0x6d, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0xf4, 0x89, 0x62, - 0x9e, 0x13, 0x00, 0xa3, 0xc8, 0xfc, 0x1d, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f, - 0x29, 0xa4, 0xa2, 0xb2, 0x39, 0x89, 0xf8, 0x77, 0x05, 0x98, 0xe6, 0xc4, 0x47, 0x7a, 0xae, 0x61, 0x28, 0xc0, 0x57, - 0x3a, 0x94, 0x9a, 0xed, 0xe9, 0x1f, 0x9d, 0xed, 0xbe, 0x44, 0x8a, 0x20, 0x81, 0x06, 0x5e, 0xae, 0x59, 0x2f, 0xac, - 0x32, 0xb8, 0x23, 0xfe, 0x14, 0x7c, 0x5f, 0x5e, 0x07, 0x9f, 0x71, 0xfe, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x83, - 0xa6, 0x62, 0x25, 0xd8, 0x25, 0x0a, 0xcc, 0xca, 0xcf, 0x1f, 0xd7, 0x69, 0xdd, 0xd4, 0x2c, 0xd1, 0x29, 0x3f, 0x77, - 0x0d, 0x33, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, 0x7f, 0x0e, 0xb2, 0x9d, 0x50, 0xbb, 0xb5, 0x55, 0x6c, 0x90, 0x86, 0x86, - 0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xf1, 0x52, 0xa8, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x87, - 0x15, 0x82, 0xc5, 0x4e, 0x65, 0xf2, 0x19, 0x0e, 0x9a, 0x22, 0xd7, 0x42, 0x28, 0x7c, 0x39, 0x88, 0x4a, 0x49, 0x8b, - 0x75, 0xb4, 0x3d, 0x3b, 0x83, 0xe7, 0x97, 0x71, 0x01, 0xd8, 0x81, 0xe5, 0x57, 0x58, 0x16, 0x07, 0xc8, 0xc5, 0x43, - 0x59, 0xeb, 0x15, 0x8d, 0xc7, 0x37, 0x76, 0x61, 0x75, 0x01, 0x3e, 0x8d, 0xd2, 0x71, 0x22, 0x26, 0x31, 0x93, 0x2a, - 0xd7, 0xe4, 0xda, 0xe8, 0x5e, 0x5a, 0xa3, 0x79, 0x2e, 0x38, 0x78, 0x85, 0xe0, 0x06, 0xd3, 0x57, 0xf2, 0x72, 0xbd, - 0x82, 0x82, 0xa1, 0xf6, 0xe6, 0x26, 0x98, 0x2b, 0xf1, 0x98, 0xc1, 0x35, 0xfd, 0x3a, 0x9c, 0x8a, 0x6e, 0x5e, 0xae, - 0x18, 0xfc, 0x3a, 0x67, 0xac, 0x21, 0x00, 0x88, 0x4e, 0x1e, 0x5e, 0x6f, 0x26, 0xbd, 0x52, 0xd2, 0x41, 0x49, 0x84, - 0xf8, 0xae, 0xcc, 0xd7, 0x5d, 0x2a, 0xba, 0x72, 0xd5, 0xbd, 0xaf, 0x19, 0x33, 0x2e, 0x18, 0x3d, 0xe7, 0xb3, 0xa4, - 0x71, 0xed, 0x86, 0xee, 0xea, 0xfc, 0xe8, 0xfd, 0x20, 0xf3, 0x16, 0x66, 0x40, 0x26, 0x20, 0x0a, 0x9e, 0x7b, 0xaf, - 0x8d, 0x88, 0xf2, 0xb7, 0x66, 0x88, 0x57, 0x0e, 0xb3, 0x2e, 0x92, 0xfc, 0xed, 0xe0, 0xdb, 0xe0, 0xfa, 0x96, 0x46, - 0x04, 0xb9, 0xab, 0x22, 0xc8, 0x84, 0xb9, 0x99, 0x3e, 0x70, 0xfb, 0x77, 0x65, 0x08, 0x22, 0x2a, 0xa6, 0x43, 0xe5, - 0xb8, 0x7f, 0xb4, 0x41, 0xa5, 0x42, 0xe2, 0x53, 0x95, 0xbb, 0x72, 0x6d, 0x6a, 0xa8, 0xc7, 0x75, 0x32, 0x0b, 0x4d, - 0xb3, 0x26, 0x97, 0xb2, 0x69, 0x31, 0x32, 0x4d, 0x4e, 0xb5, 0xf9, 0xdd, 0x6b, 0x83, 0x74, 0x0c, 0xd5, 0xc5, 0x5a, - 0x2d, 0x98, 0xdf, 0x95, 0x17, 0xde, 0xf5, 0x62, 0x23, 0x95, 0xa1, 0xa6, 0x3d, 0x8a, 0x3e, 0x8e, 0xdb, 0xcc, 0xe5, - 0x51, 0xfa, 0x67, 0x0d, 0x00, 0xd3, 0x10, 0x16, 0xdd, 0x4d, 0xcb, 0xd8, 0x13, 0xcb, 0xd3, 0x13, 0x19, 0x28, 0x7a, - 0xae, 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x35, 0x08, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb8, 0x5b, 0xad, 0x5f, 0xcd, 0xf3, - 0x79, 0xca, 0x57, 0xf2, 0x7c, 0x6a, 0x1a, 0xdd, 0x46, 0xdb, 0xbd, 0x39, 0x35, 0x54, 0xcc, 0xb5, 0xbe, 0xc9, 0x1f, - 0x98, 0xae, 0x83, 0xae, 0x16, 0x81, 0x66, 0x75, 0xaa, 0x9e, 0x95, 0xe5, 0xac, 0x9e, 0xc9, 0x31, 0x13, 0xb6, 0xa9, - 0x34, 0x87, 0xe8, 0x86, 0xa9, 0x9a, 0xe9, 0xc7, 0xc6, 0xb1, 0x90, 0x6d, 0x9e, 0x5f, 0x8e, 0x73, 0xc0, 0xb4, 0x3c, - 0x5f, 0x26, 0x0c, 0x3f, 0x5e, 0x5d, 0xfd, 0x28, 0xf8, 0x54, 0xd5, 0xd1, 0x5b, 0xbe, 0xd4, 0x3d, 0x83, 0x59, 0xa9, - 0x8c, 0x88, 0x13, 0xb6, 0x7e, 0xf0, 0xe6, 0xe9, 0x15, 0xb0, 0x9c, 0xc0, 0xea, 0x4e, 0x98, 0xd3, 0x18, 0xaa, 0x3a, - 0xc0, 0x3f, 0xac, 0x1f, 0x6c, 0xdd, 0x19, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x63, 0x63, 0xe3, 0x18, 0xef, 0xd6, 0x12, - 0x41, 0x5e, 0x61, 0x40, 0x1f, 0xaf, 0x3e, 0x0a, 0x5c, 0xae, 0x63, 0xdb, 0x03, 0x87, 0xdc, 0xd6, 0xc0, 0xdf, 0x24, - 0x4f, 0x1a, 0x2d, 0x0a, 0x9e, 0xcd, 0xe4, 0x0c, 0x85, 0xbc, 0xe6, 0xe3, 0xa0, 0xee, 0x08, 0x7f, 0x03, 0xa7, 0x16, - 0x5e, 0x5e, 0x7e, 0x82, 0x3e, 0x60, 0xe9, 0x4a, 0x6e, 0x2a, 0xfc, 0x94, 0xf2, 0x88, 0xae, 0xd6, 0x79, 0x30, 0x52, - 0x5c, 0x4c, 0x51, 0xe8, 0xb8, 0xcb, 0x1b, 0x67, 0x23, 0xa3, 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab, - 0x96, 0xde, 0x2f, 0x3a, 0xba, 0x6d, 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x0a, 0xcc, 0xf8, 0x38, 0x11, 0x5e, 0x9f, 0x18, - 0xeb, 0xbb, 0x45, 0xa0, 0xba, 0x39, 0x36, 0xd9, 0xe1, 0x78, 0xbd, 0xd9, 0xac, 0x71, 0x07, 0x6f, 0x9c, 0x27, 0xce, - 0xb2, 0x44, 0x8f, 0xca, 0x52, 0xc3, 0x03, 0x52, 0x21, 0x6e, 0xde, 0x33, 0x81, 0x71, 0xd9, 0x25, 0x71, 0x6d, 0x37, - 0x10, 0x6b, 0xb1, 0x27, 0x31, 0x4b, 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x7b, 0xed, - 0xec, 0x3c, 0xb5, 0xbd, 0x6a, 0xaa, 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x14, 0x49, 0xf7, 0x1b, 0xe8, 0x22, 0x06, 0x8c, - 0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, 0x8e, 0xf8, 0x46, 0x13, 0xa9, 0x52, 0x1f, 0x51, 0xdf, 0x61, 0x58, 0xab, 0xab, - 0x0c, 0x24, 0x30, 0x8f, 0xbc, 0x3b, 0xae, 0xa5, 0xa7, 0x63, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0xba, - 0xbd, 0xe2, 0x01, 0xe8, 0x1f, 0xe8, 0xb7, 0x88, 0x85, 0xbf, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xe4, 0xe8, 0x6b, - 0x0f, 0x7e, 0x61, 0xd5, 0x9e, 0x81, 0x1a, 0x66, 0xc4, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0b, 0xc0, - 0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, 0x64, 0xb9, 0x2a, 0x86, 0xfc, 0x1b, 0xec, 0xb3, 0xde, 0x9c, 0x80, 0x99, 0x38, - 0xe5, 0x25, 0x26, 0xa6, 0x88, 0xcb, 0xcd, 0xd2, 0xe7, 0x69, 0xda, 0x2c, 0xda, 0xc0, 0x29, 0x8c, 0x04, 0x8e, 0xd8, - 0x37, 0xb6, 0xa1, 0x99, 0xb0, 0x11, 0x13, 0x6a, 0x54, 0x4a, 0x09, 0x1f, 0xc8, 0xad, 0x96, 0xf4, 0x65, 0x6e, 0xaf, - 0xbe, 0xdc, 0x26, 0x28, 0xa0, 0xa8, 0x81, 0xe5, 0xd0, 0x38, 0x6e, 0x19, 0xc8, 0x85, 0xc5, 0xb0, 0x30, 0x6a, 0x55, - 0xae, 0x26, 0xa3, 0x3a, 0x99, 0xaf, 0x16, 0x17, 0x2a, 0xf4, 0xe0, 0x91, 0x40, 0xce, 0x5f, 0x60, 0xea, 0x60, 0x56, - 0x6a, 0x33, 0x2d, 0x36, 0x51, 0xde, 0x33, 0x1d, 0x92, 0xeb, 0xaf, 0xe1, 0xa1, 0xf2, 0x8b, 0x57, 0xe6, 0x14, 0xf3, - 0x45, 0x1e, 0x4b, 0x5b, 0x63, 0x6e, 0xfd, 0xaf, 0xf2, 0x3e, 0xad, 0x04, 0xec, 0x37, 0x60, 0x53, 0xc6, 0x5a, 0x62, - 0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xf4, 0xbe, 0x86, 0xf0, 0x5d, 0x51, 0xe9, 0x2a, 0x91, 0x75, 0x8d, 0x56, 0xf7, - 0xeb, 0x82, 0xe5, 0x97, 0x07, 0x0c, 0x73, 0x93, 0x51, 0x21, 0x5b, 0x51, 0xb3, 0x29, 0xbf, 0xda, 0xbb, 0xf1, 0x2b, - 0x0f, 0x25, 0x05, 0xd5, 0x2a, 0xd9, 0xbc, 0x72, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x8c, 0x7b, 0x14, 0x57, 0xae, 0x50, - 0xad, 0xf3, 0xdf, 0x57, 0xdd, 0x4f, 0x74, 0xd6, 0x86, 0xfa, 0xd4, 0x0d, 0xd7, 0xa6, 0xa7, 0xdf, 0xb0, 0x54, 0x23, - 0x4b, 0xe8, 0xa6, 0xa5, 0x62, 0x32, 0x12, 0x25, 0xa6, 0xab, 0x94, 0x47, 0x7d, 0x8d, 0xb8, 0x00, 0x76, 0x43, 0xf9, - 0x8b, 0x7f, 0x0d, 0xcf, 0x8f, 0x03, 0x54, 0xa2, 0x96, 0x93, 0x2c, 0xe5, 0xad, 0x49, 0x34, 0x8b, 0x93, 0xcb, 0x60, - 0x11, 0xb7, 0x66, 0x59, 0x9a, 0x15, 0x73, 0xa0, 0x4a, 0xaf, 0xb8, 0x04, 0x1d, 0x7e, 0xd6, 0x5a, 0xc4, 0xde, 0x73, - 0x96, 0x9c, 0x31, 0x1e, 0x8f, 0x22, 0xcf, 0xde, 0xcf, 0x81, 0x3d, 0x58, 0xaf, 0xa3, 0x3c, 0xcf, 0xce, 0x6d, 0xef, - 0x5d, 0x76, 0x02, 0x44, 0xeb, 0xbd, 0xb9, 0xb8, 0x3c, 0x65, 0xa9, 0xf7, 0xfe, 0x64, 0x91, 0xf2, 0x85, 0x57, 0x44, - 0x69, 0xd1, 0x2a, 0x58, 0x1e, 0x4f, 0x40, 0x4c, 0x24, 0x59, 0xde, 0xc2, 0xfc, 0xe7, 0x19, 0x0b, 0x92, 0xf8, 0x74, - 0xca, 0xad, 0x71, 0x94, 0x7f, 0xea, 0xb5, 0x5a, 0xf3, 0x3c, 0x9e, 0x45, 0xf9, 0x65, 0x8b, 0x5a, 0x04, 0x9f, 0xb5, - 0x77, 0xa3, 0x2f, 0x26, 0xf7, 0x7b, 0x3c, 0x87, 0xbe, 0x31, 0x62, 0x31, 0x00, 0xe6, 0x63, 0xed, 0x3e, 0x68, 0xcf, - 0x8a, 0x0d, 0x11, 0x51, 0x8a, 0x52, 0x5e, 0x1e, 0x7b, 0x1f, 0x41, 0xb7, 0x3d, 0xf6, 0x4f, 0x78, 0xea, 0x81, 0x2d, - 0xc7, 0xb3, 0x74, 0x39, 0x5a, 0xe4, 0x05, 0x0c, 0x30, 0xcf, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x92, 0xe5, 0x80, 0xb6, - 0x56, 0x1e, 0x8d, 0xe3, 0x45, 0x11, 0xdc, 0x9f, 0x5f, 0xf4, 0x50, 0x57, 0x38, 0xcd, 0xb3, 0x45, 0x3a, 0x96, 0x73, - 0xc5, 0x29, 0x1c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc5, 0xce, 0xa8, - 0xe8, 0xb7, 0xc7, 0xec, 0xd4, 0xcb, 0x4f, 0x4f, 0x22, 0xa7, 0xd3, 0x7d, 0xe4, 0xa9, 0x7f, 0xfe, 0x03, 0x17, 0x14, - 0xf7, 0xb5, 0xc5, 0x9d, 0x76, 0xfb, 0x1f, 0xdc, 0x5e, 0x63, 0x16, 0x02, 0x28, 0xe8, 0xcc, 0x2f, 0xac, 0x22, 0x4b, - 0x60, 0x7f, 0xd6, 0xf5, 0xec, 0xcd, 0xc1, 0x6e, 0x8a, 0xd3, 0xd3, 0xa0, 0x3b, 0xbf, 0x28, 0x71, 0x75, 0x81, 0x48, - 0xc8, 0x94, 0x8b, 0x94, 0x6f, 0xcb, 0x3f, 0x0a, 0xf1, 0xe3, 0xf5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd6, 0x5b, 0x63, - 0x38, 0x07, 0x84, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x82, 0x11, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d, - 0x0f, 0xc6, 0xe3, 0xdb, 0xc0, 0xc8, 0xd3, 0xf1, 0xb2, 0xbe, 0xaf, 0x1d, 0x30, 0x4e, 0x7b, 0x53, 0x86, 0xf4, 0x14, - 0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf3, 0xa9, 0x78, 0x24, 0x72, 0x3e, 0x17, 0x75, 0x0f, 0xda, 0x6d, 0xf1, 0x5e, 0x80, - 0x40, 0x0b, 0x3a, 0x3e, 0x36, 0x00, 0x22, 0x7a, 0x71, 0xdd, 0x47, 0x6c, 0x3e, 0xdc, 0xfa, 0xa5, 0x1a, 0xef, 0x52, - 0xe5, 0x0d, 0x0a, 0x11, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0x45, 0xbf, 0xa3, 0x03, 0x55, 0x83, 0x0f, 0x8c, 0xa4, - 0x5e, 0xc0, 0x3d, 0x33, 0x17, 0xa8, 0x97, 0xf6, 0xd1, 0x25, 0xd5, 0x6a, 0xb9, 0x20, 0x37, 0x18, 0xba, 0x90, 0x28, - 0x20, 0xe8, 0x14, 0x83, 0x9c, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xee, 0x64, 0x2e, 0x1c, 0xf9, 0x4c, 0xf3, 0xf5, 0x62, - 0x6b, 0x0b, 0xac, 0xec, 0x17, 0x4c, 0x36, 0x00, 0xee, 0x4d, 0xae, 0xae, 0xef, 0x43, 0x61, 0x4a, 0x29, 0x43, 0x6a, - 0x76, 0xd3, 0x15, 0x7d, 0xd8, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x86, 0xff, 0x0e, 0xc5, 0xde, 0xd1, 0x86, 0x65, 0x91, - 0x2d, 0xf2, 0x11, 0x79, 0xea, 0x56, 0x2d, 0x7e, 0x9b, 0x04, 0xae, 0xed, 0x31, 0xcd, 0xe7, 0xd1, 0x0c, 0xae, 0x7d, - 0xe4, 0x80, 0x53, 0x10, 0x44, 0xdc, 0x31, 0x90, 0x5e, 0x0e, 0x05, 0x21, 0x8a, 0xae, 0x31, 0xe5, 0xbb, 0xd1, 0xfd, - 0x4b, 0x7f, 0x91, 0xc6, 0xc0, 0xe9, 0x3e, 0xc6, 0x63, 0xba, 0x77, 0x12, 0x8f, 0x29, 0x10, 0xd1, 0xa2, 0xc4, 0x23, - 0xf4, 0x6c, 0x43, 0x81, 0xfa, 0x0e, 0x0b, 0x3c, 0xcb, 0x44, 0x16, 0xbb, 0x65, 0x63, 0x30, 0xc1, 0x10, 0x95, 0xe3, - 0x6c, 0x16, 0xc5, 0x69, 0x80, 0xdf, 0x07, 0xf1, 0xf4, 0x88, 0x01, 0x76, 0xf1, 0xe0, 0x27, 0x93, 0xb9, 0x68, 0x1d, - 0xd7, 0xff, 0x05, 0xf8, 0x08, 0xf5, 0x2f, 0xa5, 0x1d, 0xa6, 0xe1, 0x52, 0x61, 0xde, 0x7a, 0x29, 0xf0, 0x1e, 0xae, - 0x74, 0x56, 0x46, 0x7e, 0x8e, 0x3d, 0x4e, 0x3f, 0x06, 0xad, 0x4e, 0xd0, 0xd1, 0xa6, 0x6b, 0xed, 0x36, 0xaa, 0xc8, - 0x65, 0x91, 0x37, 0x1a, 0x09, 0x06, 0xfd, 0x2c, 0xe0, 0xac, 0xde, 0x35, 0xac, 0x9e, 0xa4, 0x4b, 0x74, 0xe0, 0x9c, - 0xa6, 0x4e, 0x0d, 0x08, 0x8a, 0x05, 0x5c, 0x33, 0x95, 0x5b, 0x46, 0x24, 0x94, 0xbe, 0xa4, 0x03, 0x5c, 0xbf, 0x4b, - 0x84, 0xf7, 0x86, 0xea, 0x29, 0x50, 0x8a, 0xe4, 0x16, 0xc7, 0x7b, 0xe2, 0xc4, 0x5b, 0x44, 0x63, 0xa1, 0x0d, 0x47, - 0xd0, 0xb6, 0xfe, 0x32, 0x02, 0x2c, 0x7d, 0x0a, 0xed, 0xcd, 0xa5, 0xa3, 0x12, 0xeb, 0x73, 0x98, 0x6b, 0x5f, 0x48, - 0x3d, 0xba, 0x91, 0x6f, 0xf7, 0x37, 0x97, 0xbc, 0xdc, 0xdb, 0x11, 0xbd, 0xfb, 0xc7, 0x65, 0x41, 0x02, 0xca, 0x74, - 0xa4, 0x55, 0x53, 0x88, 0x3a, 0x18, 0x96, 0xd2, 0x77, 0x71, 0xdc, 0x42, 0x2b, 0x5d, 0xc2, 0x63, 0x2c, 0xc9, 0x2e, - 0xc7, 0x74, 0xa5, 0x28, 0x87, 0x33, 0xa9, 0x13, 0x52, 0x72, 0x91, 0x83, 0xd1, 0x5b, 0x85, 0xe2, 0x18, 0x21, 0x18, - 0x6c, 0x2e, 0xe3, 0x32, 0xdc, 0x5c, 0x66, 0xe5, 0x31, 0x68, 0x26, 0x08, 0x55, 0xa1, 0x3e, 0xef, 0x02, 0x13, 0x0b, - 0x27, 0x8b, 0x45, 0x23, 0xe0, 0xb4, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x06, 0x2c, 0x40, 0x2c, 0x00, 0xdd, 0x8d, 0x7a, - 0x31, 0x58, 0x8b, 0x68, 0xdd, 0x87, 0x81, 0xf6, 0x76, 0x44, 0x23, 0x58, 0x57, 0x8e, 0x20, 0x57, 0xcb, 0xc2, 0x74, - 0x1c, 0x73, 0x69, 0x49, 0x74, 0xc2, 0x12, 0xe8, 0x9f, 0x5f, 0x5d, 0xb5, 0xa1, 0x9b, 0x78, 0xb5, 0xf6, 0xe2, 0x74, - 0xbe, 0x90, 0xdf, 0xd4, 0x82, 0x59, 0x3a, 0x18, 0xe6, 0xc4, 0x94, 0xff, 0x81, 0x8a, 0xdb, 0x05, 0x36, 0x8d, 0x6b, - 0x03, 0x3c, 0x14, 0x32, 0x40, 0x50, 0x2a, 0x1a, 0x80, 0xd2, 0x78, 0xbc, 0x5a, 0xa6, 0x97, 0x51, 0xc0, 0x0b, 0x9c, - 0xc1, 0x39, 0x3e, 0xa7, 0xf0, 0x3c, 0x8b, 0x53, 0x7c, 0xcc, 0xf1, 0x31, 0xba, 0xc0, 0xc7, 0xac, 0xb4, 0xff, 0x2e, - 0xe8, 0xb6, 0x34, 0x02, 0xb2, 0xab, 0x2b, 0x60, 0xee, 0x1a, 0x05, 0x40, 0x10, 0xe2, 0xdb, 0x2a, 0xcc, 0xc4, 0x16, - 0x2b, 0xe6, 0x2d, 0x51, 0x6e, 0x91, 0xf0, 0x0c, 0xc1, 0xb6, 0xca, 0x9d, 0x86, 0x8e, 0xe0, 0xc9, 0x2c, 0x92, 0x27, - 0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xf8, 0x85, 0x40, 0x07, 0x3d, 0xe2, 0xda, 0x74, 0x19, 0x97, 0x9f, 0xb5, 0x89, 0x43, - 0x1b, 0x67, 0x01, 0x35, 0x0d, 0x99, 0x3d, 0x8f, 0xe2, 0x44, 0x34, 0x5e, 0xb3, 0x92, 0x46, 0x3a, 0x20, 0x2d, 0x64, - 0x6f, 0xa7, 0x82, 0x0d, 0x80, 0x1f, 0x89, 0xcb, 0xd4, 0x15, 0xf4, 0xb6, 0xa8, 0xa2, 0x28, 0xb9, 0x3c, 0xbc, 0x03, - 0xe1, 0x0f, 0xd7, 0xeb, 0x1c, 0x82, 0x5d, 0x17, 0xa5, 0xf5, 0x16, 0x00, 0xf1, 0x9c, 0xb1, 0xb1, 0x67, 0x5b, 0xc0, - 0x26, 0xc5, 0xf3, 0xc7, 0x84, 0x9d, 0x31, 0xf9, 0x11, 0x14, 0xdd, 0x57, 0x57, 0x8e, 0x40, 0xda, 0x72, 0x79, 0x3f, - 0x53, 0x52, 0x9e, 0x5a, 0x97, 0x5c, 0x7d, 0x1d, 0x78, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x75, - 0x40, 0x91, 0xb5, 0x01, 0x4c, 0xd2, 0xcf, 0x6e, 0x5a, 0x0a, 0xf4, 0x63, 0x93, 0x09, 0x1c, 0x00, 0x15, 0xb7, 0xd0, - 0xa7, 0x5b, 0x00, 0x03, 0x66, 0xa6, 0x67, 0x8b, 0x16, 0x76, 0xd5, 0x56, 0x3f, 0x21, 0x2a, 0x92, 0x6c, 0xf4, 0xa9, - 0x36, 0xc5, 0x02, 0x09, 0x08, 0xc7, 0x6a, 0xf0, 0x29, 0xfb, 0xdf, 0xfe, 0xf5, 0x7f, 0xfe, 0x57, 0x18, 0x8e, 0x3a, - 0xb8, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x01, 0xad, 0x16, 0xe9, 0x2d, 0xed, 0xfe, 0xf6, 0xcf, 0xff, 0x0d, 0x9a, 0xd1, - 0xcd, 0x1a, 0xb7, 0x3c, 0x0e, 0xec, 0x11, 0x6a, 0x32, 0x77, 0x03, 0xa4, 0xd6, 0xf5, 0xda, 0xf1, 0xff, 0x05, 0x81, - 0x2d, 0x78, 0x36, 0xbf, 0x11, 0x08, 0x84, 0x75, 0x94, 0x64, 0x05, 0x13, 0x50, 0x08, 0x36, 0x79, 0x47, 0x30, 0x68, - 0x86, 0x39, 0x90, 0x6c, 0x61, 0x89, 0xde, 0x02, 0xfb, 0xb5, 0xde, 0x8d, 0x5d, 0x29, 0x18, 0x27, 0xd0, 0xc9, 0x03, - 0x00, 0xfb, 0x20, 0x9e, 0xe0, 0x81, 0x4e, 0x33, 0x6c, 0xbb, 0xce, 0x17, 0x68, 0x0c, 0xa1, 0x89, 0x4c, 0x8c, 0x20, - 0x5c, 0x1d, 0xaa, 0x1f, 0xfc, 0x04, 0xd6, 0xf2, 0x51, 0x3f, 0x47, 0x17, 0xfa, 0x19, 0xd9, 0x0f, 0x0c, 0x0b, 0x82, - 0x62, 0x86, 0x3a, 0x40, 0x73, 0x61, 0xea, 0xa4, 0x56, 0xfc, 0x81, 0xa9, 0xe4, 0xb0, 0x8f, 0x98, 0x0f, 0x89, 0xb7, - 0x5f, 0x16, 0x39, 0xab, 0x38, 0x26, 0x36, 0x10, 0xac, 0xc8, 0xac, 0xff, 0x98, 0x64, 0xe7, 0xe5, 0x75, 0x75, 0x53, - 0xa0, 0xe2, 0x72, 0x6f, 0x1c, 0x9f, 0xf5, 0x25, 0x22, 0x1b, 0x6b, 0x59, 0xed, 0xd2, 0x5c, 0x18, 0x56, 0xc9, 0x75, - 0xc9, 0x47, 0x5c, 0x96, 0xd7, 0x46, 0x01, 0x80, 0xe3, 0xee, 0x9d, 0xe4, 0x7d, 0xb9, 0x80, 0x57, 0x78, 0x61, 0x8b, - 0x20, 0x41, 0x3e, 0x2e, 0x64, 0x0c, 0x27, 0x19, 0x63, 0xb2, 0x7a, 0xd4, 0x5a, 0x33, 0xc5, 0xd2, 0xb1, 0x61, 0x8d, - 0x0b, 0x73, 0xc9, 0x85, 0x63, 0xa9, 0x0e, 0x49, 0x2e, 0x8c, 0x1f, 0xe0, 0x68, 0x70, 0xe1, 0xf8, 0x5a, 0x2e, 0x8c, - 0x6b, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x8d, 0xb6, 0xb8, 0x21, 0x15, 0x38, 0x0a, 0x37, 0x30, 0xc0, 0x46, 0x9f, 0xa4, - 0x6c, 0x23, 0x50, 0xd1, 0x18, 0x95, 0xd2, 0x9a, 0x04, 0x9b, 0x64, 0xcf, 0xc1, 0xe2, 0x18, 0x64, 0x9b, 0x39, 0x32, - 0x58, 0xc2, 0x13, 0x86, 0xc7, 0xff, 0x78, 0x07, 0xfb, 0x8a, 0xcd, 0x2c, 0xe9, 0x19, 0xa4, 0xcf, 0x0e, 0x0d, 0xe0, - 0x2d, 0x85, 0x3b, 0x23, 0xb0, 0xdf, 0xbe, 0x39, 0x38, 0xb4, 0xbd, 0x93, 0x6c, 0x7c, 0x19, 0xd8, 0xa0, 0x8a, 0x82, - 0x24, 0x73, 0x7d, 0x3e, 0x65, 0xa9, 0xa3, 0x94, 0xc1, 0x2c, 0x01, 0x65, 0x38, 0x3b, 0x15, 0xb7, 0xef, 0x9b, 0xae, - 0x58, 0x40, 0x1b, 0x7d, 0x9e, 0xaf, 0xbf, 0xc7, 0xc5, 0x97, 0x2b, 0x79, 0x8e, 0x8f, 0x7d, 0x0c, 0x46, 0xef, 0xed, - 0xc0, 0x03, 0xbe, 0x1c, 0x20, 0x05, 0xe9, 0x37, 0x01, 0x67, 0x21, 0xde, 0x77, 0xb0, 0xfd, 0x8e, 0xea, 0x8b, 0x50, - 0x28, 0x1a, 0xd0, 0xfa, 0x5a, 0xa5, 0x04, 0xd0, 0xd8, 0x63, 0x22, 0x41, 0xdc, 0x18, 0xc0, 0x01, 0x1f, 0xeb, 0x12, - 0x41, 0xa6, 0x46, 0x11, 0x8d, 0x52, 0xb1, 0x7f, 0x59, 0x85, 0x13, 0x12, 0xfa, 0xc4, 0x64, 0xf0, 0x93, 0xc0, 0x3f, - 0x36, 0xbf, 0x34, 0x25, 0x3e, 0x0a, 0xa3, 0x17, 0x79, 0xf4, 0x57, 0xb0, 0x61, 0xbd, 0xf3, 0x63, 0x6a, 0xa9, 0xcc, - 0x1a, 0xb4, 0xb7, 0xd1, 0xfc, 0x6b, 0x2b, 0xfb, 0x15, 0x24, 0x5e, 0x12, 0xcd, 0x0b, 0x16, 0xa8, 0x07, 0x69, 0xe1, - 0xa0, 0xa1, 0xb4, 0x6a, 0x52, 0x9a, 0x92, 0xb1, 0xe4, 0xd3, 0xa5, 0x69, 0x02, 0x3d, 0x04, 0x13, 0x08, 0xd3, 0xb7, - 0x5b, 0x11, 0xb0, 0xf7, 0x34, 0x48, 0xd8, 0x84, 0x97, 0x1c, 0xef, 0x07, 0x2f, 0x95, 0xcd, 0xe9, 0x77, 0x1f, 0x80, - 0x59, 0x64, 0xf9, 0xf8, 0xff, 0x6d, 0x63, 0x8f, 0x83, 0x14, 0xcc, 0x18, 0xba, 0x30, 0x80, 0x97, 0xb1, 0x00, 0x22, - 0xf3, 0x7d, 0x69, 0x4c, 0x34, 0x62, 0x68, 0x8f, 0x97, 0x3c, 0xb7, 0xf8, 0xd4, 0xe3, 0xb9, 0xd9, 0x0e, 0x34, 0xa5, - 0x15, 0xa3, 0x7c, 0xd5, 0x2c, 0xdc, 0x75, 0xa5, 0xf2, 0xb8, 0xda, 0x58, 0xd9, 0xd6, 0xf5, 0xb7, 0x15, 0x0c, 0x19, - 0x5e, 0x80, 0x52, 0x70, 0xbe, 0xa5, 0xe8, 0x61, 0xae, 0x69, 0xd5, 0x3f, 0x70, 0xab, 0xee, 0x51, 0xd2, 0xd9, 0x3e, - 0xa2, 0xb3, 0x4d, 0xcc, 0x65, 0xb8, 0x14, 0x73, 0x8f, 0xa2, 0x64, 0xe4, 0x20, 0x00, 0x56, 0xcb, 0xba, 0x0f, 0xd8, - 0x04, 0x2e, 0x3d, 0x2c, 0xcb, 0xde, 0x25, 0x73, 0x8e, 0x7e, 0x93, 0x79, 0xe4, 0xe2, 0xfa, 0xa0, 0xfe, 0x04, 0x5b, - 0xbb, 0x74, 0x87, 0xde, 0xf7, 0xc6, 0x77, 0xad, 0x6c, 0x45, 0xa9, 0xb6, 0x07, 0xf8, 0xfd, 0x3e, 0xc4, 0xbe, 0xaf, - 0x1c, 0x1b, 0xb5, 0x10, 0xaa, 0xb9, 0x6c, 0x11, 0xe1, 0xd8, 0xd8, 0x4d, 0x78, 0x41, 0xbf, 0xba, 0xce, 0x98, 0xfd, - 0xee, 0x76, 0x63, 0x96, 0xdd, 0xd1, 0x98, 0xfd, 0xee, 0x4f, 0x36, 0x66, 0xbf, 0x6a, 0x1a, 0xb3, 0xbf, 0xfe, 0x1e, - 0x63, 0x36, 0xcf, 0xce, 0x8b, 0xb0, 0x23, 0x83, 0xa7, 0xc0, 0x4c, 0xfe, 0x3e, 0x56, 0x2d, 0x4c, 0xd4, 0xb0, 0x69, - 0xc9, 0x88, 0x15, 0xf9, 0x5e, 0xc0, 0xab, 0xa5, 0x09, 0xd9, 0xd6, 0x89, 0x55, 0xad, 0xfb, 0xea, 0x26, 0x09, 0xe8, - 0xf5, 0xae, 0xbe, 0x03, 0xd5, 0x55, 0x46, 0x66, 0x40, 0x9f, 0x82, 0xd4, 0x1d, 0xbb, 0xdb, 0x2a, 0xa3, 0xc7, 0x1c, - 0xa1, 0xa7, 0x1c, 0xb5, 0x82, 0x7c, 0x96, 0xf6, 0x7f, 0x3a, 0xea, 0xf4, 0x76, 0x3b, 0x33, 0xe8, 0x0d, 0x72, 0x0b, - 0xde, 0xda, 0xbd, 0xdd, 0x5d, 0x7c, 0x3b, 0x57, 0x6f, 0x5d, 0x7c, 0x8b, 0xd5, 0xdb, 0x03, 0x7c, 0x1b, 0xa9, 0xb7, - 0x87, 0xf8, 0x36, 0x56, 0x6f, 0x8f, 0xf0, 0xed, 0xcc, 0x2e, 0x8f, 0xb8, 0x06, 0xee, 0x11, 0xd0, 0x15, 0x29, 0x89, - 0x81, 0x2a, 0x83, 0xd3, 0x88, 0x37, 0xb0, 0xa2, 0xd3, 0x20, 0xf6, 0x84, 0x02, 0x1d, 0x14, 0xde, 0x39, 0xb0, 0xf4, - 0x80, 0x12, 0x8e, 0x9e, 0xe2, 0x55, 0x7c, 0xd0, 0x3d, 0x0f, 0xe3, 0x19, 0x53, 0xdf, 0x24, 0x55, 0xab, 0x06, 0x35, - 0x05, 0xec, 0xed, 0xb2, 0xa7, 0xf7, 0x49, 0xd8, 0xd0, 0x2a, 0x77, 0x82, 0x76, 0xae, 0xaa, 0x13, 0xd3, 0xb5, 0xf4, - 0x0e, 0x5f, 0x23, 0x20, 0x40, 0x00, 0x2b, 0xa3, 0x74, 0x02, 0x6a, 0x40, 0xeb, 0x02, 0x94, 0xf4, 0xb5, 0x42, 0x03, - 0x21, 0xd2, 0x62, 0x82, 0xd6, 0xa4, 0xdf, 0x0e, 0xa3, 0x53, 0xfd, 0xfc, 0x0a, 0xf4, 0xa9, 0xe8, 0x94, 0xdd, 0x26, - 0x40, 0x08, 0x44, 0x53, 0x78, 0x28, 0x20, 0x48, 0x0b, 0x81, 0xad, 0x41, 0x63, 0x41, 0x0a, 0x0f, 0xc4, 0x4e, 0x5d, - 0x9c, 0xd0, 0xf4, 0xf5, 0x22, 0xc0, 0x68, 0x55, 0xb0, 0x07, 0x6a, 0x1d, 0x95, 0x0a, 0x0c, 0x43, 0x05, 0x16, 0xdc, - 0x28, 0x63, 0x84, 0x2a, 0x72, 0x93, 0xa4, 0xb1, 0x94, 0x90, 0x31, 0x1d, 0xbc, 0xda, 0xbb, 0xbb, 0xca, 0xf7, 0x3e, - 0xeb, 0x8c, 0xf0, 0x8f, 0xe4, 0xaa, 0x9f, 0x4d, 0x26, 0x93, 0x1b, 0x85, 0xce, 0x67, 0xe3, 0x09, 0xeb, 0xb2, 0x07, - 0x3d, 0x74, 0xfe, 0xb5, 0xa4, 0x2f, 0xae, 0x53, 0x12, 0xee, 0x96, 0x77, 0x6b, 0x8c, 0xce, 0x38, 0x90, 0x43, 0x77, - 0x97, 0x4e, 0x25, 0x60, 0x65, 0x09, 0x5c, 0xf9, 0x34, 0x4e, 0x83, 0x76, 0xe9, 0x9f, 0x49, 0x76, 0xfe, 0xd9, 0xe3, - 0xc7, 0x8f, 0x4b, 0x7f, 0xac, 0xde, 0xda, 0xe3, 0x71, 0xe9, 0x8f, 0x96, 0x7a, 0x19, 0xed, 0xf6, 0x64, 0x52, 0xfa, - 0xb1, 0x2a, 0xd8, 0xed, 0x8e, 0xc6, 0xbb, 0xdd, 0xd2, 0x3f, 0x37, 0x5a, 0x94, 0x3e, 0x93, 0x6f, 0x39, 0x1b, 0xd7, - 0x3c, 0x88, 0x8f, 0xc0, 0x78, 0xf5, 0x05, 0xa1, 0x2d, 0xd1, 0x64, 0x10, 0x8f, 0x41, 0xb4, 0xe0, 0x60, 0xeb, 0x02, - 0x6f, 0x67, 0xc0, 0x9f, 0x27, 0x92, 0xb7, 0x8b, 0x4f, 0x7e, 0x22, 0x47, 0xff, 0xd5, 0xe4, 0xe8, 0x48, 0xcc, 0xc4, - 0xcd, 0x19, 0xc9, 0x81, 0x66, 0x35, 0x52, 0x16, 0x55, 0xff, 0x1a, 0xb2, 0x8a, 0xd9, 0x23, 0xb7, 0xc1, 0x96, 0x82, - 0xc7, 0x7f, 0x7d, 0x1d, 0x8f, 0xff, 0xe6, 0x76, 0x1e, 0x7f, 0x72, 0x37, 0x16, 0xff, 0xcd, 0x9f, 0xcc, 0xe2, 0xbf, - 0x6e, 0xb2, 0xf8, 0xcd, 0x3b, 0xb1, 0xf8, 0x35, 0x89, 0x1f, 0xa4, 0x9a, 0xbe, 0x49, 0x43, 0xfb, 0x0d, 0xd8, 0x30, - 0x46, 0xc9, 0x64, 0x02, 0x45, 0x93, 0x89, 0xad, 0x92, 0x1d, 0x81, 0x13, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5, - 0xd5, 0x57, 0x66, 0x99, 0x59, 0x20, 0xfd, 0x0d, 0xa6, 0x7c, 0x57, 0x35, 0x52, 0x65, 0x56, 0x9f, 0x06, 0x19, 0xc7, - 0x05, 0x9e, 0x26, 0x2c, 0x28, 0x79, 0x76, 0x7a, 0x9a, 0x30, 0xfd, 0xed, 0x33, 0xd5, 0xd2, 0x7c, 0x33, 0xe7, 0x33, - 0xcb, 0x07, 0x26, 0xb4, 0x41, 0x0d, 0xd0, 0x9e, 0x70, 0x64, 0xd2, 0xe7, 0xa0, 0x45, 0xd8, 0xfa, 0x4c, 0x7e, 0x37, - 0x98, 0xfc, 0xa9, 0x4b, 0xc9, 0x7e, 0x65, 0x40, 0xb3, 0xea, 0x8a, 0x2e, 0x4c, 0x91, 0x02, 0x32, 0x2e, 0x95, 0xdb, - 0x12, 0xa0, 0x9d, 0xe3, 0x47, 0x4e, 0x74, 0xca, 0xd2, 0xca, 0x37, 0x85, 0x34, 0x9b, 0xc0, 0x8f, 0x1e, 0x88, 0x29, - 0xc4, 0x67, 0x02, 0xf5, 0xb8, 0x22, 0x0e, 0xe8, 0xd4, 0xd6, 0x68, 0xac, 0x2a, 0x0c, 0xcd, 0xa5, 0xa8, 0x9c, 0x93, - 0xd5, 0x79, 0xd6, 0x8a, 0xe6, 0xeb, 0x85, 0xf2, 0xdd, 0xa6, 0xbb, 0x45, 0x34, 0x14, 0xe7, 0x76, 0x5f, 0xdb, 0x98, - 0x35, 0x9a, 0x29, 0xeb, 0x5e, 0x38, 0x9a, 0xe8, 0x24, 0xbb, 0xa8, 0xdb, 0x48, 0x26, 0x0c, 0x68, 0x3e, 0xe9, 0xbd, - 0x57, 0x75, 0xaa, 0xa0, 0x34, 0xbd, 0xa2, 0x22, 0xd3, 0x8b, 0x48, 0x83, 0x7c, 0x60, 0xb0, 0x03, 0xa9, 0x60, 0xca, - 0x30, 0x0f, 0x71, 0x17, 0x6d, 0x47, 0xa0, 0x32, 0x6d, 0x2b, 0x60, 0x51, 0x3a, 0xe4, 0xe8, 0x6b, 0xc2, 0x0e, 0x7d, - 0xab, 0x06, 0x70, 0xaa, 0x6d, 0xb3, 0xdb, 0x19, 0x3e, 0x98, 0x16, 0xe7, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb, - 0xf3, 0x25, 0xb0, 0xb0, 0x93, 0x4f, 0x31, 0x07, 0x85, 0x71, 0xde, 0x42, 0xa3, 0x98, 0xdc, 0x3b, 0x92, 0xd7, 0x53, - 0xa8, 0x45, 0x5c, 0x89, 0xe8, 0x2d, 0x0a, 0xb4, 0x40, 0x48, 0xd5, 0x0e, 0xd2, 0x2c, 0x65, 0xbd, 0x7a, 0x48, 0xcd, - 0xd4, 0x76, 0x15, 0xb6, 0x86, 0xcb, 0x0c, 0x2d, 0x16, 0x7e, 0x09, 0x16, 0x8b, 0x90, 0x11, 0x6d, 0x15, 0x8e, 0x69, - 0xaf, 0x6d, 0x1f, 0x48, 0x64, 0x6e, 0x93, 0x28, 0xcc, 0x57, 0x55, 0xfa, 0xeb, 0x54, 0xf2, 0xdb, 0x02, 0x4c, 0xdd, - 0x07, 0x0f, 0x3c, 0xf5, 0xcf, 0x88, 0xcc, 0x35, 0x8b, 0x29, 0xc0, 0x74, 0x17, 0xc8, 0x82, 0x68, 0x82, 0x5f, 0x10, - 0xbb, 0x4b, 0xcb, 0x13, 0xca, 0xee, 0x5a, 0xa2, 0xcc, 0x0a, 0x3a, 0x8f, 0xc1, 0xc6, 0xb8, 0xf3, 0xf0, 0x37, 0x2f, - 0xbf, 0x94, 0x38, 0x52, 0x97, 0xf4, 0x6c, 0xbb, 0x87, 0xa7, 0x39, 0x89, 0x2e, 0xc1, 0xd4, 0x21, 0x01, 0x7a, 0x82, - 0xce, 0xae, 0xde, 0x3c, 0x93, 0x91, 0xd2, 0x9c, 0x25, 0xf4, 0x99, 0x7e, 0xb9, 0x15, 0xbb, 0x0f, 0xe7, 0x17, 0x6a, - 0x37, 0x3a, 0x8d, 0x08, 0xe8, 0x9f, 0x1a, 0xe8, 0xbc, 0x3e, 0xb2, 0x5a, 0x0f, 0xd6, 0x3d, 0x00, 0x18, 0x84, 0xd4, - 0x6e, 0xe5, 0x02, 0xaa, 0x36, 0x94, 0x18, 0xa1, 0xde, 0x6a, 0x20, 0xcb, 0xdf, 0x05, 0x09, 0x11, 0x81, 0xbd, 0x8b, - 0x9f, 0x72, 0x8b, 0xc1, 0xa0, 0x92, 0x9a, 0xc1, 0x2c, 0x1e, 0x8f, 0x13, 0xd6, 0x53, 0xc2, 0xdf, 0xea, 0x3c, 0xc4, - 0x48, 0xa9, 0xb9, 0x65, 0xf5, 0x5d, 0x31, 0x90, 0xa7, 0xf1, 0x14, 0x9d, 0x80, 0x32, 0x82, 0xdf, 0x63, 0x5b, 0x8b, - 0x4e, 0x19, 0x42, 0x6c, 0x57, 0xc8, 0xa3, 0xe7, 0xfa, 0x5a, 0x1e, 0x80, 0x26, 0x44, 0x1b, 0x0e, 0x46, 0x75, 0x36, - 0x0f, 0x5a, 0xbb, 0xf5, 0x85, 0x60, 0x95, 0x5e, 0x82, 0xb7, 0x66, 0x59, 0x1e, 0xd0, 0x44, 0x4b, 0x7c, 0xf8, 0xc7, - 0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0x7e, 0xe9, 0xa2, 0xb2, 0xbe, 0x98, 0xff, 0x3f, 0xa7, 0xe5, 0x8b, 0xf5, - 0xa7, 0xe5, 0x0b, 0x75, 0x5a, 0x6e, 0xa6, 0xd8, 0xcf, 0x26, 0x1d, 0xfc, 0xd3, 0xab, 0x16, 0x04, 0xbb, 0x02, 0xe8, - 0xb0, 0x50, 0xe9, 0x6b, 0x75, 0xe1, 0x3f, 0x1a, 0xba, 0xed, 0xe1, 0x1f, 0x1f, 0xd4, 0x9b, 0xb6, 0x85, 0x85, 0xf8, - 0xaf, 0x5d, 0xab, 0xea, 0xdc, 0xc7, 0x3a, 0xec, 0xf5, 0x60, 0xb5, 0xae, 0x7b, 0xf3, 0xa1, 0x05, 0x7e, 0xc5, 0x9d, - 0x40, 0x31, 0x63, 0xb0, 0x43, 0xa2, 0x93, 0x13, 0x28, 0x9d, 0x64, 0xa3, 0x45, 0xf1, 0x8f, 0x12, 0x7e, 0x89, 0xc4, - 0x1b, 0x8f, 0x74, 0x63, 0x1c, 0xd5, 0x55, 0x84, 0xdd, 0xd5, 0x08, 0x4b, 0xbd, 0x4f, 0x41, 0x01, 0x84, 0xc9, 0x9c, - 0xae, 0x7f, 0x7f, 0xcd, 0x21, 0xf8, 0xbb, 0xec, 0xcd, 0xda, 0xc5, 0xfc, 0x7b, 0x91, 0x71, 0x23, 0x12, 0x7e, 0x17, - 0x0e, 0xcc, 0x3d, 0x6c, 0x3f, 0x5e, 0x0f, 0xee, 0x91, 0x9a, 0x69, 0xa8, 0x84, 0x82, 0x94, 0x3b, 0xa0, 0xe2, 0x46, - 0x8b, 0x84, 0xdf, 0x3c, 0xea, 0x75, 0x94, 0xb1, 0x32, 0xea, 0x0d, 0x0c, 0xbd, 0x6a, 0x7b, 0x47, 0x2e, 0xfd, 0xd9, - 0x17, 0xf7, 0xf1, 0x8f, 0xf0, 0xea, 0x9c, 0x54, 0x8a, 0xbf, 0x30, 0x7c, 0x51, 0xf1, 0xdf, 0xac, 0x69, 0xf6, 0x42, - 0x82, 0x93, 0x72, 0x7f, 0xd7, 0xd6, 0xa8, 0xcf, 0xde, 0xa9, 0xb9, 0xd4, 0x83, 0x7e, 0x57, 0xeb, 0xdf, 0x37, 0xf8, - 0x1d, 0xdb, 0x8e, 0x84, 0xce, 0x5c, 0x6f, 0x2b, 0x7f, 0x65, 0xc2, 0x6a, 0x63, 0x81, 0xe7, 0xbb, 0x36, 0x57, 0x1b, - 0x44, 0xed, 0x37, 0xc3, 0x13, 0x6d, 0x1e, 0xc9, 0xb0, 0x1b, 0xb6, 0x17, 0x16, 0xd2, 0xb7, 0x2c, 0xbc, 0x87, 0x9f, - 0x1a, 0xb2, 0x2e, 0x66, 0x49, 0x0a, 0x3a, 0xd5, 0x94, 0xf3, 0x79, 0xb0, 0xb3, 0x73, 0x7e, 0x7e, 0xee, 0x9f, 0xef, - 0xfa, 0x59, 0x7e, 0xba, 0xd3, 0x6d, 0xb7, 0xdb, 0xf8, 0x85, 0x18, 0xdb, 0x3a, 0x8b, 0xd9, 0xf9, 0x97, 0xd9, 0x45, - 0x68, 0x3f, 0xb2, 0x1e, 0x5b, 0x8f, 0x76, 0xad, 0x07, 0x0f, 0x6d, 0x8b, 0xb8, 0x3f, 0x94, 0xec, 0xda, 0x96, 0xe0, - 0xfe, 0xa1, 0x0d, 0xc5, 0xfd, 0xbd, 0x53, 0xa5, 0xc0, 0x61, 0x06, 0xae, 0x50, 0x8f, 0xc0, 0x66, 0xc9, 0x3e, 0xb1, - 0xfa, 0x39, 0x17, 0x65, 0x2d, 0x29, 0x43, 0xd4, 0x2b, 0x1e, 0xf6, 0x51, 0x34, 0x0f, 0x88, 0x86, 0xcc, 0x42, 0x74, - 0x00, 0x89, 0x52, 0x9a, 0x02, 0xa3, 0xba, 0x27, 0xf0, 0x04, 0x1a, 0xfb, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x47, 0x20, - 0xe0, 0xce, 0x5a, 0xf7, 0x47, 0xed, 0x56, 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x23, 0xab, 0x2b, 0xfe, 0x07, 0x06, 0xb9, - 0x6b, 0x75, 0xe0, 0x69, 0xd7, 0x82, 0xf7, 0xb3, 0xfb, 0x22, 0x24, 0x1c, 0xd9, 0x3b, 0xfd, 0x3d, 0xfc, 0x85, 0x29, - 0xb0, 0xa8, 0x2f, 0x6c, 0xf1, 0x2b, 0x9e, 0xec, 0xcf, 0xcc, 0xd2, 0xce, 0xe3, 0xb5, 0xc5, 0xdd, 0x47, 0x6b, 0x8b, - 0x77, 0x1f, 0xae, 0x2d, 0xbe, 0xff, 0xa0, 0x5e, 0xbc, 0x73, 0x2a, 0xaa, 0x34, 0x53, 0x08, 0xed, 0x59, 0x04, 0x54, - 0x72, 0xe1, 0x74, 0x00, 0xce, 0xb6, 0xd5, 0xc2, 0x1f, 0x8f, 0xba, 0xae, 0xee, 0x75, 0x82, 0xbd, 0xf4, 0x2a, 0x1f, - 0x3d, 0x86, 0x55, 0x3e, 0xef, 0x3e, 0x1c, 0x61, 0x3b, 0x5a, 0x28, 0xfc, 0x3b, 0xdb, 0x7d, 0x3c, 0x02, 0x71, 0x60, - 0xe1, 0x3f, 0xf8, 0x33, 0x7d, 0xd0, 0x1d, 0x89, 0x97, 0x36, 0xd6, 0x7f, 0xe8, 0x3c, 0x2a, 0xa0, 0x29, 0xfe, 0xf9, - 0x4d, 0xeb, 0xcf, 0xa8, 0xbe, 0x9b, 0xe3, 0xde, 0x07, 0x1c, 0x3d, 0x9e, 0x76, 0xfd, 0x2f, 0xce, 0x1e, 0xf9, 0x8f, - 0xa7, 0x9d, 0x47, 0x1f, 0xc4, 0x5b, 0x02, 0x18, 0xfc, 0x02, 0xff, 0x7d, 0xd8, 0x6d, 0x83, 0x69, 0xeb, 0x3f, 0x3e, - 0xdb, 0xf5, 0x77, 0x93, 0xd6, 0x43, 0xff, 0x31, 0xfe, 0xab, 0x86, 0x9b, 0x66, 0x33, 0x66, 0x5b, 0xb8, 0xdf, 0x0d, - 0xbb, 0xd0, 0x9c, 0xa3, 0x7b, 0xdf, 0x7a, 0x70, 0xff, 0xf9, 0x63, 0xd8, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf7, - 0xf8, 0x01, 0x11, 0x2f, 0x07, 0x8e, 0x18, 0xe6, 0xce, 0x29, 0xc4, 0xd1, 0xd7, 0x8a, 0xee, 0x79, 0x3f, 0x5e, 0x67, - 0xda, 0xff, 0x70, 0xbb, 0x69, 0xff, 0xd7, 0x3b, 0xba, 0x6f, 0x7f, 0xf8, 0x93, 0x6d, 0xfb, 0x1f, 0x9b, 0xb6, 0xfd, - 0x39, 0x5b, 0x31, 0xee, 0x9b, 0xf6, 0xd9, 0x21, 0x73, 0x8e, 0xbe, 0x65, 0x43, 0xcc, 0x13, 0x85, 0xd6, 0x7f, 0xad, - 0x79, 0x3a, 0x32, 0x3c, 0xc8, 0xe7, 0x4c, 0x9c, 0xe4, 0xef, 0xaf, 0x43, 0x08, 0xe3, 0xb7, 0x22, 0xe4, 0xc5, 0xdd, - 0xf0, 0x41, 0x9f, 0x16, 0xff, 0x13, 0xf1, 0xf1, 0xbd, 0x89, 0x8f, 0x9a, 0x2f, 0x99, 0x8c, 0x79, 0xb2, 0xc1, 0x0f, - 0xe8, 0xdd, 0xb1, 0x77, 0x18, 0xbe, 0x15, 0xb6, 0x48, 0x7e, 0x7a, 0xf7, 0x7b, 0xfc, 0xde, 0x22, 0x8d, 0x32, 0xb4, - 0xa5, 0x83, 0x62, 0x8e, 0x1f, 0xe3, 0x54, 0x2f, 0x67, 0x22, 0x55, 0x3f, 0xa4, 0x7b, 0x36, 0xf7, 0xb5, 0x73, 0x03, - 0x33, 0x5b, 0xc3, 0x65, 0xc6, 0x23, 0xfc, 0x6d, 0x2f, 0x3c, 0xe6, 0x09, 0xde, 0x02, 0x94, 0x37, 0x66, 0x30, 0x11, - 0xf3, 0x5b, 0x4c, 0x22, 0x55, 0xee, 0xef, 0x19, 0x3a, 0x0c, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x13, - 0x0b, 0x63, 0xb6, 0x6a, 0x19, 0x9c, 0x94, 0xbc, 0xe9, 0xda, 0xea, 0x17, 0x8c, 0xe4, 0xf8, 0xc1, 0xa6, 0xf0, 0x48, - 0xba, 0xce, 0x6c, 0xa9, 0xfe, 0xc3, 0xf8, 0xaa, 0x24, 0x47, 0xd6, 0x5d, 0xa9, 0x0c, 0xb6, 0xd0, 0x19, 0x3a, 0x7e, - 0x17, 0x6c, 0x08, 0x2a, 0xc6, 0x0f, 0xe0, 0xfc, 0xe0, 0xb4, 0x76, 0x41, 0xa7, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b, - 0x1f, 0xdf, 0x14, 0x7e, 0x83, 0x46, 0xa9, 0xa7, 0x7f, 0xe3, 0x12, 0x50, 0x86, 0xca, 0xf5, 0xff, 0xf2, 0xf2, 0x50, - 0x5e, 0x72, 0xb5, 0xd1, 0x27, 0x49, 0xbe, 0xe8, 0xea, 0x03, 0x3b, 0xdb, 0x20, 0x2e, 0xe8, 0xd7, 0xde, 0x51, 0x50, - 0x16, 0x25, 0x02, 0xe6, 0x98, 0x5a, 0xd2, 0x6c, 0x08, 0x6d, 0x21, 0x0f, 0xc6, 0xec, 0x2c, 0x1e, 0x49, 0xb6, 0xee, - 0x59, 0x32, 0x37, 0xbe, 0x45, 0xab, 0x08, 0x3b, 0x9e, 0x30, 0x9c, 0xe1, 0x05, 0x65, 0x54, 0x98, 0x66, 0x76, 0xff, - 0x5e, 0x4f, 0x43, 0x52, 0x4f, 0xcf, 0xb5, 0xf1, 0x77, 0xf0, 0x1d, 0x81, 0xa1, 0xf6, 0x8f, 0xe1, 0x3d, 0xfc, 0x2d, - 0x7c, 0xf7, 0x86, 0xb6, 0xeb, 0x13, 0x53, 0xbc, 0x57, 0xfd, 0x2a, 0x3e, 0xe4, 0x08, 0xdb, 0x20, 0xbf, 0xbc, 0xbb, - 0x0a, 0x32, 0x29, 0xb4, 0xba, 0x0f, 0x2a, 0xa1, 0x05, 0xcf, 0x06, 0x97, 0x02, 0x06, 0xda, 0xf5, 0x1f, 0x18, 0xac, - 0xf0, 0xac, 0x85, 0x3f, 0x6b, 0xcc, 0xf0, 0x3e, 0x34, 0x50, 0xdc, 0xf0, 0x25, 0x34, 0xdf, 0x15, 0x8c, 0x17, 0xfa, - 0xfd, 0x48, 0xac, 0x4a, 0xb0, 0xa9, 0x3a, 0xc5, 0xac, 0x09, 0x8f, 0x88, 0x78, 0xb6, 0xed, 0x39, 0xfa, 0xfb, 0xfe, - 0x92, 0x5c, 0xe5, 0xe5, 0xa4, 0xa7, 0xd0, 0xd7, 0xd1, 0xdf, 0xad, 0x5d, 0x57, 0xe7, 0xd5, 0x4e, 0xce, 0x9a, 0x29, - 0x90, 0xe0, 0x1b, 0x21, 0x18, 0xca, 0xd5, 0x16, 0xdf, 0x6f, 0x12, 0xc7, 0xb8, 0xfa, 0xc2, 0xd5, 0x9a, 0x74, 0x43, - 0xf3, 0x50, 0xb0, 0x8a, 0x68, 0xe8, 0x5c, 0x00, 0x23, 0xa0, 0x9f, 0x55, 0xb1, 0x7a, 0x90, 0x04, 0xe5, 0x27, 0x11, - 0xfe, 0xfa, 0x09, 0xfa, 0x51, 0x56, 0x07, 0x90, 0xd3, 0x07, 0xfa, 0x08, 0xd2, 0x17, 0xe3, 0xb2, 0xb9, 0x08, 0xd0, - 0x17, 0xf0, 0xb7, 0x99, 0x55, 0xb9, 0xe1, 0xf2, 0xd2, 0x17, 0x86, 0xc1, 0xc7, 0x71, 0x4e, 0x77, 0x09, 0xd5, 0xfa, - 0x6b, 0xd7, 0xfc, 0x2a, 0x54, 0xd3, 0xa9, 0x64, 0xc5, 0xc0, 0xc6, 0x22, 0x5b, 0x65, 0xe9, 0x98, 0x5f, 0xa8, 0x35, - 0x2f, 0x7b, 0x8d, 0x45, 0x9a, 0x0e, 0x7e, 0xc1, 0xdb, 0x16, 0x48, 0xb6, 0x81, 0x8d, 0x5d, 0xbb, 0x26, 0x52, 0x6e, - 0xf0, 0x8e, 0x54, 0xf5, 0x2b, 0x59, 0xcc, 0x03, 0x6f, 0x9b, 0xbb, 0xa5, 0xc7, 0xa5, 0x7d, 0x70, 0xa5, 0xa7, 0xf0, - 0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0xf7, 0x70, 0x0c, 0xb6, 0xe0, 0x75, 0xd8, 0xae, 0x5b, 0x02, 0xe7, 0x31, 0x7e, - 0x67, 0x8d, 0x40, 0xbd, 0x0f, 0x85, 0x6e, 0xe5, 0xb5, 0x9b, 0x76, 0xfb, 0x6f, 0x0e, 0xf7, 0x2d, 0x71, 0x9a, 0xf7, - 0x76, 0xe0, 0x75, 0x8f, 0x6c, 0x61, 0x91, 0x52, 0x10, 0x8a, 0x94, 0x02, 0x4b, 0x64, 0xc3, 0x84, 0xf6, 0x8e, 0x58, - 0xa6, 0x6d, 0xb1, 0x74, 0x24, 0x3c, 0x78, 0x33, 0xb0, 0x15, 0x62, 0xfc, 0x8a, 0xd1, 0x0e, 0x76, 0x6b, 0xe1, 0x4e, - 0xc3, 0x11, 0x10, 0x3e, 0x3e, 0xa5, 0x20, 0xf0, 0xd4, 0x96, 0xfe, 0x3e, 0x10, 0xeb, 0x4c, 0x65, 0x62, 0xc8, 0xa1, - 0x74, 0x5e, 0xde, 0x6a, 0xeb, 0x62, 0x71, 0x32, 0x03, 0x3e, 0xa4, 0x92, 0x29, 0xde, 0xcb, 0x0e, 0x7b, 0x34, 0x15, - 0x66, 0x01, 0xae, 0x3a, 0x21, 0xa7, 0x9d, 0xfe, 0x5e, 0x24, 0xf5, 0x1d, 0x3c, 0xbb, 0x05, 0x1c, 0x5e, 0x10, 0x73, - 0xa8, 0x54, 0xf8, 0x71, 0xb6, 0x73, 0xce, 0x4e, 0x5a, 0xd1, 0x3c, 0xae, 0x7c, 0x7f, 0x28, 0xfd, 0xfa, 0x7b, 0x4a, - 0x10, 0xca, 0x84, 0x33, 0xf9, 0x18, 0x19, 0x89, 0x07, 0x88, 0x38, 0x22, 0xd0, 0x52, 0x3a, 0x16, 0x49, 0x69, 0x04, - 0xe4, 0x03, 0xac, 0x44, 0xbf, 0xca, 0x01, 0x29, 0x25, 0x41, 0x69, 0xf7, 0xff, 0xf6, 0xbf, 0xfe, 0xb7, 0xf4, 0x29, - 0x02, 0x5a, 0x01, 0x2c, 0xcc, 0xdc, 0xa8, 0x62, 0x67, 0xec, 0x02, 0xac, 0xd0, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, - 0x20, 0x28, 0x98, 0xb8, 0xbb, 0x21, 0xeb, 0x81, 0x0a, 0x24, 0x58, 0x66, 0xd8, 0x59, 0x82, 0x57, 0x2f, 0xc2, 0x1d, - 0xfb, 0x43, 0x19, 0x7c, 0x2a, 0xb7, 0x94, 0x08, 0xda, 0xc8, 0xe7, 0x33, 0x68, 0xae, 0x96, 0xd3, 0xa7, 0x7e, 0x23, - 0x8c, 0x64, 0x1e, 0xac, 0x96, 0xd0, 0x07, 0x2d, 0x75, 0xa0, 0xe0, 0xdf, 0xfe, 0xf5, 0x3f, 0xff, 0x77, 0xf5, 0x8a, - 0xfe, 0xff, 0xbf, 0xfd, 0xcb, 0x3f, 0xfd, 0xdf, 0xff, 0xf3, 0x5f, 0x30, 0x39, 0x52, 0xc6, 0x08, 0xe8, 0x28, 0x59, - 0x55, 0x80, 0x40, 0x9c, 0xa9, 0x7a, 0xb6, 0xdf, 0x01, 0xcd, 0x42, 0x04, 0x29, 0x41, 0x22, 0x62, 0xa6, 0x24, 0x50, - 0x42, 0xd5, 0x0d, 0x38, 0x83, 0xfd, 0xb3, 0x28, 0x4a, 0x6d, 0x3f, 0x68, 0xdb, 0xd5, 0x9e, 0xf6, 0x8d, 0xbe, 0x3b, - 0xb8, 0x1b, 0x77, 0xca, 0x14, 0xf1, 0xf5, 0x5e, 0x2d, 0x95, 0xe3, 0x0a, 0x4b, 0xca, 0xaa, 0xdc, 0x42, 0x8f, 0xf2, - 0x12, 0x5f, 0x83, 0xae, 0x51, 0x4c, 0x5b, 0x5b, 0xeb, 0xd3, 0xfb, 0x65, 0x51, 0xf0, 0x78, 0x82, 0xfb, 0x21, 0xdc, - 0x63, 0x14, 0x0a, 0x6c, 0xa1, 0x4a, 0x92, 0x5c, 0x96, 0x34, 0x8a, 0x30, 0x61, 0xee, 0x3f, 0xfe, 0x87, 0xf2, 0x2f, - 0x33, 0x54, 0x05, 0x2c, 0x67, 0x16, 0x5d, 0x48, 0xc3, 0xe6, 0x61, 0xbb, 0x3d, 0xbf, 0x70, 0x97, 0xd5, 0x0c, 0xde, - 0x75, 0x93, 0x91, 0x4b, 0xcd, 0x1c, 0x90, 0x62, 0x88, 0xda, 0x7b, 0x07, 0xba, 0x7c, 0x1b, 0x9d, 0x3d, 0x65, 0xf9, - 0xf9, 0x92, 0x1c, 0x48, 0xf1, 0x6f, 0x18, 0xeb, 0x93, 0xbe, 0x36, 0x28, 0x31, 0x56, 0xb1, 0x34, 0x7a, 0x75, 0x45, - 0xaf, 0x69, 0x67, 0x35, 0xd3, 0xc4, 0x8c, 0x55, 0x9a, 0x51, 0x46, 0xcc, 0xc3, 0x80, 0x0e, 0xde, 0xb4, 0xbb, 0xd4, - 0xc3, 0x73, 0x9e, 0xcd, 0xcc, 0xe0, 0x24, 0x8b, 0xd8, 0x88, 0x4d, 0x94, 0x8f, 0x52, 0xd6, 0x8b, 0xc0, 0x63, 0xf9, - 0x19, 0x9e, 0x31, 0xc0, 0x6d, 0x16, 0xf1, 0x80, 0x28, 0xb5, 0x67, 0x86, 0x2f, 0x23, 0x0c, 0x0c, 0x67, 0x4b, 0x63, - 0xae, 0x9e, 0x68, 0x8a, 0x9e, 0xc0, 0x7a, 0x7e, 0x4a, 0xe9, 0x53, 0x77, 0x73, 0x28, 0xe1, 0x48, 0x78, 0x51, 0x65, - 0x87, 0x54, 0x26, 0xf6, 0xbb, 0x9a, 0x39, 0x2e, 0x99, 0x31, 0x18, 0xc1, 0xb7, 0x37, 0x16, 0x52, 0x52, 0x34, 0xfd, - 0x15, 0x94, 0x1f, 0x5a, 0x80, 0xdd, 0x6c, 0x45, 0x85, 0xd8, 0xea, 0x5d, 0xf8, 0x42, 0xab, 0xe2, 0xd1, 0x7c, 0x4e, - 0x0d, 0x5d, 0xa0, 0x53, 0x52, 0xa9, 0x91, 0x71, 0x50, 0x2c, 0x5c, 0x84, 0x9e, 0x65, 0x1b, 0x49, 0xd0, 0xe2, 0x49, - 0x06, 0xa5, 0xe9, 0xf7, 0x0d, 0xff, 0x3f, 0xdf, 0x8d, 0x21, 0x2b, 0x85, 0x78, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xc9, 0x76, 0xdb, 0xc8, 0xb2, 0xe0, 0xba, + 0xcf, 0xe9, 0x3f, 0xe8, 0x0d, 0x84, 0xd2, 0x93, 0x81, 0x12, 0x08, 0x91, 0x94, 0x65, 0xbb, 0x40, 0x41, 0xbc, 0xf2, + 0x50, 0xd7, 0xae, 0xf2, 0x54, 0x96, 0xec, 0x1a, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02, 0x92, + 0x1a, 0x8a, 0xc2, 0x3b, 0xbd, 0xea, 0x55, 0x9f, 0xd3, 0xe3, 0xe2, 0x6d, 0xfa, 0xbc, 0x5e, 0xf4, 0x47, 0xf4, 0xba, + 0x3f, 0xe5, 0xfe, 0x40, 0xbf, 0x4f, 0xe8, 0x88, 0xc8, 0x01, 0x09, 0x90, 0x1a, 0x5c, 0xaf, 0xfa, 0x1e, 0x0f, 0x02, + 0x72, 0x8c, 0x88, 0x8c, 0x8c, 0x29, 0x23, 0xa1, 0xdd, 0xb5, 0x51, 0x36, 0xe4, 0x97, 0x33, 0x66, 0x4d, 0xf8, 0x34, + 0xd9, 0xdb, 0x95, 0xff, 0xb3, 0x68, 0xb4, 0xb7, 0x9b, 0xc4, 0xe9, 0x27, 0x2b, 0x67, 0x49, 0x18, 0x0f, 0xb3, 0xd4, + 0x9a, 0xe4, 0x6c, 0x1c, 0x8e, 0x22, 0x1e, 0x05, 0xf1, 0x34, 0x3a, 0x65, 0xd6, 0xd6, 0xde, 0xee, 0x94, 0xf1, 0xc8, + 0x1a, 0x4e, 0xa2, 0xbc, 0x60, 0x3c, 0x7c, 0x7f, 0xf8, 0x75, 0xeb, 0xd1, 0xde, 0x6e, 0x31, 0xcc, 0xe3, 0x19, 0xb7, + 0x70, 0xc8, 0x70, 0x9a, 0x8d, 0xe6, 0x09, 0xdb, 0x3b, 0x8b, 0x72, 0xeb, 0x82, 0x85, 0x6f, 0x4e, 0x7e, 0x65, 0x43, + 0xee, 0x8f, 0xd8, 0x38, 0x4e, 0xd9, 0xdb, 0x3c, 0x9b, 0xb1, 0x9c, 0x5f, 0x7a, 0xcf, 0x56, 0x57, 0xc4, 0xac, 0xf0, + 0x0e, 0x74, 0xd5, 0x29, 0xe3, 0x6f, 0xce, 0x53, 0xd5, 0xe7, 0x29, 0x13, 0x93, 0x64, 0x79, 0xe1, 0xf1, 0x6b, 0xda, + 0x1c, 0x5c, 0x4e, 0x4f, 0xb2, 0xa4, 0xf0, 0x3e, 0xe9, 0xfa, 0x59, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0x27, 0x51, 0x61, + 0xb4, 0xf4, 0x9e, 0xac, 0x68, 0x32, 0x93, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x7c, 0xca, 0xf2, 0xe8, 0x24, 0x61, 0x5e, + 0xc1, 0x42, 0x87, 0x79, 0xdc, 0x8b, 0xdd, 0x70, 0x8f, 0x5b, 0x71, 0x6a, 0xb1, 0xfe, 0x05, 0xa3, 0x92, 0x05, 0xd3, + 0xad, 0x82, 0xb5, 0xb6, 0x07, 0xe4, 0x1a, 0xc7, 0xa7, 0x73, 0xfd, 0x7e, 0x9e, 0xc7, 0x5c, 0x3d, 0x9f, 0x45, 0xc9, + 0x9c, 0x05, 0x71, 0xe9, 0x06, 0xec, 0x88, 0x0f, 0xc2, 0xd8, 0x7b, 0x42, 0x83, 0xc2, 0x90, 0x8b, 0x71, 0x96, 0x3b, + 0x48, 0xab, 0x18, 0xc7, 0xe6, 0x57, 0x57, 0x0e, 0x0f, 0x17, 0xa5, 0xeb, 0x7e, 0x62, 0xfe, 0x30, 0x4a, 0x12, 0x07, + 0x27, 0xde, 0xd8, 0x28, 0x70, 0xc6, 0xd8, 0xe3, 0x47, 0xf1, 0xc0, 0xed, 0xc5, 0x63, 0x87, 0x33, 0xb7, 0xea, 0x97, + 0x8d, 0x2d, 0xce, 0x1c, 0xee, 0xba, 0x4f, 0xae, 0xef, 0x93, 0x33, 0x3e, 0xcf, 0x01, 0xf6, 0xd2, 0x7b, 0xa3, 0x66, + 0x7e, 0x86, 0xf5, 0x07, 0xd4, 0xb1, 0x07, 0xb0, 0x17, 0xdc, 0xfa, 0x10, 0x9e, 0xc7, 0xe9, 0x28, 0x3b, 0xf7, 0x0f, + 0x26, 0x11, 0xfc, 0x78, 0x97, 0x65, 0x7c, 0x63, 0xc3, 0x39, 0xcb, 0xe2, 0x91, 0xd5, 0x0e, 0x43, 0xb3, 0xf2, 0xf2, + 0xc9, 0xc1, 0xc1, 0xd5, 0x55, 0xa3, 0xc0, 0x4f, 0x23, 0x1e, 0x9f, 0x31, 0xd1, 0x19, 0x00, 0xb0, 0xe1, 0xe7, 0x8c, + 0xb3, 0xd1, 0x01, 0xbf, 0x4c, 0xa0, 0x94, 0x31, 0x5e, 0xd8, 0x80, 0xe3, 0xd3, 0x6c, 0x08, 0x64, 0x4b, 0x0d, 0xc2, + 0x43, 0xd3, 0x9c, 0xcd, 0x92, 0x68, 0xc8, 0xb0, 0x1e, 0x46, 0xaa, 0x7a, 0x54, 0x8d, 0xbc, 0xaf, 0x43, 0xb1, 0xbc, + 0x8e, 0xeb, 0xc5, 0x2c, 0x4c, 0xd9, 0xb9, 0xf5, 0x2a, 0x9a, 0xf5, 0x86, 0x49, 0x54, 0x14, 0xc0, 0xaf, 0x0b, 0x42, + 0x21, 0x9f, 0x0f, 0x81, 0x41, 0x08, 0xc1, 0x05, 0x92, 0x69, 0x12, 0x17, 0xfe, 0xc7, 0xf5, 0x61, 0x51, 0xbc, 0x63, + 0xc5, 0x3c, 0xe1, 0xeb, 0x21, 0xac, 0x05, 0x5f, 0x0b, 0xc3, 0xaf, 0x5d, 0x3e, 0xc9, 0xb3, 0x73, 0xeb, 0x59, 0x9e, + 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0xb8, 0xb0, 0xd2, 0x8c, 0x5b, 0x7a, 0x30, 0x5c, 0x40, 0xdf, 0x7a, 0x5f, + 0x30, 0xeb, 0x78, 0x9e, 0x16, 0xd1, 0x98, 0x41, 0xd3, 0x63, 0x2b, 0xcb, 0xad, 0x63, 0x18, 0xf4, 0x18, 0x96, 0xac, + 0xe0, 0xb0, 0x6b, 0x7c, 0xdb, 0xed, 0xd1, 0x5c, 0x50, 0x78, 0xc8, 0x2e, 0x78, 0xc8, 0x4a, 0x60, 0x4c, 0xab, 0xd0, + 0x68, 0x38, 0xee, 0x22, 0x81, 0x02, 0x16, 0xc6, 0x0c, 0x59, 0xd6, 0x31, 0x1b, 0xeb, 0xc5, 0xf9, 0xb0, 0xb1, 0xa1, + 0x69, 0x0d, 0x34, 0x71, 0xa0, 0x6d, 0xd1, 0x68, 0xeb, 0x09, 0xc4, 0x6b, 0x24, 0x72, 0x3d, 0xe6, 0x4b, 0xf2, 0x1d, + 0x5c, 0xa6, 0xc3, 0xfa, 0xd8, 0x50, 0x59, 0xf2, 0xec, 0x80, 0xe7, 0x71, 0x7a, 0x0a, 0x40, 0xc8, 0x99, 0xcc, 0x26, + 0x65, 0x29, 0x16, 0xff, 0x0d, 0x0b, 0x59, 0xb8, 0x87, 0xa3, 0xe7, 0xcc, 0xb1, 0x0b, 0xea, 0x61, 0x87, 0x21, 0x92, + 0x1e, 0x18, 0x8c, 0xf5, 0x59, 0xc0, 0x36, 0x6d, 0xdb, 0xfb, 0xda, 0xf5, 0x2e, 0x91, 0x83, 0x7c, 0xdf, 0x27, 0xf6, + 0x15, 0x9d, 0xe3, 0xb0, 0x83, 0x40, 0xfb, 0x09, 0x4b, 0x4f, 0xf9, 0xa4, 0xcf, 0x8e, 0xda, 0x83, 0x80, 0x03, 0x54, + 0xa3, 0xf9, 0x90, 0x39, 0xc8, 0x8f, 0x5e, 0x8e, 0xdb, 0x67, 0xd3, 0x81, 0x29, 0x70, 0x61, 0xd6, 0x08, 0xc7, 0xda, + 0xd2, 0xb8, 0x8a, 0x45, 0x15, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x13, 0x96, 0x1b, 0x70, 0xe8, 0x66, 0xbd, 0xda, + 0x0a, 0xce, 0x61, 0x85, 0xa0, 0x9f, 0x35, 0x9e, 0xa7, 0x43, 0x1e, 0x83, 0xe0, 0xb2, 0x37, 0x01, 0x5c, 0xb1, 0x72, + 0x7a, 0xe1, 0x6c, 0xb7, 0x74, 0x9d, 0xd8, 0xdd, 0x64, 0x47, 0xf9, 0x66, 0x67, 0xe0, 0x21, 0x94, 0x9a, 0xf8, 0x12, + 0xf1, 0x18, 0x10, 0x2c, 0xbd, 0xb7, 0x4c, 0x6f, 0xcf, 0x0f, 0x7d, 0xe6, 0x2f, 0xf3, 0x71, 0xc8, 0xfd, 0x69, 0x34, + 0x43, 0x6c, 0x18, 0xf1, 0x40, 0x94, 0x0e, 0x11, 0xba, 0xda, 0xba, 0x20, 0xc5, 0xfc, 0x8a, 0x05, 0x5c, 0x20, 0x08, + 0xec, 0xd9, 0x67, 0xd1, 0x70, 0x02, 0x5b, 0xbc, 0x22, 0xdc, 0x48, 0x6d, 0x87, 0x61, 0xce, 0x22, 0xce, 0x9e, 0x25, + 0x0c, 0xdf, 0x70, 0x05, 0xa0, 0xa7, 0xed, 0x7a, 0xb9, 0xda, 0x77, 0x49, 0xcc, 0x5f, 0x67, 0x30, 0x4f, 0x4f, 0x30, + 0x09, 0x70, 0x71, 0xbe, 0xb1, 0x11, 0x23, 0x8b, 0xec, 0x73, 0x58, 0xad, 0x93, 0x39, 0x08, 0x01, 0x3b, 0xc5, 0x16, + 0x36, 0x50, 0xdb, 0x8b, 0x7d, 0x0e, 0x44, 0x7c, 0x92, 0xa5, 0x1c, 0x86, 0x03, 0x78, 0x35, 0x07, 0xf9, 0xd1, 0x6c, + 0xc6, 0xd2, 0xd1, 0x93, 0x49, 0x9c, 0x8c, 0x80, 0x1a, 0x25, 0xe0, 0x9b, 0xb1, 0x10, 0xf0, 0x04, 0x64, 0x82, 0x9b, + 0x31, 0xa2, 0xe5, 0x43, 0x46, 0xe6, 0xa1, 0x6d, 0xf7, 0x50, 0x02, 0x49, 0x2c, 0x50, 0x06, 0xd1, 0xc2, 0xbd, 0x03, + 0xd1, 0x5f, 0xb8, 0x7c, 0x33, 0x8c, 0xf5, 0x32, 0x4a, 0x02, 0xbf, 0x41, 0x49, 0x03, 0xf4, 0x67, 0x20, 0x03, 0x7b, + 0x28, 0xb8, 0xbe, 0x97, 0x52, 0x27, 0x65, 0x0a, 0x43, 0x20, 0xc0, 0x10, 0x25, 0x88, 0xa4, 0xc1, 0xdb, 0x2c, 0xb9, + 0x1c, 0xc7, 0x49, 0x72, 0x30, 0x9f, 0xcd, 0xb2, 0x9c, 0x7b, 0xdf, 0x84, 0x0b, 0x9e, 0x55, 0xb8, 0xd2, 0x26, 0x2f, + 0xce, 0x63, 0x8e, 0x04, 0x75, 0x17, 0xc3, 0x08, 0x96, 0xfa, 0x71, 0x96, 0x25, 0x2c, 0x4a, 0x01, 0x0d, 0xd6, 0xb7, + 0xed, 0x20, 0x9d, 0x27, 0x49, 0xef, 0x04, 0x86, 0xfd, 0xd4, 0xa3, 0x6a, 0x21, 0xf1, 0x03, 0x7a, 0xde, 0xcf, 0xf3, + 0xe8, 0x12, 0x1a, 0x62, 0x1b, 0xe0, 0x45, 0x58, 0xad, 0x6f, 0x0e, 0xde, 0xbc, 0xf6, 0x05, 0xe3, 0xc7, 0xe3, 0x4b, + 0x00, 0xb4, 0xac, 0xa4, 0xe6, 0x38, 0xcf, 0xa6, 0x8d, 0xa9, 0x91, 0x0e, 0x71, 0xc8, 0x7a, 0xd7, 0x80, 0x10, 0xd3, + 0xc8, 0xb0, 0x4a, 0xcc, 0x84, 0xe0, 0x35, 0xf1, 0xb3, 0xac, 0xc4, 0x3d, 0xd0, 0xc7, 0x87, 0x40, 0x14, 0xc3, 0x94, + 0x37, 0x43, 0xcb, 0xf3, 0xcb, 0x45, 0x1c, 0x12, 0x9c, 0x33, 0xd4, 0xbf, 0x08, 0xe3, 0x30, 0x82, 0xd9, 0x17, 0x62, + 0xc0, 0x52, 0x41, 0x1c, 0x97, 0xa5, 0x97, 0x68, 0x26, 0x46, 0x89, 0x87, 0x02, 0x85, 0xc3, 0x36, 0xba, 0xba, 0x62, + 0xf0, 0xe2, 0x7a, 0xdf, 0x86, 0x8b, 0x48, 0xe1, 0x83, 0x1a, 0x0a, 0xf7, 0x57, 0x20, 0xe4, 0x04, 0x6a, 0xb2, 0x33, + 0xd0, 0x83, 0x00, 0xe7, 0x37, 0x1e, 0xe8, 0xff, 0x04, 0xa1, 0x58, 0xeb, 0x78, 0xa0, 0x41, 0x9f, 0x4c, 0xa2, 0xf4, + 0x94, 0x8d, 0x82, 0x84, 0x95, 0x52, 0xf2, 0xee, 0x5b, 0xb0, 0xc6, 0xc0, 0x4e, 0x85, 0xf5, 0xfc, 0xf0, 0xd5, 0x4b, + 0xb9, 0x72, 0x35, 0x61, 0x0c, 0x8b, 0x34, 0x07, 0xb5, 0x0a, 0x62, 0x5b, 0x8a, 0xe3, 0x67, 0x5c, 0x49, 0x6f, 0x51, + 0x12, 0x17, 0xef, 0x67, 0x60, 0x62, 0xb0, 0xb7, 0x30, 0x0c, 0x4c, 0x1f, 0xc2, 0x54, 0x54, 0x0e, 0xf3, 0x89, 0x8a, + 0x91, 0x2e, 0x82, 0xce, 0x02, 0x53, 0xf1, 0x9a, 0x39, 0x6e, 0x09, 0xac, 0xca, 0xe3, 0xa1, 0x15, 0x8d, 0x46, 0x2f, + 0xd2, 0x98, 0xc7, 0x51, 0x12, 0xff, 0x4e, 0x94, 0x5c, 0x20, 0x8f, 0xf1, 0x9e, 0x5c, 0x04, 0xc0, 0x9d, 0x7a, 0x24, + 0xae, 0x12, 0xb2, 0x6b, 0x44, 0x0c, 0x21, 0x2d, 0x93, 0xf0, 0x68, 0x20, 0xc1, 0x4b, 0xfc, 0xd9, 0xbc, 0x98, 0x20, + 0x61, 0xe5, 0xc0, 0x28, 0xc8, 0xb3, 0x93, 0x82, 0xe5, 0x67, 0x6c, 0xa4, 0x39, 0xa0, 0x00, 0xac, 0xa8, 0x39, 0x18, + 0x2f, 0x34, 0xa3, 0xa3, 0x74, 0x28, 0x83, 0xa1, 0x7a, 0xa6, 0x98, 0x65, 0x92, 0x99, 0xb5, 0x85, 0xa3, 0xa5, 0x80, + 0x23, 0x8c, 0x0a, 0x29, 0x09, 0xf2, 0x50, 0x61, 0x38, 0x01, 0x29, 0x04, 0x5a, 0xc1, 0xdc, 0xe6, 0x4a, 0x93, 0x3d, + 0x9b, 0x93, 0x4a, 0xc8, 0xa1, 0x23, 0x6c, 0x64, 0x82, 0x34, 0x77, 0x61, 0x57, 0x81, 0x94, 0x97, 0xe0, 0x0a, 0x29, + 0xa2, 0xcc, 0x1c, 0x64, 0x80, 0xf0, 0x5b, 0xa1, 0x0b, 0x7d, 0x6c, 0x41, 0x6c, 0xe0, 0xeb, 0x95, 0x07, 0xc2, 0x4a, + 0xbc, 0x2b, 0x44, 0xbc, 0x6b, 0xc0, 0xc6, 0x89, 0x91, 0x9f, 0xbc, 0x35, 0xee, 0xa7, 0xd9, 0xfe, 0x70, 0xc8, 0x8a, + 0x22, 0x03, 0xd8, 0xd6, 0xa8, 0xfd, 0x75, 0x86, 0x16, 0x50, 0xd2, 0xd5, 0xb2, 0xce, 0x2e, 0x48, 0x83, 0x9b, 0x6a, + 0x45, 0xe9, 0xf4, 0xc0, 0xfe, 0xf8, 0x11, 0x64, 0xb6, 0x27, 0xc9, 0x00, 0x54, 0x5f, 0x36, 0xfc, 0x84, 0x3d, 0x53, + 0xa7, 0xcc, 0x4a, 0xfb, 0xd2, 0xa9, 0x83, 0xe4, 0xc1, 0xb0, 0x6e, 0x69, 0x2c, 0xe8, 0xca, 0xa1, 0x71, 0x35, 0xa4, + 0x82, 0x5c, 0x9c, 0x92, 0xca, 0x36, 0x96, 0x11, 0xac, 0xb6, 0xd2, 0x23, 0xd2, 0x2b, 0x6c, 0x0a, 0x02, 0xf4, 0x88, + 0x0d, 0x7a, 0xb2, 0x3e, 0xcc, 0x05, 0xe5, 0x72, 0xf6, 0xdb, 0x9c, 0x15, 0x5c, 0xb0, 0x2e, 0x8c, 0x5b, 0xc0, 0xb8, + 0xe5, 0x92, 0x75, 0x58, 0xb3, 0x1d, 0x57, 0xc1, 0xf6, 0x66, 0x86, 0x7a, 0xac, 0x40, 0x4e, 0xbe, 0x99, 0x9d, 0x10, + 0x56, 0xe6, 0x5e, 0x5d, 0x7d, 0xab, 0x06, 0xa9, 0x96, 0x52, 0xdb, 0x40, 0x8d, 0x35, 0xb1, 0x55, 0x93, 0x91, 0xed, + 0x4a, 0x85, 0xba, 0xd6, 0xe9, 0xd5, 0xf8, 0x00, 0xf6, 0x5c, 0x5b, 0xb3, 0x74, 0x65, 0x6c, 0xbf, 0x55, 0x34, 0x7d, + 0x23, 0x46, 0x26, 0x6b, 0x94, 0xdd, 0xce, 0x3d, 0x6a, 0xc7, 0x43, 0xdb, 0xa5, 0xba, 0x4a, 0x30, 0xcc, 0xeb, 0x82, + 0xa1, 0x09, 0xf5, 0x4c, 0x77, 0xb1, 0x35, 0x53, 0xb1, 0x50, 0xad, 0xb5, 0x72, 0x20, 0x78, 0x78, 0x04, 0xc6, 0xc9, + 0x4a, 0xff, 0xe0, 0x75, 0x34, 0x65, 0x48, 0x51, 0xef, 0xba, 0x06, 0xd2, 0x81, 0x80, 0x26, 0x83, 0xa6, 0x7a, 0xe3, + 0xae, 0xb0, 0x9a, 0xea, 0xfb, 0x2b, 0x06, 0x2b, 0x02, 0xec, 0xeb, 0x72, 0xc5, 0x12, 0x91, 0xde, 0x14, 0x5c, 0xa2, + 0xe9, 0x23, 0xca, 0xc4, 0x9a, 0x90, 0x82, 0x07, 0xe4, 0x61, 0xf9, 0x1b, 0x0b, 0x27, 0x5b, 0x31, 0x85, 0x23, 0x47, + 0x99, 0x02, 0x74, 0x26, 0x25, 0x00, 0xe2, 0x92, 0x7e, 0xd6, 0x36, 0x16, 0x92, 0xed, 0x00, 0xf9, 0xc0, 0x1f, 0x27, + 0x11, 0x77, 0x3a, 0x5b, 0x6d, 0x17, 0xf8, 0x10, 0x84, 0x38, 0xe8, 0x08, 0x30, 0xef, 0x2b, 0x54, 0x18, 0xa2, 0x12, + 0xbb, 0xdc, 0x07, 0xa3, 0x68, 0x12, 0x8f, 0xb9, 0x93, 0xa1, 0x12, 0x71, 0x4b, 0x96, 0x80, 0x92, 0xd1, 0xfb, 0x0a, + 0xa4, 0x04, 0x17, 0xd2, 0x45, 0x54, 0x6b, 0x81, 0xa6, 0x20, 0x25, 0x29, 0x45, 0x5a, 0x50, 0x41, 0x60, 0x08, 0x95, + 0x9e, 0xe2, 0x28, 0xd0, 0x6f, 0x71, 0x5f, 0x0c, 0x1a, 0x2c, 0x59, 0x94, 0x71, 0x3f, 0x5e, 0x2e, 0x04, 0x35, 0xec, + 0xf3, 0xec, 0x65, 0x76, 0xce, 0xf2, 0x27, 0x11, 0xc2, 0x1e, 0x88, 0xee, 0x25, 0x48, 0x7a, 0x12, 0xe8, 0xac, 0xa7, + 0x78, 0xe5, 0x8c, 0x90, 0x86, 0x85, 0x98, 0xc6, 0xa8, 0x08, 0x41, 0xcb, 0x11, 0xed, 0x53, 0xdc, 0x52, 0xb4, 0xf7, + 0x50, 0x95, 0x30, 0xcd, 0x5b, 0xfb, 0x2f, 0xeb, 0xbc, 0x05, 0x23, 0xcc, 0x14, 0xb7, 0xd6, 0x77, 0xac, 0xeb, 0x49, + 0xdd, 0xec, 0x48, 0xde, 0x32, 0x94, 0x19, 0xe8, 0x8f, 0xab, 0xab, 0xca, 0x48, 0x07, 0x65, 0xaa, 0xa5, 0x39, 0x5a, + 0x4e, 0x62, 0x4b, 0xb8, 0x25, 0x28, 0x23, 0x34, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x5d, 0xe4, 0xc5, 0x3d, 0xa7, 0xa1, + 0x8e, 0x00, 0x8a, 0x69, 0x4d, 0x23, 0xf5, 0x79, 0xa0, 0x2b, 0x50, 0x29, 0x29, 0x6d, 0xe4, 0x55, 0x4d, 0x04, 0xc4, + 0xe9, 0x88, 0xe5, 0xc2, 0x41, 0x93, 0x3a, 0x14, 0x26, 0x4c, 0x81, 0xa1, 0xd9, 0x08, 0x24, 0xbc, 0x42, 0x00, 0xcc, + 0x13, 0x7f, 0x92, 0x15, 0x5c, 0xd7, 0x99, 0xd0, 0xc7, 0x57, 0x57, 0xb1, 0xf0, 0x17, 0x91, 0x01, 0x72, 0x36, 0xcd, + 0xce, 0xd8, 0x0a, 0xa8, 0x7b, 0x6a, 0x30, 0x13, 0x64, 0x63, 0x18, 0x50, 0xa2, 0xa0, 0x5a, 0x66, 0x49, 0x3c, 0x64, + 0x5a, 0x4b, 0x4d, 0x7d, 0x30, 0xe8, 0xd8, 0x05, 0xc8, 0x08, 0xe6, 0xee, 0xed, 0xed, 0xb5, 0xbd, 0x8e, 0x5b, 0x0a, + 0x82, 0x2f, 0x96, 0x28, 0x7a, 0x83, 0x7e, 0x94, 0x26, 0xf8, 0x2a, 0x59, 0xc0, 0x5d, 0x43, 0x29, 0x72, 0xe1, 0x27, + 0x79, 0x52, 0x10, 0xbb, 0xde, 0x08, 0x06, 0xe5, 0x4c, 0x09, 0x6e, 0x34, 0x71, 0xc5, 0xb6, 0x7d, 0xa7, 0xc9, 0xa6, + 0xd9, 0x49, 0xed, 0x30, 0xb5, 0x30, 0x72, 0xcd, 0x0b, 0xed, 0x01, 0x9b, 0xcb, 0x83, 0x56, 0x22, 0x55, 0x03, 0xaf, + 0x03, 0x84, 0xc2, 0xd3, 0x75, 0x96, 0x50, 0xaa, 0x3a, 0x4b, 0x21, 0xae, 0x37, 0xd0, 0x5b, 0x26, 0xc1, 0x5c, 0x45, + 0x82, 0x03, 0x29, 0x10, 0x38, 0x7a, 0x64, 0x62, 0xbd, 0x9e, 0xc0, 0xf2, 0x9c, 0x44, 0xc3, 0x4f, 0x1a, 0xdc, 0x8a, + 0xec, 0x4d, 0x36, 0x70, 0x1a, 0x25, 0xa1, 0x21, 0xae, 0x4c, 0xbc, 0x95, 0x84, 0xae, 0x6d, 0x14, 0x70, 0xc8, 0x96, + 0xd8, 0xbe, 0xb9, 0xd0, 0x4d, 0x6e, 0x97, 0xec, 0xa1, 0xfc, 0x27, 0xcd, 0x25, 0x37, 0xb0, 0x1c, 0x57, 0xd2, 0x80, + 0x2b, 0xc6, 0x83, 0xa5, 0x69, 0x40, 0x02, 0x7c, 0x57, 0x8e, 0xe2, 0xe2, 0x7a, 0x12, 0xfc, 0xa9, 0x60, 0x3e, 0x35, + 0x66, 0xba, 0x15, 0x52, 0x2d, 0xe1, 0xa4, 0x19, 0xac, 0x41, 0x93, 0xc6, 0x83, 0x12, 0x35, 0xdf, 0xa2, 0xa1, 0x42, + 0x1c, 0x7f, 0x2a, 0xaa, 0xd0, 0x04, 0x43, 0x30, 0x72, 0xaf, 0x90, 0x0c, 0x97, 0x2d, 0x8b, 0x16, 0x29, 0x53, 0x63, + 0x52, 0xa9, 0x9a, 0xe5, 0x32, 0x30, 0xb0, 0x68, 0xb7, 0xfa, 0xd2, 0x12, 0x57, 0x22, 0x37, 0x0d, 0xb5, 0x30, 0x29, + 0x94, 0x37, 0xe1, 0xe4, 0xe8, 0x77, 0x29, 0xeb, 0xdd, 0xc4, 0x27, 0x57, 0xf8, 0xe4, 0xbe, 0xe1, 0x43, 0x99, 0xbc, + 0x5d, 0xf4, 0x8b, 0xe0, 0x9b, 0x5a, 0x25, 0xda, 0xa7, 0x3e, 0x0a, 0x66, 0x57, 0x0b, 0x5d, 0x10, 0x28, 0x92, 0x4d, + 0xd2, 0xbe, 0xe4, 0x37, 0x14, 0x1b, 0x95, 0x67, 0x94, 0xb9, 0x62, 0x83, 0xd4, 0xbc, 0xd2, 0xcc, 0x4b, 0xdd, 0x86, + 0xfd, 0x5e, 0x96, 0x92, 0x4e, 0x5c, 0x50, 0x26, 0xf6, 0x6e, 0xa2, 0x8d, 0x97, 0x86, 0x99, 0xb0, 0x7e, 0x85, 0xb1, + 0x53, 0xa3, 0x50, 0x2a, 0x45, 0x20, 0x8e, 0x8d, 0xaf, 0x95, 0x65, 0x90, 0xf9, 0x2b, 0xec, 0x29, 0x00, 0x25, 0x81, + 0xc5, 0xd7, 0x54, 0xf2, 0xa2, 0xb0, 0x4e, 0xc7, 0x35, 0xa2, 0x63, 0x25, 0x42, 0x6b, 0x22, 0x5f, 0xeb, 0xb3, 0xd8, + 0xaf, 0xb9, 0x84, 0x26, 0x25, 0xf3, 0x7e, 0x1e, 0xd8, 0x2a, 0x10, 0x51, 0xe9, 0xb6, 0xa4, 0x9f, 0x90, 0x43, 0xba, + 0x4c, 0xf4, 0xda, 0x48, 0x06, 0xad, 0x53, 0x21, 0xd1, 0xd2, 0x41, 0x18, 0x39, 0xe8, 0xb8, 0xd3, 0x5a, 0x2c, 0x11, + 0xb2, 0x69, 0x6f, 0x12, 0x2b, 0xa2, 0x73, 0x9a, 0xa3, 0x09, 0x67, 0xea, 0x74, 0xc7, 0x01, 0x74, 0x40, 0xec, 0x2f, + 0xb1, 0xde, 0x4a, 0xb3, 0xd3, 0xf5, 0x2b, 0x87, 0xef, 0xea, 0x2a, 0x41, 0x7e, 0x10, 0x06, 0x2f, 0xac, 0x59, 0x5f, + 0xc9, 0xde, 0xfd, 0x97, 0xd8, 0x8a, 0xec, 0xcf, 0xaa, 0xa4, 0xf2, 0x14, 0x6a, 0x9c, 0x5b, 0x5f, 0x27, 0x66, 0x86, + 0x16, 0x55, 0xc5, 0x81, 0x21, 0xd5, 0x0f, 0x94, 0xc2, 0xae, 0x50, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x39, + 0x39, 0x2f, 0x6b, 0xab, 0x5c, 0xc8, 0x8d, 0x0d, 0xd3, 0x67, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x39, 0x17, 0x97, + 0x29, 0x59, 0x30, 0x4f, 0x94, 0x3a, 0x5a, 0x72, 0x40, 0xdb, 0x3d, 0xf4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e, + 0x45, 0x78, 0xda, 0x28, 0x9f, 0xa4, 0xd1, 0x01, 0x78, 0xa1, 0x09, 0x49, 0x4e, 0xb8, 0x69, 0x8b, 0x16, 0xc3, 0x09, + 0xc3, 0x10, 0xb8, 0xb2, 0x27, 0x4c, 0xd9, 0xb3, 0x86, 0x78, 0x8b, 0x81, 0xd9, 0x6a, 0xd8, 0xcb, 0x66, 0xf7, 0x9a, + 0xf9, 0x0f, 0x6b, 0x04, 0xb2, 0x6d, 0xaa, 0xea, 0xca, 0xc6, 0xbb, 0x14, 0x91, 0x18, 0x61, 0x5b, 0x35, 0xb6, 0xb4, + 0xf5, 0x7b, 0x0d, 0xf7, 0xba, 0x72, 0xcc, 0x6b, 0x4a, 0xb5, 0xa1, 0x87, 0x95, 0x9b, 0xc3, 0x4c, 0x47, 0x5e, 0xac, + 0xa0, 0xdb, 0x13, 0x41, 0x21, 0x70, 0x22, 0xb4, 0x3d, 0xa8, 0xb8, 0x81, 0x48, 0xc9, 0x95, 0x56, 0xcd, 0xe6, 0xc9, + 0x48, 0x02, 0x0b, 0x2e, 0x2c, 0x97, 0x7c, 0x74, 0x1e, 0x27, 0x49, 0x55, 0xfa, 0xa7, 0x0a, 0x78, 0x31, 0xec, 0x6d, + 0xa2, 0x5d, 0x60, 0x34, 0x57, 0x20, 0xb8, 0xda, 0x08, 0x7b, 0xef, 0xb8, 0xd5, 0xba, 0x8b, 0x88, 0x23, 0x37, 0xa3, + 0x11, 0x50, 0x8f, 0x11, 0x56, 0xcd, 0xda, 0xfb, 0xcf, 0x30, 0xa4, 0x66, 0xe0, 0x83, 0xea, 0x8c, 0x8a, 0x3f, 0xcb, + 0x9e, 0xfa, 0x4c, 0xf4, 0x6e, 0x55, 0x5d, 0xcd, 0x80, 0x8a, 0x0a, 0x7c, 0x98, 0x21, 0x96, 0xb6, 0x0a, 0x04, 0xe4, + 0x7a, 0x58, 0x94, 0x02, 0x26, 0x69, 0xb0, 0xa0, 0x14, 0x58, 0x6b, 0x65, 0xf7, 0xf2, 0xb6, 0x60, 0x0e, 0x85, 0xc2, + 0x45, 0xff, 0x27, 0xd9, 0x74, 0x86, 0x96, 0x59, 0x83, 0xa9, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa2, 0xac, 0xd6, + 0x87, 0x76, 0x64, 0x8d, 0x9f, 0xb4, 0xa3, 0x0c, 0x0e, 0xd5, 0x5c, 0x17, 0xd5, 0xed, 0xe6, 0xa6, 0x88, 0x59, 0xc5, + 0xe3, 0x3e, 0xe9, 0x6d, 0x6d, 0x4d, 0x7a, 0x9a, 0x06, 0x24, 0x93, 0x24, 0xc3, 0x9b, 0x0c, 0x50, 0x56, 0xc4, 0x59, + 0x94, 0x0d, 0xf2, 0x2d, 0xca, 0x12, 0xd7, 0xef, 0xbb, 0xde, 0x7e, 0xcd, 0xb3, 0xf6, 0xf6, 0xaf, 0x77, 0x91, 0xab, + 0x3a, 0xe9, 0x41, 0x1e, 0x0d, 0xa0, 0x68, 0xc9, 0xa6, 0x0c, 0x17, 0xd3, 0x6c, 0xc4, 0x02, 0x1b, 0xba, 0xa7, 0x76, + 0xa9, 0xb4, 0x32, 0x6c, 0x8e, 0x94, 0x39, 0x8b, 0x77, 0xf5, 0x48, 0x6a, 0xb0, 0x0f, 0x2c, 0xa0, 0xcd, 0x85, 0xef, + 0xc3, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0xa1, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xcb, 0x48, 0x16, 0xab, 0xa1, 0xa4, + 0xbe, 0xef, 0x7f, 0x1f, 0xdc, 0xde, 0xa3, 0xf2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0x8a, 0x88, 0xbd, 0x34, + 0xa7, 0x03, 0x8d, 0x53, 0x00, 0x65, 0x0e, 0x41, 0x93, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0xfd, 0x97, 0xca, 0xa5, 0xce, + 0xc0, 0x85, 0x00, 0x27, 0x3f, 0x89, 0x79, 0x0b, 0xcf, 0x23, 0x6d, 0x6f, 0x21, 0x2a, 0x30, 0xae, 0x48, 0x71, 0xe9, + 0x52, 0x79, 0x83, 0xde, 0xc7, 0xf0, 0x18, 0x9a, 0xad, 0xaf, 0x2f, 0x9c, 0x57, 0x11, 0x9f, 0xf8, 0x79, 0x94, 0x8e, + 0xb2, 0xa9, 0xe3, 0x6e, 0xda, 0xb6, 0xeb, 0x17, 0xe4, 0x89, 0x7c, 0xe5, 0x96, 0xeb, 0xc7, 0xde, 0x84, 0x85, 0x76, + 0xdf, 0xde, 0xfc, 0xe8, 0xbd, 0x67, 0xe1, 0xf1, 0xee, 0xfa, 0x62, 0xc2, 0xca, 0xbd, 0x63, 0xef, 0x42, 0xc7, 0xdc, + 0xbd, 0xb7, 0x28, 0x65, 0xa0, 0x57, 0xb8, 0x77, 0x21, 0xc1, 0x00, 0x76, 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0xf7, 0x74, + 0x20, 0x22, 0xe3, 0xb4, 0x57, 0x57, 0x76, 0x46, 0x11, 0x03, 0x7b, 0x4d, 0x3b, 0xab, 0x1b, 0x1b, 0x95, 0x9a, 0xaf, + 0x4a, 0xbd, 0x21, 0x0b, 0x6b, 0x9e, 0xba, 0x77, 0x48, 0x47, 0x2b, 0xf5, 0x8d, 0x3c, 0x64, 0xa4, 0x34, 0x97, 0xed, + 0x04, 0xc7, 0xd8, 0xe2, 0xab, 0xb7, 0xf5, 0x91, 0x88, 0x52, 0xf8, 0x31, 0x58, 0x2f, 0x11, 0xa8, 0x6f, 0x70, 0x70, + 0xbc, 0xc3, 0x70, 0x6b, 0xd7, 0xe9, 0x07, 0xce, 0x5a, 0xab, 0x75, 0xf5, 0xf3, 0xd6, 0xd1, 0x2f, 0x51, 0xeb, 0xf7, + 0xfd, 0xd6, 0x4f, 0x03, 0xf7, 0xca, 0xf9, 0x79, 0xab, 0x7f, 0x24, 0xdf, 0x8e, 0x7e, 0xd9, 0xfb, 0xb9, 0x18, 0x7c, + 0x29, 0x0a, 0xd7, 0x5d, 0x77, 0xeb, 0x14, 0x3c, 0xa5, 0x70, 0xab, 0xd5, 0xda, 0x83, 0xa7, 0x39, 0x3c, 0xe1, 0xcf, + 0x73, 0xf8, 0x71, 0x75, 0x64, 0xfd, 0x9b, 0x9f, 0xd3, 0x7f, 0xfb, 0x73, 0x3e, 0xc0, 0x31, 0x8f, 0x7e, 0xf9, 0xb9, + 0xb0, 0xef, 0xed, 0x85, 0x5b, 0x83, 0x4d, 0xd7, 0xd1, 0x35, 0x5f, 0x86, 0xd5, 0x23, 0xb4, 0x3a, 0xfa, 0x45, 0xbe, + 0xd9, 0xf7, 0x8e, 0x77, 0xf7, 0xc2, 0xc1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x1d, 0xe7, 0x99, + 0xc1, 0xe8, 0xf7, 0xe0, 0xe7, 0x29, 0xfc, 0xb4, 0xe1, 0xe7, 0x19, 0xfc, 0xfc, 0x05, 0xba, 0x89, 0xf8, 0xdb, 0x15, + 0xc5, 0x42, 0xae, 0xf0, 0xc0, 0x22, 0x82, 0x55, 0x70, 0xd7, 0xb7, 0x62, 0x6f, 0x4c, 0x44, 0x83, 0x7d, 0xe8, 0xfb, + 0x3e, 0x86, 0x49, 0x9d, 0xc5, 0xc7, 0x75, 0x58, 0x74, 0xe4, 0x9c, 0xf5, 0x80, 0x79, 0x22, 0x72, 0x50, 0x04, 0x5c, + 0x9c, 0xad, 0x16, 0x78, 0xb8, 0xea, 0x8d, 0xc2, 0x31, 0x73, 0xc0, 0x28, 0x78, 0xce, 0xf0, 0xa1, 0xeb, 0x7a, 0xcf, + 0xe4, 0x99, 0x21, 0xee, 0x73, 0xc1, 0x5a, 0x69, 0x26, 0x4c, 0x1a, 0xdb, 0xf5, 0x66, 0x2b, 0x2a, 0x61, 0x5b, 0xa7, + 0xa7, 0x50, 0xb7, 0x2e, 0x0e, 0xda, 0xbe, 0x67, 0xd1, 0x27, 0xdc, 0x92, 0xaf, 0x8d, 0x43, 0xe0, 0x25, 0x4b, 0xbe, + 0x69, 0x34, 0x1a, 0x36, 0xa2, 0x70, 0xc7, 0x1e, 0x33, 0x98, 0x61, 0xc9, 0x44, 0xe4, 0xa4, 0x34, 0x85, 0x65, 0x0b, + 0x93, 0xbf, 0x8d, 0x72, 0xbe, 0x5e, 0x19, 0xb6, 0x61, 0xcd, 0x92, 0x6d, 0x5a, 0xfa, 0x77, 0x98, 0x02, 0x4d, 0x4b, + 0x3a, 0xff, 0x30, 0xc7, 0x0f, 0x53, 0x42, 0xeb, 0xb5, 0xc3, 0xc1, 0x43, 0x2f, 0x40, 0xee, 0x88, 0x7e, 0xce, 0x5b, + 0x54, 0x63, 0xf0, 0x57, 0x86, 0x19, 0x3c, 0x31, 0x1f, 0x86, 0x68, 0x16, 0xa5, 0x0e, 0x6e, 0xa5, 0x28, 0xee, 0x5f, + 0xe0, 0xce, 0x48, 0x4b, 0xef, 0x20, 0x54, 0x3b, 0xe6, 0x30, 0x67, 0xec, 0xfb, 0x28, 0xf9, 0xc4, 0x72, 0xe7, 0xc2, + 0xeb, 0x74, 0xbf, 0xa2, 0xce, 0x1e, 0xda, 0x66, 0xaf, 0xaa, 0x63, 0x34, 0x65, 0x16, 0xa8, 0x23, 0xc2, 0x56, 0xc7, + 0xcb, 0x31, 0xaa, 0x85, 0x24, 0x28, 0xbc, 0x2c, 0xec, 0x12, 0x87, 0xdb, 0xbb, 0xc5, 0xd9, 0xe9, 0x9e, 0x1d, 0xd8, + 0x36, 0x58, 0xfc, 0x87, 0x14, 0xb6, 0x12, 0x86, 0x05, 0x18, 0x64, 0xbb, 0x71, 0x8f, 0x6f, 0x6e, 0x56, 0x01, 0x27, + 0x3c, 0x48, 0xa7, 0xee, 0x89, 0x17, 0x79, 0x93, 0x10, 0x06, 0x1c, 0x42, 0x33, 0xec, 0xd2, 0x1b, 0xee, 0xc6, 0x72, + 0x1a, 0x8c, 0x85, 0xf8, 0x49, 0x54, 0xf0, 0x17, 0x18, 0x8f, 0x08, 0x87, 0x68, 0xec, 0xfb, 0xec, 0x82, 0x0d, 0x95, + 0x9d, 0x01, 0x84, 0x8a, 0xdc, 0x9e, 0x3b, 0x0c, 0x8d, 0x66, 0x30, 0x77, 0x18, 0x1e, 0xf6, 0x6d, 0xd8, 0x4b, 0xb0, + 0x2b, 0xc3, 0xe8, 0xa8, 0x33, 0xe8, 0xa7, 0x21, 0xc8, 0x5a, 0x4d, 0x5b, 0x59, 0x34, 0xaf, 0x15, 0x75, 0x07, 0x7d, + 0xe7, 0x0c, 0x8c, 0x74, 0xb0, 0xc5, 0x1d, 0x7c, 0xc3, 0x08, 0x45, 0x11, 0xbe, 0x63, 0xa7, 0xcf, 0x2e, 0x66, 0x8e, + 0xbd, 0xbb, 0x65, 0x6f, 0x62, 0xa9, 0x67, 0x03, 0x7b, 0xc1, 0xdc, 0xe1, 0xb9, 0x6b, 0x76, 0xde, 0x1e, 0x20, 0xa8, + 0x58, 0x88, 0x93, 0x9f, 0xf7, 0xed, 0x3d, 0x31, 0x75, 0x1b, 0x06, 0x4d, 0xe5, 0xf2, 0xe3, 0x8a, 0x1e, 0x12, 0xaa, + 0xaa, 0xab, 0x82, 0x0e, 0xca, 0xba, 0x81, 0x33, 0x31, 0x91, 0x68, 0xe1, 0x64, 0x92, 0x0a, 0xe0, 0xf0, 0x60, 0x33, + 0x98, 0xd4, 0xe8, 0xb6, 0x3d, 0xe8, 0x9f, 0x07, 0xf7, 0xec, 0x7b, 0xea, 0xe5, 0x94, 0x05, 0xe0, 0x5d, 0xd0, 0xf4, + 0xa7, 0xa8, 0x45, 0xe0, 0xe7, 0x8c, 0x01, 0x92, 0xe7, 0x54, 0x34, 0x92, 0x45, 0x73, 0x2c, 0x3a, 0x0c, 0x10, 0x54, + 0xaf, 0xd0, 0xd6, 0x9f, 0x58, 0x93, 0x51, 0x48, 0xb0, 0x6f, 0x6c, 0xc0, 0xd2, 0x6c, 0x76, 0x06, 0x78, 0xde, 0x90, + 0xf3, 0xe2, 0xfb, 0x98, 0x83, 0x4a, 0xd8, 0xda, 0xb3, 0xdd, 0xbe, 0x6d, 0xe1, 0xd2, 0xf6, 0xb2, 0xcd, 0x50, 0x50, + 0x38, 0xde, 0x7c, 0xcf, 0x82, 0xc9, 0x5e, 0xd8, 0xee, 0x3b, 0xb9, 0x50, 0x1d, 0x09, 0x9e, 0x5b, 0x0a, 0x09, 0xde, + 0xf6, 0x26, 0x20, 0xd0, 0x91, 0x73, 0xd7, 0xed, 0x4d, 0x55, 0x08, 0x45, 0x1f, 0x37, 0x47, 0x6e, 0x10, 0xc3, 0x0f, + 0xa7, 0x85, 0x4c, 0x33, 0xd1, 0x7d, 0xb5, 0x66, 0x76, 0x83, 0x91, 0xb2, 0xc8, 0x93, 0x30, 0xdb, 0x74, 0x30, 0x42, + 0x0b, 0x92, 0x76, 0xb7, 0x0f, 0x30, 0x6c, 0x3a, 0x8a, 0xd3, 0xb6, 0x14, 0xab, 0x29, 0xfb, 0xfc, 0xa8, 0x5a, 0x0e, + 0xd6, 0x8f, 0x98, 0x5f, 0x69, 0x1f, 0x00, 0x2b, 0x48, 0xbc, 0x7c, 0xa0, 0xce, 0xbc, 0x9e, 0xd7, 0xce, 0xb7, 0x16, + 0x4a, 0x14, 0x31, 0xcf, 0x90, 0x50, 0xbc, 0xd4, 0x6e, 0x98, 0x30, 0xb7, 0x67, 0x48, 0x0c, 0xcd, 0xf2, 0x61, 0x1b, + 0x98, 0x5e, 0x05, 0xd8, 0x53, 0x73, 0x5b, 0x24, 0x61, 0xd5, 0xdc, 0x3b, 0x02, 0xd6, 0x1e, 0x84, 0xaf, 0xc4, 0x89, + 0x63, 0x4f, 0x45, 0xf3, 0x59, 0x12, 0x3e, 0x6f, 0x1c, 0x17, 0x47, 0x78, 0x22, 0x74, 0xe0, 0x0f, 0xe7, 0x39, 0xc8, + 0x03, 0xfe, 0x1a, 0x2c, 0x83, 0x50, 0x36, 0x45, 0x47, 0x0f, 0x8f, 0x80, 0x3d, 0x42, 0xbc, 0x11, 0x36, 0x37, 0xaa, + 0xd1, 0xa2, 0x24, 0xe3, 0x85, 0x0e, 0x86, 0x7b, 0x5c, 0xba, 0xf6, 0x28, 0x18, 0xe4, 0x89, 0xb1, 0x83, 0x67, 0xfe, + 0xfe, 0x10, 0xab, 0x71, 0x82, 0xc2, 0x2d, 0x69, 0xb7, 0x55, 0xe2, 0xef, 0xc0, 0x4f, 0x41, 0x82, 0x63, 0x1d, 0xf8, + 0x59, 0x1b, 0x1b, 0x89, 0x44, 0x6a, 0x37, 0xed, 0xd1, 0x49, 0x04, 0xc6, 0x83, 0x73, 0x3f, 0x85, 0x6a, 0x24, 0x11, + 0x15, 0xe5, 0x68, 0x81, 0x9a, 0xa7, 0x6a, 0x15, 0x7c, 0x47, 0x66, 0x04, 0x9e, 0x63, 0xd8, 0x9a, 0xfc, 0x54, 0xdd, + 0x58, 0xc4, 0xf2, 0x5d, 0x97, 0x8e, 0xb6, 0xf0, 0x00, 0x52, 0x30, 0x9a, 0x60, 0x18, 0x97, 0x82, 0x92, 0x15, 0xff, + 0x7d, 0x34, 0x62, 0xe5, 0x93, 0xa3, 0x6c, 0x73, 0x73, 0x20, 0xce, 0x2d, 0x88, 0x71, 0xb8, 0x11, 0x5d, 0x8d, 0x2b, + 0x00, 0xea, 0xd3, 0x39, 0x71, 0x3d, 0x30, 0xad, 0x58, 0xd3, 0xa5, 0xd8, 0x27, 0x87, 0x19, 0x80, 0x82, 0x5b, 0xce, + 0x91, 0xdf, 0xff, 0xcb, 0x00, 0xdc, 0x63, 0xff, 0x4b, 0x77, 0x4b, 0x09, 0x9a, 0x9e, 0x3c, 0x53, 0x5c, 0xd0, 0x19, + 0x6b, 0xc7, 0xa3, 0xd8, 0x68, 0x50, 0x78, 0x29, 0x60, 0x00, 0xda, 0x1c, 0x64, 0x42, 0xc5, 0x41, 0xc8, 0x51, 0x81, + 0xed, 0xe3, 0xe6, 0xe7, 0xb8, 0xb3, 0x9f, 0x82, 0x85, 0xd7, 0xd7, 0x6f, 0x8f, 0xe1, 0xed, 0x2f, 0xfa, 0xed, 0x25, + 0x0b, 0x7e, 0x2d, 0x65, 0xe8, 0xbe, 0x36, 0xc5, 0x03, 0x35, 0x45, 0x29, 0x96, 0xc8, 0xa0, 0x21, 0x73, 0xf3, 0xa5, + 0x98, 0x0d, 0x77, 0x4b, 0x54, 0x3b, 0x52, 0x74, 0xe5, 0x3e, 0x8f, 0x4e, 0x91, 0xb8, 0xae, 0x49, 0x0a, 0x23, 0x97, + 0xc0, 0x44, 0xb8, 0xe2, 0x5b, 0x62, 0xce, 0xbd, 0x36, 0xd8, 0xe0, 0xb5, 0xbc, 0x03, 0xb4, 0xef, 0xd8, 0x74, 0xc6, + 0x2f, 0x0f, 0x48, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0xb7, 0x7b, 0xf1, 0x2e, 0xef, 0xc5, 0x20, 0xd5, 0x73, + 0xc5, 0x62, 0xb8, 0x57, 0xbd, 0xb7, 0x18, 0xa5, 0x34, 0x99, 0xc9, 0xab, 0xa1, 0xd7, 0x95, 0xe8, 0x6d, 0x6e, 0x02, + 0x82, 0x3d, 0xa3, 0x2b, 0x17, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, 0x7a, 0x54, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, + 0x43, 0xc1, 0xc1, 0xdc, 0x95, 0xa3, 0xe2, 0x98, 0xd8, 0x5d, 0x26, 0xec, 0x00, 0x66, 0xc4, 0xe5, 0xad, 0x8e, 0x88, + 0x0e, 0x8b, 0xfe, 0x3a, 0xbe, 0xfd, 0xd1, 0x63, 0x9b, 0x1d, 0x17, 0x34, 0x48, 0x6d, 0xac, 0x87, 0xd5, 0x58, 0x50, + 0x1f, 0x7e, 0xd4, 0x54, 0x2a, 0x8b, 0xcd, 0xcd, 0xb2, 0x7e, 0x54, 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0xa2, 0x99, + 0x0d, 0xc2, 0x81, 0x88, 0x09, 0x14, 0x68, 0x69, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0x94, 0x4f, 0x21, 0xf3, 0xe2, + 0xb2, 0xd4, 0xa9, 0x2f, 0x32, 0x1e, 0x19, 0xe2, 0xa9, 0x27, 0x19, 0x2b, 0xa0, 0x60, 0xbd, 0xd4, 0x4b, 0x68, 0x89, + 0x00, 0xf3, 0x67, 0x2a, 0x87, 0x46, 0x58, 0x20, 0x51, 0x68, 0x98, 0x25, 0xca, 0xf8, 0x2c, 0xc2, 0x18, 0xb4, 0xfd, + 0x93, 0x5a, 0xec, 0xab, 0x50, 0x46, 0x47, 0x71, 0x94, 0x0f, 0x02, 0xaa, 0x9f, 0x4b, 0x09, 0x36, 0x09, 0xdf, 0x03, + 0x1b, 0x55, 0x8e, 0x27, 0x09, 0xc2, 0xa7, 0x71, 0xce, 0xc8, 0x53, 0x58, 0x97, 0x30, 0x4b, 0xd3, 0x36, 0x52, 0xed, + 0x22, 0x33, 0x08, 0xe5, 0xc2, 0xfc, 0x13, 0xe3, 0xec, 0x22, 0x0b, 0x97, 0x5a, 0x83, 0xf9, 0xf1, 0xc6, 0x04, 0x28, + 0xbb, 0xba, 0xca, 0x84, 0x8f, 0x1b, 0x91, 0xbd, 0xa1, 0x2b, 0x26, 0x7d, 0x85, 0x54, 0xe0, 0x44, 0x64, 0xf1, 0xd0, + 0x19, 0x0a, 0x8d, 0x70, 0x48, 0xa7, 0xc8, 0xb9, 0x6b, 0x6c, 0xfa, 0xbc, 0xaf, 0x7d, 0xa3, 0x34, 0x74, 0x12, 0x10, + 0x02, 0x02, 0x77, 0xc3, 0x9a, 0x4a, 0xfb, 0x69, 0x90, 0x50, 0x29, 0xfa, 0x39, 0x80, 0x7f, 0x18, 0x49, 0x0a, 0x80, + 0xfd, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, 0x2e, 0x00, 0xcd, 0x75, 0x80, 0x2b, 0xe1, 0x0b, 0x03, 0x15, 0xa6, 0xa7, + 0x59, 0x79, 0x29, 0x94, 0xc8, 0xbb, 0x15, 0x29, 0x6b, 0x24, 0x93, 0xcf, 0xd0, 0xe1, 0x53, 0xde, 0xf5, 0x6b, 0x89, + 0x87, 0x2e, 0x78, 0x0a, 0xcb, 0xaa, 0x9e, 0x5f, 0x85, 0x9c, 0x9c, 0x6b, 0xd0, 0x15, 0x52, 0xe8, 0x2f, 0x39, 0xc9, + 0xfb, 0xaf, 0xfc, 0xaa, 0x96, 0x1a, 0x43, 0xd9, 0xfb, 0x55, 0xcd, 0xb0, 0xbc, 0x9c, 0x55, 0x61, 0x0a, 0x02, 0x6e, + 0xc1, 0x92, 0x60, 0x21, 0x35, 0x04, 0x58, 0xd8, 0x1e, 0x69, 0xa5, 0x20, 0x2f, 0x75, 0x78, 0xe7, 0x29, 0x58, 0x01, + 0xc6, 0xa1, 0x96, 0x4a, 0xa6, 0x91, 0xc4, 0x97, 0x4a, 0x14, 0x98, 0xf2, 0x60, 0x08, 0x7e, 0x6a, 0xf3, 0xa4, 0xeb, + 0xc2, 0xf5, 0xe3, 0x29, 0xa6, 0xf6, 0x10, 0xe8, 0xb1, 0xb7, 0x06, 0xa6, 0x44, 0x5d, 0x87, 0x15, 0xc4, 0xa1, 0x59, + 0x4d, 0xb3, 0x80, 0x19, 0xd3, 0x06, 0x2d, 0xd9, 0x06, 0x5b, 0x2e, 0x07, 0xfb, 0x48, 0x6c, 0xcf, 0x6a, 0x05, 0x84, + 0xae, 0x41, 0x03, 0x43, 0xee, 0x52, 0xa1, 0x85, 0x79, 0xaf, 0x4b, 0x45, 0xb8, 0x3f, 0xfb, 0x5c, 0x5a, 0xc1, 0x99, + 0x97, 0xd1, 0xc0, 0x07, 0xf1, 0x49, 0x82, 0x89, 0x2f, 0x88, 0x15, 0xd8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x38, 0x15, + 0x17, 0x29, 0x83, 0x65, 0x45, 0xa9, 0x0d, 0x3f, 0xa4, 0xc8, 0xd6, 0x5d, 0x1e, 0xe8, 0x2e, 0xc4, 0x02, 0xd8, 0xe9, + 0x57, 0x8c, 0x7c, 0xcb, 0x7a, 0x19, 0x30, 0x38, 0xd3, 0x1a, 0x07, 0x81, 0xdf, 0xdc, 0x4c, 0x06, 0x65, 0x4a, 0x6c, + 0xd7, 0x64, 0x75, 0x01, 0x39, 0x0c, 0xd5, 0xc4, 0x1d, 0x84, 0xa5, 0xb2, 0xc7, 0x8b, 0x72, 0x8a, 0xcb, 0xa5, 0x2c, + 0xe4, 0xe6, 0x79, 0x35, 0xcd, 0xe7, 0x56, 0x9a, 0x4d, 0xc7, 0x5b, 0xf1, 0x45, 0xc1, 0xdf, 0x77, 0x62, 0x69, 0xd5, + 0x53, 0x6a, 0x85, 0x47, 0x99, 0x5b, 0xb2, 0x4e, 0x49, 0xad, 0xae, 0x1b, 0xa8, 0x46, 0x78, 0x9a, 0x86, 0x8d, 0x40, + 0x88, 0x09, 0x2e, 0x7e, 0xdd, 0x64, 0x62, 0xda, 0x5b, 0x42, 0xea, 0x08, 0xbb, 0x87, 0x72, 0x82, 0xbb, 0x9a, 0x67, + 0x9f, 0x87, 0xb3, 0xeb, 0x99, 0x7b, 0xdf, 0x60, 0xee, 0xc7, 0x21, 0x37, 0x18, 0x3d, 0x96, 0x09, 0x3f, 0x32, 0xf6, + 0x91, 0xab, 0xaa, 0x27, 0xa7, 0x61, 0x25, 0xb2, 0xc4, 0x93, 0x71, 0xd4, 0x61, 0x9c, 0x8a, 0xd6, 0x04, 0xd9, 0xd5, + 0x55, 0x61, 0xee, 0x05, 0x0a, 0x9a, 0x7a, 0xbc, 0x1e, 0xa7, 0xad, 0xd8, 0xd9, 0x88, 0x44, 0xee, 0xbf, 0xaa, 0x45, + 0x22, 0x2b, 0x3e, 0xc7, 0x91, 0xae, 0x39, 0xc8, 0x7d, 0x72, 0xba, 0xbc, 0x49, 0x85, 0x6e, 0xd1, 0x68, 0x1b, 0x7b, + 0x54, 0x1f, 0x48, 0xea, 0x19, 0x15, 0x58, 0xd5, 0xd8, 0x1b, 0x1b, 0x1d, 0x91, 0x6e, 0xa9, 0x14, 0x1b, 0x2c, 0x2d, + 0x8c, 0x66, 0x8c, 0x82, 0x41, 0x49, 0x91, 0x81, 0x1a, 0xe5, 0xd7, 0x08, 0x86, 0x7d, 0x6a, 0x00, 0x8a, 0x73, 0x75, + 0xf5, 0xe3, 0x52, 0xb2, 0x85, 0x80, 0xc4, 0x5d, 0x30, 0x10, 0x6b, 0x82, 0x99, 0x91, 0x4f, 0xde, 0x03, 0xe7, 0xf5, + 0x19, 0xfa, 0x08, 0xe0, 0x17, 0x88, 0x4d, 0x0f, 0x26, 0xb6, 0x4d, 0x44, 0xd1, 0x67, 0x03, 0xcf, 0x01, 0xd8, 0x59, + 0x15, 0x1a, 0x7d, 0x57, 0xa5, 0x80, 0x21, 0x1b, 0xb8, 0x01, 0xab, 0xc2, 0x72, 0xfb, 0xcf, 0xc1, 0x6d, 0x80, 0xd7, + 0x67, 0xb2, 0xf9, 0x3a, 0xe6, 0x09, 0x56, 0x67, 0x17, 0x7e, 0x65, 0x59, 0x8b, 0x73, 0xa7, 0xc3, 0x46, 0xbd, 0xa2, + 0x84, 0xa8, 0x3d, 0xc0, 0xda, 0x43, 0x8c, 0xb0, 0x88, 0xf7, 0x57, 0xf8, 0xae, 0xc7, 0x2d, 0xf7, 0x35, 0x5a, 0x84, + 0xe9, 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, 0xbd, 0x3c, 0x10, 0xb1, 0xe0, 0x0a, 0x47, 0x56, 0x85, 0x14, + 0x1b, 0x48, 0xd2, 0xd3, 0x3e, 0x1d, 0xb0, 0xaf, 0x37, 0x7b, 0x01, 0x65, 0xde, 0x57, 0xa4, 0x92, 0x90, 0xd2, 0xec, + 0x86, 0x48, 0x12, 0xd6, 0x8a, 0x3c, 0x75, 0x3e, 0x70, 0xb4, 0xcf, 0xad, 0x24, 0x82, 0x11, 0x9c, 0x84, 0xe9, 0x58, + 0x79, 0xd8, 0x14, 0xe0, 0x2a, 0x3a, 0x62, 0xfa, 0x26, 0x20, 0xbf, 0x19, 0xc8, 0xed, 0xa5, 0xe4, 0xda, 0x5c, 0xc3, + 0xf0, 0x04, 0x09, 0x56, 0x45, 0x22, 0xf0, 0x88, 0x1a, 0x70, 0xcc, 0x57, 0x79, 0x1e, 0x60, 0xc2, 0xd7, 0xf6, 0x26, + 0x00, 0x94, 0x93, 0xab, 0xe2, 0x2c, 0x05, 0xba, 0x01, 0xcb, 0xd5, 0x71, 0x6a, 0x54, 0x24, 0x2e, 0x6e, 0x4c, 0x57, + 0xb7, 0xf4, 0xa7, 0x68, 0x39, 0x93, 0x21, 0xa6, 0x83, 0x20, 0x20, 0x53, 0xdf, 0x31, 0x47, 0xc8, 0x5c, 0x61, 0x7d, + 0xce, 0x9c, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x49, 0x2d, 0x5e, 0xa7, 0x4d, 0x29, 0x11, 0x93, 0x12, 0x73, 0x5d, + 0xa4, 0x62, 0x33, 0x25, 0xee, 0xdc, 0xfa, 0x46, 0x0b, 0x69, 0xa3, 0xad, 0x8b, 0x1c, 0x6c, 0x56, 0xc9, 0x7b, 0x02, + 0xe3, 0xb9, 0x20, 0x7c, 0xf9, 0x8a, 0x92, 0x74, 0x98, 0x63, 0x22, 0x58, 0xbd, 0x98, 0x8a, 0xfc, 0x9d, 0xa3, 0xd3, + 0xec, 0x0d, 0x7a, 0x90, 0x7a, 0x03, 0x89, 0x59, 0x13, 0xdf, 0x85, 0x34, 0xd4, 0x11, 0x02, 0x95, 0x51, 0x2d, 0xd3, + 0x71, 0x62, 0x15, 0xbe, 0x11, 0x7c, 0xf5, 0x56, 0x1f, 0xe7, 0x1b, 0xcf, 0x8d, 0xd5, 0x08, 0x62, 0xf0, 0x16, 0xf2, + 0x81, 0x27, 0x45, 0x38, 0x10, 0x2e, 0xdf, 0xdc, 0xec, 0xe5, 0xbb, 0xbc, 0x0a, 0x91, 0x54, 0x30, 0xc6, 0x98, 0x51, + 0x8c, 0x7b, 0xa2, 0xa6, 0x16, 0x73, 0x18, 0x58, 0xb6, 0x0e, 0x73, 0x3c, 0x00, 0x80, 0x96, 0xa6, 0xf4, 0xaa, 0xa9, + 0x50, 0x79, 0x9e, 0x4b, 0xf8, 0x54, 0x87, 0xa8, 0xaa, 0xf1, 0xdb, 0xd5, 0x19, 0x28, 0x04, 0xf7, 0x5a, 0xc7, 0xc3, + 0x43, 0x08, 0x58, 0x45, 0x21, 0x0b, 0xf4, 0x06, 0xed, 0x55, 0x89, 0x50, 0xcc, 0x9c, 0xac, 0xc7, 0x0c, 0x27, 0x15, + 0x6c, 0xa1, 0x12, 0x96, 0x4a, 0x0b, 0xfc, 0x6a, 0x23, 0x34, 0x4f, 0x19, 0xf7, 0x5f, 0x55, 0x38, 0x83, 0xfe, 0x60, + 0xde, 0x32, 0xa3, 0xbe, 0x5d, 0x3a, 0x91, 0xa9, 0xc0, 0xc4, 0xcd, 0x2c, 0xb5, 0xdf, 0xaf, 0xab, 0xb4, 0x9f, 0x57, + 0xc8, 0x7d, 0x4e, 0x9a, 0xaf, 0x73, 0x07, 0xcd, 0x27, 0xc3, 0xfd, 0x4a, 0xf9, 0xa1, 0x85, 0x51, 0x53, 0x7e, 0x79, + 0x5d, 0xf9, 0x15, 0x9e, 0x0a, 0x6f, 0xed, 0x75, 0x51, 0xe8, 0xa2, 0x3e, 0x07, 0x43, 0x48, 0x3f, 0x82, 0x6b, 0x68, + 0xf0, 0xa0, 0x48, 0x16, 0x8b, 0xb5, 0x0b, 0xe2, 0xfa, 0x98, 0x53, 0xed, 0x50, 0xc6, 0x18, 0xf1, 0xb4, 0xe4, 0x20, + 0xc9, 0xe0, 0x60, 0xfc, 0x06, 0x06, 0xc4, 0xa4, 0x24, 0xa4, 0x43, 0xe8, 0xac, 0xcc, 0x44, 0x54, 0xee, 0xe2, 0xed, + 0xc6, 0x65, 0x4d, 0xa1, 0x08, 0x3b, 0xc1, 0x4c, 0xa5, 0x54, 0x10, 0x48, 0x93, 0x6f, 0xad, 0x53, 0x0b, 0x86, 0x16, + 0xae, 0xa9, 0x80, 0xbc, 0xb6, 0xeb, 0x41, 0x93, 0xf7, 0x14, 0x43, 0x5f, 0xa5, 0x46, 0xbc, 0xcc, 0xe0, 0x6b, 0xd8, + 0xfc, 0x35, 0x51, 0x92, 0x87, 0x4c, 0xc4, 0x5e, 0xc1, 0x27, 0x42, 0x36, 0x05, 0x3b, 0x13, 0xe8, 0x87, 0x76, 0x65, + 0x2f, 0xdd, 0x2d, 0x2a, 0x97, 0x16, 0x8d, 0xad, 0x44, 0xcd, 0x9a, 0x1f, 0xc5, 0x9b, 0x29, 0xec, 0x67, 0x8f, 0x12, + 0x08, 0x48, 0x53, 0x39, 0x49, 0x35, 0xef, 0x51, 0x3a, 0x00, 0x90, 0x60, 0xf7, 0x13, 0x58, 0xe8, 0x37, 0x25, 0x26, + 0x58, 0x54, 0x8d, 0xdd, 0x66, 0xa0, 0x35, 0x67, 0xa4, 0xf9, 0x66, 0xa8, 0xb5, 0x37, 0x95, 0xf5, 0x8c, 0xd9, 0x01, + 0xb6, 0xed, 0x6e, 0x16, 0x47, 0xe9, 0x66, 0x67, 0x60, 0x08, 0x2e, 0x3c, 0xfe, 0x4f, 0x4a, 0x4c, 0x03, 0xc9, 0xa5, + 0x6e, 0xfc, 0x84, 0x3a, 0x0c, 0xff, 0x5b, 0x90, 0x02, 0xee, 0xd7, 0x56, 0x63, 0xc9, 0xb9, 0x57, 0x1c, 0x25, 0x97, + 0x55, 0xb5, 0xab, 0x25, 0x68, 0xe8, 0x46, 0x32, 0x26, 0x8a, 0x79, 0x4e, 0x00, 0x8c, 0x62, 0xf3, 0xa7, 0x4c, 0x27, + 0x79, 0xff, 0xba, 0x32, 0xb5, 0xdb, 0xf7, 0xfd, 0x28, 0x3f, 0xa5, 0x23, 0x15, 0x95, 0xcd, 0x49, 0xcc, 0xbf, 0x2d, + 0xc0, 0x34, 0x27, 0x3e, 0xd2, 0x73, 0x0d, 0x42, 0x01, 0xbe, 0xb2, 0xa1, 0xd4, 0x6c, 0x8f, 0xff, 0xe8, 0x6c, 0xf7, + 0x25, 0x51, 0x04, 0x0b, 0x34, 0xe8, 0x72, 0x0d, 0xbe, 0x80, 0x65, 0x70, 0x47, 0xfa, 0x29, 0xf8, 0x5e, 0x5e, 0x07, + 0x9f, 0xb1, 0xff, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x4e, 0xd3, 0xb0, 0x12, 0xe2, 0x12, 0x15, 0x66, 0x15, 0xe7, + 0x8f, 0xeb, 0xbc, 0x6e, 0x5a, 0x96, 0x18, 0x94, 0x9f, 0xb9, 0x86, 0x1b, 0xdf, 0x6b, 0xe4, 0x8f, 0xef, 0x3f, 0x07, + 0xdd, 0x4e, 0xa4, 0xdd, 0xd8, 0xc8, 0xd7, 0xc8, 0x42, 0xc3, 0x7b, 0x61, 0x33, 0x68, 0x8b, 0x74, 0xc9, 0xd5, 0x33, + 0x16, 0xe3, 0x6d, 0x11, 0x2a, 0xc3, 0x07, 0x2c, 0x98, 0x01, 0x86, 0xe0, 0xb1, 0x53, 0x99, 0x7c, 0x86, 0x8d, 0xa6, + 0xd8, 0x35, 0x17, 0x06, 0x1f, 0xa8, 0xca, 0x42, 0xf2, 0x62, 0x9d, 0x6c, 0xcf, 0xce, 0xe0, 0xf9, 0x65, 0x5c, 0x00, + 0x75, 0x00, 0xfd, 0x8a, 0xca, 0x62, 0x03, 0xb9, 0xb8, 0x29, 0x6b, 0xbd, 0xa2, 0xd1, 0xe8, 0xc6, 0x2e, 0xac, 0xae, + 0xc0, 0x27, 0x51, 0x3a, 0x4a, 0xc4, 0x24, 0x66, 0x52, 0xe5, 0x8a, 0x5c, 0x1b, 0xdd, 0x4b, 0x5b, 0x34, 0xcf, 0x85, + 0x04, 0xaf, 0x08, 0xdc, 0x10, 0xfa, 0x4a, 0x5f, 0xae, 0x36, 0x50, 0xf0, 0xa8, 0xbd, 0xb9, 0x08, 0x26, 0x26, 0x1e, + 0x33, 0xa4, 0xa6, 0x5f, 0x87, 0x53, 0x2b, 0x8b, 0x25, 0x87, 0x5f, 0xe7, 0x8c, 0x35, 0x14, 0x00, 0xf1, 0xc9, 0x83, + 0xeb, 0xdd, 0xa4, 0x57, 0x4a, 0x3b, 0x28, 0x8d, 0x10, 0xdf, 0x55, 0xf8, 0xba, 0x0b, 0xc5, 0x57, 0xae, 0xba, 0xf7, + 0x35, 0x65, 0xc6, 0x05, 0xa3, 0xe7, 0x7c, 0x9a, 0x34, 0xae, 0xdd, 0xd0, 0x5d, 0x9d, 0x9f, 0xbc, 0x1f, 0x65, 0xde, + 0xc2, 0x14, 0xd8, 0x04, 0x54, 0xc1, 0x73, 0xef, 0xb5, 0x71, 0xa2, 0xfc, 0x9d, 0x79, 0xc4, 0x2b, 0x87, 0x59, 0x75, + 0x92, 0xfc, 0x5d, 0xff, 0xbb, 0xe0, 0xfa, 0x96, 0xc6, 0x09, 0x72, 0x57, 0x9d, 0x20, 0x13, 0xe5, 0xa6, 0x7a, 0xc3, + 0xed, 0xdf, 0x55, 0x20, 0x88, 0x53, 0x31, 0x7d, 0x54, 0x8e, 0xeb, 0x47, 0x0b, 0x54, 0x2a, 0x22, 0x3e, 0x55, 0xb9, + 0x2b, 0xd7, 0xa6, 0x86, 0x7a, 0x5c, 0x27, 0xb3, 0xd0, 0x34, 0x2b, 0x72, 0x29, 0x9b, 0x1e, 0x23, 0xd3, 0xec, 0x54, + 0x9b, 0xdf, 0xbd, 0xf6, 0x90, 0x8e, 0xa1, 0xb9, 0x58, 0xab, 0x05, 0xf7, 0xbb, 0x8a, 0xc2, 0xbb, 0x5e, 0x6c, 0xa4, + 0x32, 0xd4, 0xac, 0x47, 0xd1, 0xc7, 0x71, 0x9b, 0xb9, 0x3c, 0xca, 0xfe, 0xac, 0x01, 0x60, 0x3a, 0xc2, 0xa2, 0xbb, + 0xe9, 0x19, 0x7b, 0x02, 0x3d, 0x3d, 0x91, 0x41, 0xa2, 0xd7, 0x3a, 0x5f, 0xb5, 0x4a, 0x2c, 0x5d, 0x41, 0x60, 0xf7, + 0x86, 0x8c, 0x55, 0x49, 0xbb, 0xe5, 0xfa, 0xe5, 0x3c, 0x9f, 0xa7, 0x7c, 0x29, 0xcf, 0xa7, 0x66, 0xd1, 0xad, 0xb5, + 0xdd, 0x9b, 0x53, 0x43, 0xc5, 0x5c, 0xab, 0x9b, 0xfc, 0x81, 0xe9, 0x3a, 0x18, 0x6a, 0x11, 0x64, 0x56, 0xbb, 0xea, + 0x59, 0x59, 0x4e, 0xeb, 0x99, 0x1c, 0x53, 0xe1, 0x9b, 0x4a, 0x77, 0x88, 0x6e, 0x98, 0xaa, 0x99, 0x7e, 0x6a, 0x6c, + 0x0b, 0xd9, 0xe6, 0xf9, 0xe5, 0x28, 0x07, 0x4a, 0xcb, 0xfd, 0x65, 0xc2, 0xf0, 0xd3, 0xd5, 0xd5, 0x4f, 0x42, 0x4e, + 0x55, 0x1d, 0xbd, 0xc5, 0x4b, 0xdd, 0x33, 0x98, 0x96, 0xca, 0x89, 0x38, 0x61, 0xab, 0x07, 0x6f, 0xee, 0x5e, 0x01, + 0xcb, 0x09, 0x60, 0x77, 0xc2, 0x9c, 0xc6, 0x50, 0xd5, 0x06, 0xfe, 0x71, 0xf5, 0x60, 0xab, 0xf6, 0xf0, 0x8f, 0xfd, + 0x1f, 0x83, 0x1b, 0x1b, 0x1b, 0xdb, 0x78, 0xbb, 0x96, 0x08, 0xf2, 0x0a, 0x0f, 0xf4, 0xf1, 0xea, 0xa3, 0xa0, 0xe5, + 0x2a, 0xb1, 0xdd, 0x77, 0x28, 0x6c, 0x0d, 0xf2, 0x4d, 0xca, 0xa4, 0xe1, 0xbc, 0xe0, 0xd9, 0x54, 0xce, 0x50, 0xc8, + 0x6b, 0x3e, 0x0e, 0xda, 0x8e, 0xf0, 0x37, 0x70, 0x6a, 0xc7, 0xcb, 0x8b, 0x4f, 0xd0, 0x07, 0x3c, 0x5d, 0x29, 0x4d, + 0x45, 0x9c, 0x52, 0x6e, 0xd1, 0xe5, 0x3a, 0x0f, 0x46, 0x8a, 0x8b, 0x09, 0x2a, 0x1d, 0x77, 0x71, 0xe3, 0x6c, 0xe4, + 0xf4, 0x97, 0x78, 0x75, 0x91, 0x2e, 0x1f, 0x89, 0x6c, 0xd5, 0xd2, 0xfb, 0x5d, 0x9f, 0x6e, 0xdb, 0x53, 0xc6, 0x27, + 0xd9, 0x88, 0x0e, 0x66, 0x7c, 0x9c, 0x08, 0xaf, 0x4f, 0x8c, 0xf4, 0xdd, 0x22, 0x30, 0xdd, 0x1c, 0x9b, 0xfc, 0x70, + 0xbc, 0xde, 0x6c, 0xd6, 0xb8, 0xfd, 0x37, 0xce, 0x13, 0x67, 0x51, 0x62, 0x44, 0x65, 0xa1, 0xe1, 0x01, 0xad, 0x10, + 0x37, 0xef, 0x99, 0xc0, 0xb8, 0xec, 0x92, 0xa4, 0xb6, 0x1b, 0x08, 0x5c, 0xec, 0x71, 0xcc, 0x92, 0x91, 0xed, 0x41, + 0x79, 0xa0, 0x2f, 0x46, 0xd3, 0x2d, 0x60, 0x5a, 0x5e, 0x3b, 0x3b, 0x4f, 0x6d, 0xaf, 0x9a, 0x2a, 0x80, 0x59, 0xb2, + 0x3c, 0x3e, 0x45, 0xd6, 0xfd, 0x16, 0xba, 0x88, 0x01, 0x63, 0xe3, 0xca, 0x9c, 0xbb, 0x58, 0xb5, 0x22, 0xbe, 0xd1, + 0x44, 0x9a, 0xd4, 0x47, 0xd4, 0x77, 0x10, 0xd6, 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, + 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, 0xe2, 0x9a, 0x61, 0xaf, 0xb8, 0x0f, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, + 0x6f, 0xe7, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, 0x68, 0x6e, 0xb8, 0xc1, 0xef, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, + 0x92, 0xdf, 0x8d, 0x7b, 0x55, 0x63, 0x47, 0xae, 0x02, 0x88, 0xbe, 0x05, 0x5d, 0x92, 0xc3, 0x2b, 0x59, 0xae, 0x3a, + 0x43, 0xfe, 0x0d, 0xd6, 0x59, 0x2f, 0x4e, 0xc0, 0x4c, 0x9a, 0xf2, 0x12, 0x13, 0x53, 0xc4, 0xe5, 0x66, 0x19, 0xf3, + 0x34, 0x7d, 0x16, 0xed, 0xe0, 0xe4, 0x46, 0x02, 0x47, 0xec, 0x1b, 0xcb, 0xd0, 0x4c, 0xd8, 0x88, 0x89, 0x34, 0x2a, + 0xa5, 0x84, 0xf7, 0xe5, 0x52, 0x4b, 0xfe, 0x32, 0x97, 0x57, 0x5f, 0x6e, 0x13, 0x1c, 0x90, 0xd7, 0xc0, 0x72, 0x68, + 0x1c, 0xb7, 0x0c, 0x24, 0x62, 0x31, 0x20, 0x46, 0xad, 0xca, 0xe5, 0x64, 0x54, 0x27, 0xf3, 0x15, 0x72, 0xa1, 0x22, + 0x0f, 0x6e, 0x09, 0x94, 0xfc, 0x39, 0xa6, 0x0e, 0x66, 0xa5, 0x76, 0xd3, 0x62, 0x93, 0xe4, 0x3d, 0x33, 0x20, 0xb9, + 0xfa, 0x1a, 0x1e, 0x1a, 0xbf, 0x78, 0x65, 0x4e, 0x09, 0x5f, 0x94, 0xb1, 0xb4, 0x34, 0xe6, 0xd2, 0xbf, 0x90, 0xf7, + 0x69, 0x25, 0x60, 0xbf, 0x81, 0x98, 0x32, 0x70, 0x89, 0x8d, 0x0b, 0x92, 0xf2, 0x5a, 0x9e, 0xb2, 0xfb, 0x1a, 0xca, + 0x77, 0xc9, 0xa4, 0xab, 0x54, 0xd6, 0x35, 0x56, 0xdd, 0x6f, 0x73, 0x96, 0x5f, 0x1e, 0x30, 0xcc, 0x4d, 0x46, 0x83, + 0x6c, 0xc9, 0xcc, 0xa6, 0xfc, 0x6a, 0xef, 0xc6, 0xaf, 0x3c, 0x94, 0x74, 0xa8, 0x56, 0xe9, 0xe6, 0xa5, 0x1b, 0x8e, + 0x71, 0xe3, 0x86, 0x23, 0x80, 0x8d, 0x61, 0xa7, 0x8a, 0xd4, 0x3a, 0xff, 0x7d, 0x39, 0xfc, 0x44, 0x7b, 0x6d, 0xa0, + 0x77, 0xdd, 0x60, 0x65, 0x7a, 0xfa, 0x0d, 0xa8, 0x1a, 0x59, 0x42, 0x37, 0xa1, 0x8a, 0xc9, 0x48, 0x94, 0x98, 0xae, + 0x52, 0x1e, 0xf5, 0x35, 0xe2, 0x1c, 0xc4, 0x0d, 0xe5, 0x2f, 0xfe, 0x35, 0xbc, 0x3c, 0x0e, 0xd0, 0x88, 0x5a, 0x8c, + 0xb3, 0x94, 0xb7, 0xc6, 0xd1, 0x34, 0x4e, 0x2e, 0x83, 0x79, 0xdc, 0x9a, 0x66, 0x69, 0x56, 0xcc, 0x80, 0x2b, 0xbd, + 0xe2, 0x12, 0x6c, 0xf8, 0x69, 0x6b, 0x1e, 0x7b, 0xcf, 0x59, 0x72, 0xc6, 0x78, 0x3c, 0x8c, 0x3c, 0x7b, 0x3f, 0x07, + 0xf1, 0x60, 0xbd, 0x8e, 0xf2, 0x3c, 0x3b, 0xb7, 0xbd, 0x77, 0xd9, 0x09, 0x30, 0xad, 0xf7, 0xe6, 0xe2, 0xf2, 0x94, + 0xa5, 0xde, 0xfb, 0x93, 0x79, 0xca, 0xe7, 0x5e, 0x11, 0xa5, 0x45, 0xab, 0x60, 0x79, 0x3c, 0x06, 0x35, 0x91, 0x64, + 0x79, 0x0b, 0xf3, 0x9f, 0xa7, 0x2c, 0x48, 0xe2, 0xd3, 0x09, 0xb7, 0x46, 0x51, 0xfe, 0xa9, 0xd7, 0x6a, 0xcd, 0xf2, + 0x78, 0x1a, 0xe5, 0x97, 0x2d, 0x6a, 0x11, 0x7c, 0xd1, 0xde, 0x8e, 0xbe, 0x1a, 0xdf, 0xef, 0xf1, 0x1c, 0xfa, 0xc6, + 0x48, 0xc5, 0x00, 0x84, 0x8f, 0xb5, 0xbd, 0xd3, 0x9e, 0x16, 0x6b, 0xe2, 0x44, 0x29, 0x4a, 0x79, 0x79, 0xec, 0x7d, + 0x64, 0x00, 0xb7, 0x7f, 0xc2, 0x53, 0x0f, 0x7c, 0x39, 0x9e, 0xa5, 0x8b, 0xe1, 0x3c, 0x2f, 0x60, 0x80, 0x59, 0x16, + 0xa7, 0x9c, 0xe5, 0xbd, 0x93, 0x2c, 0x07, 0xb2, 0xb5, 0xf2, 0x68, 0x14, 0xcf, 0x8b, 0xe0, 0xfe, 0xec, 0xa2, 0x87, + 0xb6, 0xc2, 0x69, 0x9e, 0xcd, 0xd3, 0x91, 0x9c, 0x2b, 0x4e, 0x61, 0x63, 0xc4, 0xdc, 0xac, 0xa0, 0x2f, 0xa1, 0x00, + 0x7c, 0x29, 0x8b, 0xf2, 0xd6, 0x29, 0x76, 0x46, 0x43, 0xbf, 0x3d, 0x62, 0xa7, 0x5e, 0x7e, 0x7a, 0x12, 0x39, 0x9d, + 0xee, 0x43, 0x4f, 0xfd, 0xf3, 0x77, 0x5c, 0x30, 0xdc, 0x57, 0x16, 0x77, 0xda, 0xed, 0x7f, 0x70, 0x7b, 0x8d, 0x59, + 0x08, 0xa0, 0xa0, 0x33, 0xbb, 0xb0, 0x8a, 0x2c, 0x81, 0xf5, 0x59, 0xd5, 0xb3, 0x37, 0x03, 0xbf, 0x29, 0x4e, 0x4f, + 0x83, 0xee, 0xec, 0xa2, 0x44, 0xec, 0x02, 0x91, 0x90, 0x29, 0x91, 0x94, 0x6f, 0x8b, 0x3f, 0x0a, 0xf1, 0xa3, 0xd5, + 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd5, 0x5b, 0x23, 0xd8, 0x07, 0x44, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x80, 0x13, 0x98, + 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d, 0x0d, 0xc6, 0xa3, 0xdb, 0xc0, 0xc8, 0xd3, 0xd1, 0xa2, 0xbe, 0xae, + 0x1d, 0x70, 0x4e, 0x7b, 0x13, 0x86, 0xfc, 0x14, 0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf1, 0x89, 0x78, 0x24, 0x76, 0x3e, + 0x17, 0x75, 0x3b, 0xed, 0xb6, 0x78, 0x2f, 0x40, 0xa1, 0x05, 0x1d, 0x1f, 0x1b, 0x00, 0x13, 0x7d, 0xb8, 0xee, 0x23, + 0x36, 0x5f, 0xdf, 0xfa, 0xa5, 0x1a, 0xef, 0x5c, 0xe5, 0x0d, 0x0a, 0x15, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0xf9, + 0x5e, 0x47, 0x1f, 0x54, 0xf5, 0xbf, 0x66, 0xa4, 0xf5, 0x02, 0xee, 0x99, 0xb9, 0x40, 0xbd, 0x74, 0x0f, 0x43, 0x52, + 0xad, 0x96, 0x0b, 0x7a, 0x83, 0x61, 0x08, 0x89, 0x0e, 0x04, 0x9d, 0xbc, 0x5f, 0xd0, 0x37, 0x35, 0x32, 0x37, 0x28, + 0x9c, 0xcc, 0x85, 0x2d, 0x9f, 0x69, 0xb9, 0x0e, 0x4a, 0x1a, 0xbc, 0xec, 0x0f, 0x4c, 0x36, 0x00, 0xe9, 0x4d, 0xa1, + 0xae, 0x1f, 0x42, 0xe1, 0x4a, 0x29, 0x47, 0x6a, 0x7a, 0xd3, 0x15, 0x7d, 0x58, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x06, + 0xff, 0x0a, 0xc3, 0xde, 0xd1, 0x8e, 0x65, 0x91, 0xcd, 0xf3, 0x21, 0x45, 0xea, 0x96, 0x3d, 0x7e, 0x9b, 0x14, 0xae, + 0xed, 0x31, 0x2d, 0xe7, 0xd1, 0x0d, 0xae, 0x7d, 0xe4, 0x80, 0xd3, 0x21, 0x88, 0xb8, 0x63, 0x20, 0xa3, 0x1c, 0x0a, + 0x42, 0x54, 0x5d, 0x23, 0xca, 0x77, 0xa3, 0xfb, 0x97, 0xfe, 0x3c, 0x8d, 0x41, 0xd2, 0x7d, 0x8c, 0x47, 0x74, 0xef, + 0x24, 0x1e, 0xd1, 0x41, 0x44, 0x8b, 0x12, 0x8f, 0x30, 0xb2, 0x0d, 0x05, 0xea, 0x3b, 0x2c, 0xf0, 0x2c, 0x13, 0x59, + 0xec, 0x96, 0x8d, 0x87, 0x09, 0x86, 0xaa, 0x1c, 0x65, 0xd3, 0x28, 0x4e, 0x03, 0xfc, 0x3e, 0x88, 0xa7, 0x47, 0x0c, + 0xb0, 0x8b, 0x07, 0x3f, 0x99, 0xcc, 0x45, 0xeb, 0xb8, 0xfe, 0xaf, 0x20, 0x47, 0xa8, 0x7f, 0x29, 0xfd, 0x30, 0x0d, + 0x97, 0x3a, 0xe6, 0xad, 0x97, 0x82, 0xec, 0xe1, 0xca, 0x66, 0x65, 0x14, 0xe7, 0xd8, 0xe5, 0xf4, 0xa3, 0xdf, 0xea, + 0x04, 0x1d, 0xed, 0xba, 0xd6, 0x6e, 0xa3, 0x8a, 0x5c, 0x16, 0x79, 0xa3, 0x91, 0x60, 0xd0, 0xcf, 0x02, 0xce, 0xea, + 0x5d, 0xc3, 0xea, 0x49, 0xbe, 0xc4, 0x00, 0xce, 0x69, 0xea, 0xd4, 0x80, 0xa0, 0xb3, 0x80, 0x6b, 0xa6, 0x72, 0xcb, + 0x88, 0x94, 0xd2, 0x63, 0xda, 0xc0, 0xf5, 0xbb, 0x44, 0x78, 0x6f, 0xa8, 0x9e, 0x02, 0xa5, 0x58, 0x6e, 0x74, 0xbc, + 0x2b, 0x76, 0xbc, 0x45, 0x3c, 0x16, 0xda, 0xb0, 0x05, 0x6d, 0xeb, 0x2f, 0x43, 0xa0, 0xd2, 0xa7, 0xd0, 0x5e, 0x5f, + 0x38, 0x2a, 0xb1, 0x3e, 0x87, 0xb9, 0xf6, 0x85, 0xd6, 0xa3, 0x1b, 0xf9, 0xf6, 0xde, 0xfa, 0x82, 0x97, 0xbb, 0x5b, + 0xa2, 0xf7, 0xde, 0x71, 0x59, 0x90, 0x82, 0x32, 0x03, 0x69, 0xd5, 0x14, 0xa2, 0x0e, 0x86, 0xa5, 0xf4, 0x5d, 0x1c, + 0x37, 0xd7, 0x46, 0x97, 0x88, 0x18, 0x4b, 0xb6, 0x2b, 0x30, 0x5d, 0x29, 0xca, 0x61, 0x4f, 0xea, 0x84, 0x94, 0x42, + 0xe4, 0x60, 0xf4, 0x96, 0xa1, 0x38, 0x46, 0x08, 0xfa, 0xeb, 0x8b, 0xb8, 0x0c, 0xd7, 0x17, 0x59, 0x79, 0x0c, 0x96, + 0x09, 0x42, 0x95, 0xab, 0xcf, 0xbb, 0xc0, 0xc4, 0x22, 0xc8, 0x62, 0xd1, 0x08, 0x38, 0x2d, 0x2b, 0x6d, 0xab, 0x2f, + 0xa0, 0x01, 0x0f, 0x10, 0x0b, 0xc0, 0x76, 0xa3, 0x5e, 0x0c, 0x70, 0x11, 0xad, 0xf7, 0x60, 0xa0, 0xdd, 0x2d, 0xd1, + 0x08, 0xf0, 0xca, 0x11, 0xe4, 0x0a, 0x2d, 0x4c, 0xc7, 0x31, 0x51, 0x1b, 0xc5, 0x67, 0x9a, 0x74, 0x94, 0x9b, 0xbc, + 0xb7, 0x9b, 0x44, 0x27, 0x2c, 0x81, 0x21, 0x8b, 0xab, 0xab, 0x36, 0x8c, 0x24, 0x5e, 0xad, 0xdd, 0x38, 0x9d, 0xcd, + 0xe5, 0x67, 0xb6, 0x60, 0xe2, 0x0e, 0x9e, 0x7c, 0xe2, 0x2d, 0x80, 0xbe, 0x3a, 0xca, 0x0b, 0xe4, 0x00, 0x00, 0x22, + 0x9d, 0x22, 0x20, 0x74, 0x15, 0x5b, 0x40, 0x69, 0x3c, 0x5a, 0x2e, 0xd3, 0x98, 0xe5, 0xf0, 0x02, 0xdb, 0x72, 0x86, + 0xcf, 0x29, 0x3c, 0x4f, 0xe3, 0x14, 0x1f, 0x0b, 0x7c, 0x8c, 0x2e, 0xf0, 0x31, 0x2b, 0xed, 0xbf, 0xcb, 0x0a, 0x58, + 0x9a, 0x00, 0xd9, 0xd5, 0x15, 0xc8, 0x7b, 0x4d, 0x82, 0xdd, 0x2d, 0x20, 0x16, 0x72, 0x8a, 0xf8, 0xe8, 0x0a, 0x33, + 0xc9, 0xc8, 0x8a, 0x59, 0x4b, 0x94, 0x5b, 0xa4, 0x55, 0x43, 0x70, 0xba, 0x72, 0xa7, 0x61, 0x3c, 0x78, 0x32, 0xbd, + 0xe4, 0x09, 0xbe, 0xb8, 0xb6, 0x25, 0xbe, 0x8a, 0x21, 0x88, 0x42, 0x8f, 0x88, 0xa1, 0x2e, 0xe3, 0xf2, 0x7b, 0x37, + 0x71, 0x68, 0xe3, 0x2c, 0x60, 0xbf, 0xa1, 0x16, 0xe0, 0x51, 0x9c, 0x88, 0xc6, 0x2b, 0xf0, 0x69, 0xe4, 0x09, 0x12, + 0x3a, 0xbb, 0x5b, 0x15, 0x6c, 0x00, 0xfc, 0x50, 0xdc, 0xb2, 0x76, 0x44, 0x0e, 0xa4, 0x2d, 0xca, 0xe9, 0xec, 0x5c, + 0x6e, 0x69, 0x19, 0xd9, 0x15, 0xb1, 0x72, 0x8d, 0x2a, 0xe5, 0x2c, 0xda, 0xe3, 0x28, 0x5d, 0xd5, 0x14, 0xa0, 0x9f, + 0x31, 0x36, 0xf2, 0x6c, 0x0b, 0x64, 0xa9, 0x78, 0xfe, 0x98, 0xb0, 0x33, 0x26, 0xbf, 0x94, 0xa2, 0x07, 0xd1, 0x95, + 0x43, 0x50, 0xc9, 0x5c, 0x5e, 0xe2, 0x94, 0xec, 0xa9, 0x70, 0x94, 0x94, 0xa8, 0x23, 0xe2, 0xd9, 0xc6, 0xa0, 0xcd, + 0x39, 0xda, 0xf5, 0x61, 0xbd, 0x0e, 0x58, 0xbb, 0xb6, 0x80, 0x97, 0xec, 0xb8, 0xdb, 0x91, 0x83, 0x01, 0xd8, 0x78, + 0x0c, 0xdb, 0x45, 0x45, 0x96, 0xb5, 0x2c, 0x10, 0x50, 0x81, 0x53, 0xea, 0xd9, 0xa2, 0x85, 0x5d, 0xb5, 0xd5, 0x4f, + 0x92, 0x38, 0x49, 0x36, 0xfc, 0xb4, 0x62, 0x2e, 0xe0, 0x8e, 0x75, 0x11, 0x69, 0x6d, 0xc8, 0x37, 0xfb, 0x5f, 0xfe, + 0xf9, 0xbf, 0xff, 0x67, 0x18, 0x98, 0xfa, 0xb9, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x06, 0xad, 0xe6, 0xe9, 0x2d, 0xed, + 0xfe, 0xf6, 0x1f, 0xfe, 0x0b, 0x34, 0xa3, 0x1b, 0x39, 0x6e, 0x79, 0x4c, 0x10, 0x0d, 0xd1, 0x08, 0xfa, 0x2c, 0x90, + 0x6a, 0x83, 0x5c, 0x3b, 0xd3, 0x3f, 0x21, 0xd8, 0x05, 0xcf, 0x66, 0x37, 0x82, 0x83, 0x50, 0x0f, 0x93, 0xac, 0x60, + 0x1a, 0x1e, 0x21, 0x6b, 0x3f, 0x0f, 0x20, 0x9a, 0x6b, 0x06, 0x5c, 0x5e, 0x58, 0x7a, 0x1c, 0xb1, 0x3c, 0xab, 0xc6, + 0x69, 0xac, 0x5e, 0xc1, 0x38, 0xa1, 0x43, 0x71, 0x05, 0x58, 0x2f, 0xf1, 0x04, 0x0f, 0x24, 0x10, 0xdc, 0xfa, 0x57, + 0xbe, 0x56, 0x0f, 0xa6, 0xf9, 0x53, 0x8c, 0x25, 0x42, 0x29, 0x6a, 0x04, 0xf8, 0x09, 0x72, 0xea, 0xa3, 0x7e, 0x8e, + 0x2e, 0xf4, 0x33, 0xca, 0x32, 0x31, 0x01, 0xe8, 0xa2, 0x29, 0x9a, 0x19, 0xe6, 0x0c, 0x22, 0x0d, 0xa0, 0xf2, 0x23, + 0x8d, 0x6c, 0x12, 0x21, 0xbc, 0x3e, 0x62, 0xd2, 0x25, 0x5e, 0xb1, 0x99, 0xe7, 0xec, 0x63, 0x92, 0x9d, 0x63, 0x70, + 0x0a, 0x89, 0x74, 0x5d, 0x7d, 0x69, 0xf9, 0xbe, 0x75, 0x4d, 0xf5, 0x04, 0x76, 0x01, 0xd8, 0x92, 0x3c, 0xd4, 0x54, + 0x6e, 0x20, 0xb5, 0x7a, 0x48, 0xc4, 0x72, 0x75, 0x8d, 0x44, 0x1b, 0x4b, 0x25, 0xd6, 0xf2, 0x11, 0x91, 0xf6, 0xda, + 0x15, 0x53, 0xdc, 0x3e, 0x0f, 0xc2, 0x76, 0xcd, 0x44, 0x58, 0x75, 0xeb, 0x4c, 0xc7, 0x6e, 0x70, 0x03, 0x65, 0x3e, + 0x13, 0xeb, 0xd5, 0x80, 0xdc, 0x01, 0x06, 0x0f, 0x34, 0x80, 0xd0, 0x11, 0x4f, 0x84, 0x84, 0x0f, 0x68, 0x2d, 0xa4, + 0xb8, 0xaf, 0x41, 0xe4, 0x59, 0xff, 0xe7, 0x7f, 0x1e, 0xd2, 0x1c, 0xd8, 0x26, 0xae, 0xf4, 0x2b, 0x99, 0x23, 0x27, + 0xb9, 0x52, 0xbf, 0xaf, 0xf0, 0x82, 0x1d, 0x8d, 0x82, 0x4a, 0xb6, 0x20, 0x9b, 0x80, 0x93, 0x4d, 0x60, 0xea, 0x61, + 0xf4, 0x32, 0xb2, 0xd5, 0xfa, 0xf6, 0x23, 0xf6, 0xab, 0x69, 0x4c, 0x2e, 0x35, 0x66, 0x2c, 0xad, 0x59, 0xa9, 0x31, + 0xf1, 0xfb, 0x29, 0x0d, 0x8d, 0x19, 0x5f, 0xab, 0x31, 0x91, 0x76, 0x7d, 0x1c, 0x39, 0xb4, 0x37, 0x31, 0x94, 0x62, + 0x68, 0x70, 0x8e, 0xb6, 0x09, 0xf8, 0xcf, 0xc3, 0x4f, 0xd2, 0x34, 0x21, 0xc8, 0x31, 0x96, 0x20, 0x8d, 0x2d, 0xb2, + 0x4b, 0x04, 0x76, 0xc7, 0xa5, 0xde, 0xf8, 0xf8, 0x68, 0x4c, 0xc0, 0xdd, 0xc5, 0x98, 0xa1, 0x58, 0x3e, 0xde, 0xc2, + 0x21, 0xc4, 0x7e, 0x29, 0xe9, 0x19, 0x90, 0xda, 0xa2, 0x71, 0xbc, 0x85, 0x08, 0x4a, 0x05, 0xf6, 0xdb, 0x37, 0x07, + 0x87, 0xb6, 0x77, 0x92, 0x8d, 0x2e, 0x03, 0x1b, 0x1c, 0x0a, 0x30, 0x3e, 0x5c, 0x9f, 0x4f, 0x58, 0xea, 0x28, 0x93, + 0x3e, 0x4b, 0xc0, 0xa5, 0xc9, 0x4e, 0xc5, 0x37, 0x14, 0x9a, 0x01, 0x75, 0x20, 0x26, 0x7d, 0x64, 0x71, 0x6f, 0x97, + 0x8b, 0xef, 0x8f, 0xf2, 0x1c, 0x1f, 0xf7, 0x30, 0xa5, 0x60, 0x77, 0x0b, 0x1e, 0xf0, 0xe5, 0x00, 0xd5, 0x91, 0x7e, + 0x13, 0x70, 0x16, 0xe2, 0x7d, 0x0b, 0xdb, 0x6f, 0xa9, 0xbe, 0x08, 0xc5, 0x9e, 0x64, 0x35, 0x6d, 0x75, 0x57, 0xa6, + 0x1c, 0x8d, 0x3d, 0x42, 0x4b, 0x8d, 0xac, 0x6e, 0x20, 0x05, 0x1f, 0xe9, 0x12, 0xa1, 0xfd, 0x8d, 0x22, 0x1a, 0xa5, + 0xd2, 0xd5, 0xb2, 0x0a, 0x27, 0x24, 0x2a, 0x8a, 0xc9, 0xe0, 0x27, 0x81, 0x7f, 0x6c, 0x7e, 0x2f, 0x4c, 0x7c, 0xda, + 0x47, 0x23, 0x79, 0xf4, 0x57, 0xef, 0x23, 0xf3, 0x2e, 0x8f, 0xa9, 0xa5, 0x72, 0x4e, 0x31, 0x6a, 0x82, 0x4e, 0x7c, + 0x5b, 0x45, 0x21, 0xc0, 0x3c, 0x49, 0xa2, 0x59, 0xc1, 0x02, 0xf5, 0x20, 0xfd, 0x54, 0x74, 0x77, 0x97, 0x03, 0x03, + 0xa6, 0x19, 0x53, 0xf2, 0xc9, 0xc2, 0x74, 0x64, 0x1f, 0x80, 0x23, 0x8b, 0x49, 0xf8, 0xad, 0x08, 0x94, 0x6f, 0x1a, + 0x24, 0x6c, 0xcc, 0x4b, 0x8e, 0xb7, 0xbc, 0x17, 0x2a, 0x72, 0xe0, 0x77, 0x77, 0xc0, 0xb9, 0xb5, 0x7c, 0xfc, 0xff, + 0xb6, 0xb1, 0x47, 0x41, 0x0a, 0xce, 0x28, 0x5d, 0xfb, 0xc0, 0x2b, 0x75, 0x00, 0x91, 0xf9, 0xbe, 0x30, 0x26, 0x1a, + 0x32, 0x8c, 0xaa, 0x94, 0x3c, 0x07, 0xb1, 0xed, 0xf1, 0xdc, 0x6c, 0x07, 0xe2, 0x76, 0x29, 0xb4, 0xb2, 0xec, 0xdc, + 0x6f, 0xbb, 0xd2, 0x05, 0x58, 0x6e, 0xac, 0x22, 0x24, 0xf5, 0xb7, 0x25, 0x0a, 0x19, 0xb1, 0x9c, 0x52, 0x68, 0x9a, + 0x85, 0xe8, 0x61, 0xe2, 0xb4, 0x1c, 0xe5, 0xb9, 0xd5, 0x50, 0x2c, 0x69, 0xc7, 0x1f, 0xd1, 0x8e, 0x27, 0x19, 0x36, + 0x58, 0x88, 0xb9, 0x87, 0x51, 0x32, 0x74, 0x10, 0x00, 0xab, 0x65, 0x3d, 0x02, 0x6a, 0xba, 0x2a, 0xd2, 0xe0, 0x3f, + 0x44, 0xe2, 0x96, 0x42, 0xe2, 0xad, 0xa0, 0xd2, 0xf1, 0xa0, 0x2c, 0x7b, 0xe7, 0xcc, 0x39, 0xfa, 0x5d, 0x5e, 0x1a, + 0x10, 0x77, 0x45, 0xf5, 0xf7, 0xf6, 0xda, 0xa5, 0x3b, 0xf0, 0x7e, 0x30, 0x3e, 0x62, 0x66, 0x2b, 0x86, 0xb6, 0x3d, + 0x58, 0x86, 0x1f, 0x42, 0xec, 0xfb, 0xca, 0xb1, 0xd1, 0xb2, 0xa4, 0x9a, 0xcb, 0x16, 0xf1, 0x97, 0x8d, 0xdd, 0x44, + 0xc8, 0xfb, 0xfb, 0xeb, 0x22, 0x17, 0xdf, 0xdc, 0x1e, 0xb9, 0x60, 0x77, 0x8c, 0x5c, 0x7c, 0xf3, 0x27, 0x47, 0x2e, + 0xbe, 0x6f, 0x46, 0x2e, 0x7e, 0xfb, 0x9c, 0xc8, 0x45, 0x9e, 0x9d, 0x17, 0x61, 0x47, 0x9e, 0x94, 0x83, 0xcc, 0xf9, + 0xfb, 0x84, 0x30, 0x60, 0xa2, 0x46, 0x00, 0x83, 0x22, 0x16, 0x22, 0xb9, 0x0f, 0x24, 0xbb, 0x8c, 0x17, 0xb4, 0x75, + 0x16, 0x5d, 0xeb, 0xbe, 0xba, 0x36, 0x04, 0x1e, 0x9b, 0xab, 0x2f, 0xbc, 0x75, 0x55, 0x44, 0x21, 0xa0, 0xef, 0x7e, + 0xea, 0x8e, 0xdd, 0x4d, 0x95, 0xbe, 0x65, 0x8e, 0xd0, 0x53, 0x51, 0x79, 0xc1, 0x3e, 0x0b, 0xfb, 0xdf, 0x1d, 0x75, + 0x7a, 0xdb, 0x9d, 0x29, 0xf4, 0x06, 0x2d, 0x0a, 0x6f, 0xed, 0xde, 0xf6, 0x36, 0xbe, 0x9d, 0xab, 0xb7, 0x2e, 0xbe, + 0xc5, 0xea, 0x6d, 0x07, 0xdf, 0x86, 0xea, 0xed, 0x01, 0xbe, 0x8d, 0xd4, 0xdb, 0x43, 0x7c, 0x3b, 0xb3, 0xcb, 0x23, + 0xae, 0x81, 0x7b, 0x08, 0x7c, 0x45, 0xc6, 0x7e, 0xa0, 0xca, 0x60, 0xd3, 0xe2, 0x75, 0xbb, 0xe8, 0x34, 0x88, 0x3d, + 0xe1, 0x14, 0x05, 0xb9, 0x77, 0x0e, 0x92, 0x3f, 0xa0, 0xec, 0xb2, 0xa7, 0xf8, 0xdd, 0x05, 0xf0, 0x21, 0x0e, 0xe3, + 0x29, 0x53, 0x1f, 0xa0, 0x55, 0x58, 0x83, 0x1d, 0x79, 0xd4, 0x1e, 0x94, 0x3d, 0xbd, 0x4e, 0x22, 0x60, 0xa2, 0x4e, + 0xef, 0x69, 0xe5, 0xaa, 0x3a, 0x31, 0x5d, 0x4b, 0xaf, 0xf0, 0x35, 0x7a, 0xc4, 0x70, 0xa3, 0xc7, 0x60, 0x21, 0xb5, + 0x2e, 0xc0, 0xf1, 0x5a, 0xa9, 0x5b, 0x10, 0x22, 0xad, 0x4d, 0x08, 0x27, 0xfd, 0x76, 0x18, 0x9d, 0xea, 0xe7, 0x57, + 0x60, 0xf0, 0x46, 0xa7, 0xec, 0x36, 0x3d, 0x43, 0x20, 0x9a, 0x3a, 0x46, 0x01, 0x41, 0xf6, 0x10, 0x2c, 0x0d, 0x3a, + 0x80, 0x52, 0xc7, 0x20, 0x75, 0xea, 0x5a, 0x87, 0xa6, 0xaf, 0x17, 0x01, 0x45, 0xab, 0x82, 0x5d, 0xb0, 0xbb, 0xa9, + 0x54, 0x50, 0x18, 0x2a, 0xb0, 0xe0, 0x46, 0x55, 0xa4, 0x7d, 0xe4, 0x6b, 0x15, 0x92, 0xa5, 0x74, 0x91, 0x19, 0xcd, + 0xd7, 0xa1, 0xfc, 0x65, 0xf1, 0xf8, 0x45, 0x67, 0x88, 0x7f, 0xa4, 0xf0, 0xfd, 0x62, 0x3c, 0x1e, 0xdf, 0xa8, 0x9b, + 0xbe, 0x18, 0x8d, 0x59, 0x97, 0xed, 0xf4, 0x30, 0xd2, 0xdb, 0x92, 0xe2, 0xb0, 0x53, 0x12, 0xed, 0x16, 0x77, 0x6b, + 0x8c, 0x92, 0x13, 0xd4, 0xd5, 0xdd, 0x95, 0x58, 0x09, 0x54, 0x59, 0x80, 0xf0, 0x3e, 0x8d, 0xd3, 0xa0, 0x5d, 0xfa, + 0x67, 0x52, 0xea, 0x7f, 0xf1, 0xe8, 0xd1, 0xa3, 0xd2, 0x1f, 0xa9, 0xb7, 0xf6, 0x68, 0x54, 0xfa, 0xc3, 0x85, 0x46, + 0xa3, 0xdd, 0x1e, 0x8f, 0x4b, 0x3f, 0x56, 0x05, 0xdb, 0xdd, 0xe1, 0x68, 0xbb, 0x5b, 0xfa, 0xe7, 0x46, 0x8b, 0xd2, + 0x67, 0xf2, 0x2d, 0x67, 0xa3, 0x5a, 0xb8, 0xf8, 0x61, 0x1b, 0x2a, 0x05, 0xa3, 0x2d, 0xd0, 0xc9, 0x13, 0x8f, 0x41, + 0x34, 0xe7, 0x59, 0x79, 0x0c, 0xb2, 0x9d, 0x81, 0x7c, 0x1e, 0x4b, 0xd9, 0x2e, 0xbe, 0xef, 0x8a, 0x12, 0xfd, 0x37, + 0x53, 0xa2, 0x23, 0x33, 0x93, 0x34, 0x67, 0xa4, 0x07, 0x9a, 0xd5, 0xc8, 0x59, 0x54, 0xfd, 0x5b, 0xc8, 0x2a, 0x61, + 0x8f, 0xd2, 0x06, 0x5b, 0x0a, 0x19, 0xff, 0xed, 0x75, 0x32, 0xfe, 0xbb, 0xdb, 0x65, 0xfc, 0xc9, 0xdd, 0x44, 0xfc, + 0x77, 0x7f, 0xb2, 0x88, 0xff, 0xd6, 0x14, 0xf1, 0x42, 0x88, 0x5d, 0x82, 0xf5, 0x4a, 0x66, 0xeb, 0x49, 0x76, 0xd1, + 0xc2, 0x2d, 0x91, 0xdb, 0x24, 0x3d, 0xd7, 0xef, 0x24, 0xfc, 0x57, 0xe4, 0xff, 0xa8, 0xc1, 0x8c, 0x8f, 0xc5, 0xf2, + 0xec, 0xf4, 0x34, 0x61, 0x4a, 0xc6, 0x1b, 0x15, 0x64, 0x0e, 0xbf, 0x49, 0x43, 0xfb, 0x0d, 0x38, 0xa8, 0x46, 0xc9, + 0x78, 0x0c, 0x45, 0xe3, 0xb1, 0xad, 0xf2, 0x63, 0x41, 0x9e, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5, 0xf5, 0xd7, + 0x66, 0x99, 0x59, 0x20, 0x23, 0x51, 0xa6, 0x31, 0x21, 0x6b, 0x46, 0x71, 0x81, 0x7b, 0xb0, 0xfa, 0xb8, 0x2d, 0xda, + 0x2b, 0x53, 0x50, 0x2a, 0xf1, 0x10, 0xbf, 0x9a, 0xd2, 0xfc, 0x90, 0x88, 0xc8, 0x65, 0x5e, 0x46, 0xae, 0x3a, 0xef, + 0x34, 0xbe, 0x59, 0x57, 0x9d, 0x71, 0xc2, 0xe2, 0xcb, 0x7c, 0x86, 0xc7, 0x97, 0x2f, 0x46, 0xce, 0x25, 0xd8, 0xb1, + 0x71, 0xf1, 0x26, 0x6d, 0xe4, 0x89, 0x09, 0xb0, 0xc3, 0xd0, 0xc4, 0xb4, 0x14, 0x04, 0xab, 0x12, 0xe6, 0xab, 0xca, + 0x9e, 0xd1, 0x49, 0xa6, 0x13, 0xe1, 0x90, 0xfd, 0x1a, 0x59, 0x02, 0x73, 0x30, 0xa9, 0x0b, 0xe9, 0xe3, 0xe5, 0x22, + 0xc9, 0xe2, 0x4c, 0x7e, 0xe5, 0x9a, 0xa2, 0xff, 0x0b, 0xa9, 0x3f, 0xe4, 0xf1, 0x7b, 0xd5, 0x13, 0x03, 0xee, 0x62, + 0x86, 0x51, 0xa9, 0x82, 0xec, 0x40, 0xb8, 0x19, 0x7e, 0x92, 0x47, 0x0c, 0xa1, 0x62, 0xd9, 0x15, 0xf5, 0xf0, 0x13, + 0x28, 0xd5, 0x97, 0x21, 0x6b, 0x5f, 0x11, 0x6c, 0xf0, 0x00, 0x7e, 0xdd, 0x9f, 0xa3, 0x36, 0xc8, 0xe6, 0xdc, 0x71, + 0xa8, 0x95, 0xe3, 0x96, 0x5e, 0x77, 0x07, 0x3c, 0x4a, 0xd7, 0x17, 0xdf, 0xfd, 0x71, 0x74, 0x67, 0x89, 0xef, 0x75, + 0xa1, 0xf3, 0xa5, 0xef, 0x70, 0x69, 0x12, 0xe3, 0x87, 0x42, 0x04, 0xa2, 0xc6, 0x5d, 0x11, 0xb5, 0x88, 0xcd, 0x77, + 0x5f, 0xb9, 0x6f, 0x06, 0x61, 0xdd, 0x55, 0x1c, 0x2c, 0xe3, 0x64, 0xf5, 0x42, 0x6c, 0x2b, 0xac, 0x9a, 0x65, 0x70, + 0x6e, 0xd1, 0x99, 0xc5, 0xb9, 0x11, 0x77, 0xae, 0x6d, 0x83, 0x52, 0x05, 0x9e, 0x45, 0xf4, 0xf8, 0x12, 0x63, 0xa4, + 0xc2, 0xf7, 0x55, 0x40, 0xd7, 0xbd, 0x4e, 0x03, 0x72, 0xf4, 0x47, 0x35, 0xa3, 0xab, 0x2a, 0x55, 0x50, 0x9a, 0xa7, + 0x04, 0x06, 0x32, 0x14, 0x00, 0x86, 0x35, 0x4e, 0x85, 0xde, 0x82, 0x69, 0x48, 0x00, 0x6b, 0x8f, 0x0c, 0xdd, 0x12, + 0x5b, 0x81, 0x2d, 0xa4, 0x05, 0x28, 0x3d, 0xec, 0xb0, 0x67, 0xd5, 0x40, 0x4f, 0x97, 0xe3, 0xc6, 0x37, 0x39, 0x69, + 0x97, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb, 0xcb, 0x05, 0x48, 0xf9, 0x93, 0x4f, 0x31, 0x07, 0x9b, 0x7a, 0xd6, + 0xc2, 0x28, 0x08, 0x85, 0x31, 0xa5, 0x3a, 0xa4, 0xa3, 0x47, 0x71, 0x45, 0xa8, 0x37, 0x2f, 0xd0, 0x97, 0x23, 0xa7, + 0x25, 0x48, 0xb3, 0x94, 0xf5, 0xea, 0x47, 0xcc, 0xa6, 0xdf, 0xa0, 0x88, 0x35, 0x58, 0x64, 0xe8, 0xfb, 0xf1, 0x4b, + 0xf0, 0xfd, 0x84, 0x1a, 0x6d, 0x2b, 0xa7, 0xa1, 0xbd, 0xb2, 0x7d, 0x20, 0x69, 0xbb, 0x49, 0xd6, 0x42, 0xbe, 0xec, + 0x1c, 0x5d, 0xe7, 0xdc, 0xdc, 0x76, 0xe0, 0xda, 0xdd, 0xd9, 0xf1, 0xd4, 0x3f, 0xe3, 0xa4, 0xba, 0x59, 0x4c, 0x07, + 0xae, 0x77, 0x81, 0x2c, 0x88, 0xc6, 0xf8, 0x45, 0xbd, 0xbb, 0xb4, 0x3c, 0xa1, 0x6c, 0xc7, 0x05, 0xaa, 0xf5, 0xa0, + 0xf3, 0x08, 0xbc, 0xb5, 0x3b, 0x0f, 0x7f, 0x33, 0xfa, 0xa5, 0xa4, 0x91, 0xba, 0xb4, 0x6a, 0xdb, 0x3d, 0x94, 0x17, + 0x49, 0x74, 0x09, 0x4e, 0x23, 0xd9, 0x18, 0x27, 0x18, 0xc4, 0xed, 0xcd, 0x32, 0x99, 0x39, 0x90, 0xb3, 0x84, 0x7e, + 0x6d, 0x85, 0x5c, 0x8a, 0xed, 0x07, 0xb3, 0x0b, 0xb5, 0x1a, 0x9d, 0x46, 0x46, 0xc0, 0x9f, 0x7a, 0xf0, 0x7f, 0x7d, + 0xa6, 0x41, 0xfd, 0xf0, 0x7a, 0x07, 0x60, 0x10, 0x86, 0x4d, 0x2b, 0x17, 0x50, 0xb5, 0xa1, 0xc4, 0x48, 0x7d, 0xa8, + 0x06, 0xb2, 0xfc, 0x6d, 0x50, 0x95, 0x51, 0xc1, 0x7a, 0xf8, 0x69, 0xc3, 0x18, 0x5c, 0x53, 0x69, 0x3c, 0x4d, 0xe3, + 0xd1, 0x28, 0x61, 0x3d, 0x65, 0x1f, 0x59, 0x9d, 0x07, 0x98, 0x39, 0x60, 0x2e, 0x59, 0x7d, 0x55, 0x0c, 0xe2, 0x69, + 0x3a, 0x45, 0x27, 0x60, 0xaf, 0xe1, 0xf7, 0x09, 0x57, 0x92, 0x53, 0x1e, 0xa9, 0xb7, 0x2b, 0xe2, 0xd1, 0x73, 0x1d, + 0x97, 0x1d, 0x30, 0x16, 0x69, 0xc1, 0xdb, 0x3d, 0x9e, 0xcd, 0x82, 0xd6, 0x76, 0x1d, 0x11, 0xac, 0xd2, 0x28, 0x78, + 0x2b, 0xd0, 0xf2, 0xd0, 0x3a, 0x10, 0x5a, 0xce, 0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0xe0, 0xe9, 0xa2, 0xb2, + 0x8e, 0xcc, 0xff, 0x9f, 0xdd, 0xf2, 0xd5, 0xea, 0xdd, 0xf2, 0x95, 0xda, 0x2d, 0x37, 0x73, 0xec, 0x17, 0xe3, 0x0e, + 0xfe, 0xe9, 0x55, 0x08, 0xc1, 0xaa, 0x00, 0x39, 0x2c, 0xb4, 0x8b, 0x5b, 0x5d, 0xf8, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, + 0xc7, 0x07, 0x0b, 0xb0, 0x6d, 0x61, 0x21, 0xfe, 0x6b, 0xd7, 0xaa, 0x3a, 0xf7, 0xb1, 0x0e, 0x7b, 0xed, 0x2c, 0xd7, + 0x75, 0x6f, 0xde, 0xb4, 0x20, 0xaf, 0xb8, 0x13, 0x28, 0x61, 0x0c, 0xae, 0x5a, 0x74, 0x72, 0x02, 0xa5, 0xe3, 0x6c, + 0x38, 0x2f, 0xfe, 0x51, 0xc2, 0x2f, 0x89, 0x78, 0xe3, 0x96, 0x6e, 0x8c, 0xa3, 0xba, 0x8a, 0x34, 0x14, 0x35, 0xc2, + 0x42, 0xaf, 0x53, 0x50, 0x00, 0x63, 0x32, 0xa7, 0xeb, 0xdf, 0x5f, 0xb1, 0x09, 0xfe, 0x2e, 0x6b, 0xb3, 0x12, 0x99, + 0x7f, 0x2d, 0x31, 0x6e, 0x24, 0xc2, 0x67, 0xd1, 0xc0, 0x5c, 0xc3, 0xf6, 0xa3, 0xd5, 0xe0, 0x1e, 0xa9, 0x99, 0x06, + 0x4a, 0x29, 0x48, 0xbd, 0x03, 0x5e, 0x40, 0x34, 0x4f, 0xf8, 0xcd, 0xa3, 0x5e, 0xc7, 0x19, 0x4b, 0xa3, 0xde, 0x20, + 0xd0, 0xab, 0xb6, 0x77, 0x94, 0xd2, 0x5f, 0x7c, 0x75, 0x1f, 0xff, 0x88, 0xc0, 0xd7, 0x49, 0xe5, 0x1b, 0x89, 0xd8, + 0x00, 0xfa, 0x46, 0xeb, 0x35, 0xe7, 0x47, 0x68, 0x70, 0xf2, 0x7f, 0xee, 0xda, 0x1a, 0x8d, 0xf5, 0x3b, 0x35, 0x97, + 0x56, 0xe9, 0x67, 0xb5, 0xfe, 0xbc, 0xc1, 0xef, 0xd8, 0x76, 0x28, 0x1c, 0x82, 0x7a, 0x5b, 0xf9, 0x2b, 0x44, 0x96, + 0x1a, 0x2b, 0x8a, 0xdf, 0xb5, 0x7d, 0x65, 0x12, 0x53, 0x8f, 0xf5, 0xf0, 0x44, 0x3b, 0x91, 0xf2, 0xcc, 0x19, 0x7b, + 0x08, 0x3f, 0xf2, 0x27, 0x16, 0xde, 0xc3, 0xaf, 0x6f, 0x59, 0x17, 0xd3, 0x24, 0x05, 0xb3, 0x6a, 0xc2, 0xf9, 0x2c, + 0xd8, 0xda, 0x3a, 0x3f, 0x3f, 0xf7, 0xcf, 0xb7, 0xfd, 0x2c, 0x3f, 0xdd, 0xea, 0xb6, 0xdb, 0x6d, 0xfc, 0x68, 0x92, + 0x6d, 0x9d, 0xc5, 0xec, 0xfc, 0x31, 0xb8, 0x1f, 0xf6, 0x43, 0xeb, 0x91, 0xf5, 0x70, 0xdb, 0xda, 0x79, 0x60, 0x5b, + 0xa4, 0x00, 0xa0, 0x64, 0xdb, 0xb6, 0x84, 0x02, 0x08, 0x6d, 0x28, 0xde, 0xdb, 0x3d, 0x55, 0x36, 0x1c, 0x26, 0xa5, + 0x0b, 0x0b, 0x09, 0xfc, 0xb7, 0xec, 0x13, 0xab, 0x6f, 0x75, 0x51, 0xd6, 0x92, 0x6a, 0x44, 0xbd, 0xe2, 0x7e, 0x1f, + 0x46, 0xb3, 0x80, 0xd8, 0xc8, 0x2c, 0xc4, 0x30, 0x99, 0x28, 0xa5, 0x29, 0xd0, 0x2e, 0x3d, 0x81, 0x27, 0x70, 0x0b, + 0x26, 0x16, 0x3c, 0xbf, 0xea, 0x3e, 0x04, 0x1d, 0x77, 0xd6, 0xba, 0x3f, 0x6c, 0xb7, 0x3a, 0x56, 0xa7, 0xd5, 0xf5, + 0x1f, 0x5a, 0x5d, 0xf1, 0x3f, 0xc8, 0xc8, 0x6d, 0xab, 0x03, 0x4f, 0xdb, 0x16, 0xbc, 0x9f, 0xdd, 0x17, 0x29, 0x11, + 0x91, 0xbd, 0xb5, 0xb7, 0x8b, 0xbf, 0x43, 0x08, 0x90, 0xfa, 0xca, 0x16, 0xbf, 0xf5, 0xcc, 0xfe, 0xc2, 0x2c, 0xed, + 0x3c, 0x5a, 0x59, 0xdc, 0x7d, 0xb8, 0xb2, 0x78, 0xfb, 0xc1, 0xca, 0xe2, 0xfb, 0x3b, 0xf5, 0xe2, 0xad, 0x53, 0x51, + 0xa5, 0xe5, 0x42, 0x68, 0x4f, 0x23, 0x60, 0x94, 0x0b, 0xa7, 0x03, 0x70, 0xb6, 0xad, 0x16, 0xfe, 0x78, 0xd8, 0x75, + 0x75, 0xaf, 0x13, 0xec, 0xa5, 0xb1, 0x7c, 0xf8, 0x08, 0xb0, 0x7c, 0xde, 0x7d, 0x30, 0xc4, 0x76, 0x84, 0x28, 0xfc, + 0x3b, 0xdb, 0x7e, 0x34, 0x04, 0x8d, 0x60, 0xe1, 0x3f, 0xf8, 0x33, 0xd9, 0xe9, 0x0e, 0xc5, 0x4b, 0x1b, 0xeb, 0x3f, + 0x74, 0x1e, 0x16, 0xd0, 0x14, 0xff, 0xfc, 0xae, 0x4d, 0x68, 0x34, 0xe0, 0xcd, 0x71, 0xef, 0x03, 0x8d, 0x1e, 0x4d, + 0xba, 0xfe, 0x57, 0x67, 0x0f, 0xfd, 0x47, 0x93, 0xce, 0xc3, 0x0f, 0xe2, 0x2d, 0x01, 0x0a, 0x7e, 0x85, 0xff, 0x3e, + 0x6c, 0xb7, 0x27, 0xad, 0x8e, 0xff, 0xe8, 0x6c, 0xdb, 0xdf, 0x4e, 0x5a, 0x0f, 0xfc, 0x47, 0xf8, 0xaf, 0x1a, 0x6e, + 0x92, 0x4d, 0x99, 0x6d, 0xe1, 0x7a, 0x37, 0xfc, 0x5e, 0x73, 0x8e, 0xee, 0x7d, 0x6b, 0xe7, 0xfe, 0xf3, 0x47, 0xb0, + 0x46, 0x93, 0x4e, 0x17, 0xfe, 0xbf, 0xee, 0xf1, 0x03, 0x12, 0x5e, 0x0e, 0x1c, 0x31, 0x4c, 0x27, 0x55, 0x84, 0xa3, + 0x0f, 0x78, 0xdd, 0xf3, 0x7e, 0xbc, 0x2e, 0x00, 0xf2, 0xd7, 0xdb, 0x03, 0x20, 0x3f, 0xdc, 0x31, 0xc8, 0xfd, 0xd7, + 0x3f, 0x39, 0x02, 0xf2, 0x63, 0x33, 0xc8, 0xbd, 0xcf, 0x96, 0x02, 0x1d, 0x4d, 0x67, 0xed, 0x39, 0x73, 0x8e, 0x7e, + 0x62, 0x03, 0x4c, 0x9d, 0x86, 0xd6, 0x3f, 0xd4, 0xe2, 0x41, 0x19, 0x6e, 0xe4, 0x7d, 0x26, 0x76, 0x32, 0xe3, 0xd7, + 0x10, 0x84, 0xf3, 0x5b, 0x09, 0xf2, 0xe2, 0x6e, 0xf4, 0xe0, 0xfc, 0xcf, 0xa5, 0x07, 0x7d, 0xbd, 0x5f, 0xd1, 0xa3, + 0x16, 0x71, 0xa7, 0x88, 0x01, 0x39, 0xfa, 0x07, 0xf4, 0xee, 0xd8, 0x5b, 0x0c, 0xdf, 0x0a, 0x5b, 0xe4, 0x03, 0xbe, + 0xfb, 0x9c, 0xd3, 0x01, 0x91, 0x59, 0x1c, 0xda, 0x32, 0x00, 0x33, 0xc3, 0xef, 0xd3, 0xaa, 0x97, 0x33, 0x71, 0x7b, + 0x25, 0xa4, 0xab, 0x67, 0x3b, 0x3a, 0x78, 0x83, 0xc9, 0xde, 0xe1, 0x22, 0xe3, 0x11, 0xfe, 0x02, 0x24, 0x1e, 0xf3, + 0x04, 0x2f, 0xc6, 0xca, 0x4b, 0x64, 0x98, 0x9b, 0xfc, 0x1d, 0xe6, 0x55, 0xab, 0x43, 0x82, 0x29, 0x06, 0x0c, 0x5e, + 0xb1, 0x51, 0x1c, 0x39, 0xb6, 0x33, 0x83, 0x1d, 0x0b, 0x63, 0xb6, 0x6a, 0x49, 0xcd, 0x94, 0xcf, 0xec, 0xda, 0xea, + 0x77, 0xee, 0xe4, 0xf8, 0x0d, 0xb3, 0xf0, 0x48, 0x06, 0x18, 0x6d, 0xe9, 0x01, 0xc0, 0xf8, 0xaa, 0x24, 0x47, 0x61, + 0x5f, 0x59, 0x0d, 0xb6, 0x30, 0x1b, 0x3a, 0x7e, 0x17, 0xdc, 0x08, 0x2a, 0xc6, 0x6f, 0x42, 0xfd, 0xe8, 0xb4, 0xb6, + 0xc1, 0xac, 0x31, 0xba, 0xe9, 0x81, 0x06, 0x4b, 0x61, 0x24, 0x11, 0x1c, 0x68, 0x94, 0x7a, 0xfa, 0x97, 0x90, 0x55, + 0xe1, 0xa2, 0xe2, 0xf1, 0xe5, 0xa1, 0xbc, 0xf7, 0x6d, 0x63, 0xe4, 0x96, 0x22, 0xf6, 0xd5, 0x37, 0xa7, 0x36, 0x41, + 0x5d, 0xd0, 0x6f, 0x82, 0xa4, 0x73, 0x6f, 0xd4, 0x08, 0x98, 0x76, 0x6d, 0x49, 0xcf, 0x21, 0xb4, 0x85, 0x3e, 0x18, + 0xb1, 0xb3, 0x78, 0x28, 0xc5, 0xba, 0x67, 0xc9, 0xeb, 0x22, 0x2d, 0xc2, 0x22, 0xec, 0x78, 0xc2, 0x77, 0x86, 0x17, + 0xd4, 0x6a, 0x61, 0x9a, 0xd9, 0x7b, 0xf7, 0x7a, 0x1a, 0x92, 0x7a, 0xc6, 0xba, 0x8d, 0xbf, 0x96, 0xf2, 0x08, 0x7c, + 0xb5, 0x7f, 0x0c, 0xef, 0xe1, 0x2f, 0xa6, 0xbc, 0x37, 0xb0, 0x5d, 0x9f, 0x84, 0xe2, 0xbd, 0xea, 0xb7, 0x53, 0xa2, + 0x44, 0xd8, 0x04, 0xfd, 0xe5, 0xdd, 0x55, 0x91, 0x49, 0xa5, 0xd5, 0xdd, 0xa9, 0x94, 0x16, 0x3c, 0x1b, 0x52, 0x0a, + 0x04, 0x68, 0xd7, 0xdf, 0x31, 0x44, 0xe1, 0x59, 0x0b, 0x7f, 0xd6, 0x84, 0xe1, 0x7d, 0x68, 0xa0, 0xa4, 0xe1, 0x4b, + 0x68, 0xbe, 0x2d, 0x04, 0x2f, 0xf4, 0xfb, 0x89, 0x44, 0x95, 0x10, 0x53, 0x75, 0x8e, 0x59, 0x71, 0x88, 0x24, 0x72, + 0x04, 0x6c, 0xcf, 0x88, 0x37, 0x09, 0x76, 0x95, 0xd1, 0x94, 0xa7, 0xd0, 0xd7, 0xd1, 0x9f, 0x72, 0x5e, 0x55, 0xe7, + 0xd5, 0x76, 0xce, 0x8a, 0x29, 0x90, 0xe1, 0x1b, 0x07, 0x55, 0x74, 0x7d, 0x41, 0x7c, 0xd2, 0x4c, 0x6c, 0xe3, 0xea, + 0xa3, 0x6f, 0x2b, 0x32, 0x70, 0xcd, 0x4d, 0xc1, 0x2a, 0xa6, 0xa1, 0x7d, 0x81, 0x69, 0x33, 0xf8, 0xb3, 0x2a, 0x56, + 0x0f, 0x92, 0xa1, 0xfc, 0x24, 0xc2, 0xdf, 0xc8, 0x42, 0x3f, 0xca, 0x6a, 0x03, 0x72, 0xfa, 0x66, 0x25, 0x41, 0xfa, + 0x62, 0x54, 0x36, 0x91, 0x00, 0x7b, 0x01, 0x7f, 0xc1, 0x5f, 0x75, 0x5d, 0x42, 0xde, 0x83, 0xc4, 0x9c, 0x82, 0x51, + 0x9c, 0xd3, 0xf5, 0x5a, 0x85, 0x7f, 0x2d, 0xa2, 0x59, 0x91, 0x9a, 0x76, 0x25, 0x2b, 0xfa, 0x36, 0x16, 0xd9, 0x81, + 0x4c, 0x48, 0x33, 0x3f, 0xda, 0x6c, 0xde, 0x7f, 0x1c, 0x89, 0x5c, 0x34, 0xfc, 0xa8, 0xbd, 0x2d, 0x88, 0x6c, 0x83, + 0x18, 0xbb, 0x16, 0x27, 0x32, 0x6e, 0xf0, 0xda, 0x60, 0xf5, 0x5b, 0x8a, 0xcc, 0x0d, 0x6f, 0x9b, 0xab, 0xa5, 0xc7, + 0xa5, 0x75, 0x70, 0x65, 0xfc, 0xee, 0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0x4f, 0x72, 0x0c, 0xb1, 0xe0, 0x75, 0xd8, + 0xb6, 0x5b, 0x82, 0xe4, 0x31, 0x7e, 0x8d, 0x93, 0x20, 0xbd, 0x0f, 0x85, 0x55, 0xd2, 0xd6, 0xee, 0xa4, 0xbb, 0xf7, + 0xe6, 0x70, 0xdf, 0x12, 0xbb, 0x79, 0x77, 0x0b, 0x5e, 0x77, 0xc9, 0x1d, 0x16, 0xf9, 0x19, 0xa1, 0xc8, 0xcf, 0xb0, + 0x44, 0x52, 0x57, 0x68, 0x6f, 0x09, 0x34, 0x6d, 0x8b, 0xa5, 0x43, 0x11, 0xc3, 0x9b, 0x82, 0xbb, 0x10, 0xe3, 0x87, + 0xbd, 0xb6, 0xb0, 0x5b, 0x0b, 0x57, 0x1a, 0xb6, 0x80, 0x08, 0xf8, 0x29, 0x03, 0x81, 0xa7, 0x2a, 0xe2, 0x07, 0x6a, + 0x9d, 0xa9, 0x64, 0x17, 0x39, 0x94, 0xce, 0x4b, 0x5d, 0x6e, 0x5d, 0xcc, 0x4f, 0xa6, 0x20, 0x87, 0x54, 0x82, 0xca, + 0x7b, 0xd9, 0x61, 0x97, 0xa6, 0xa2, 0x0c, 0x94, 0xa5, 0x88, 0xe4, 0xa4, 0xb3, 0xb7, 0x1b, 0x49, 0x7b, 0x07, 0xf7, + 0x6e, 0x01, 0x9b, 0x17, 0xd4, 0x1c, 0x1a, 0x15, 0x7e, 0x9c, 0x6d, 0x9d, 0xb3, 0x93, 0x56, 0x34, 0x8b, 0xab, 0xf0, + 0x1f, 0x6a, 0xbf, 0xbd, 0x5d, 0xa5, 0x08, 0x65, 0xaa, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24, 0x1c, 0x31, 0x68, + 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0, 0xb4, 0xf7, 0xfe, + 0xf6, 0x3f, 0xfe, 0x97, 0x0c, 0x2b, 0x02, 0x59, 0x01, 0x2c, 0x4c, 0x83, 0xa9, 0x4e, 0x18, 0xd9, 0x05, 0x38, 0xa2, + 0xf1, 0xa8, 0x35, 0x89, 0x92, 0x31, 0x40, 0x50, 0x30, 0x71, 0x9d, 0x49, 0xd6, 0x03, 0x17, 0x48, 0xb0, 0xcc, 0xc3, + 0x79, 0x09, 0x5e, 0xbd, 0x08, 0x57, 0xec, 0x0f, 0xe5, 0xae, 0xaa, 0x9c, 0x61, 0x62, 0x68, 0x23, 0x93, 0xd5, 0xe0, + 0xb9, 0x5a, 0x36, 0xab, 0xfa, 0x25, 0x49, 0x52, 0x78, 0xb0, 0x5a, 0x2a, 0x2b, 0xb4, 0xd4, 0x07, 0x21, 0xff, 0xf2, + 0xcf, 0xff, 0xf1, 0xbf, 0xaa, 0x57, 0x3c, 0xdf, 0xf8, 0xdb, 0x3f, 0xfd, 0xfb, 0xff, 0xfb, 0xbf, 0xff, 0x13, 0x26, + 0x07, 0xcb, 0x33, 0x10, 0xda, 0x4a, 0x56, 0x75, 0x00, 0x22, 0xf6, 0x94, 0x55, 0x39, 0x1c, 0xf5, 0x94, 0xd7, 0x03, + 0x9a, 0x90, 0x78, 0x53, 0x42, 0x47, 0x7c, 0x4d, 0x29, 0xd2, 0x44, 0xb5, 0x1b, 0xc8, 0x07, 0x4b, 0x69, 0xd1, 0xb1, + 0xbe, 0xbd, 0xd3, 0xb6, 0xab, 0xe5, 0xdd, 0x33, 0xfa, 0x6e, 0xe1, 0xc2, 0xdc, 0x29, 0x03, 0xc7, 0xd7, 0xcb, 0xb6, + 0x50, 0x61, 0x2c, 0x2c, 0x29, 0xab, 0x72, 0x0b, 0xe3, 0xcb, 0x0b, 0x7c, 0x0d, 0xba, 0x46, 0x31, 0xad, 0x72, 0xad, + 0x4f, 0xef, 0xd7, 0x39, 0x20, 0x3a, 0xc6, 0xa5, 0x11, 0xc1, 0x32, 0x3a, 0x3b, 0x6d, 0xa1, 0x75, 0x92, 0x5c, 0x96, + 0x34, 0x8a, 0xf0, 0x66, 0xee, 0x3f, 0xfa, 0x87, 0xf2, 0x2f, 0x53, 0xb4, 0x0a, 0x2c, 0x67, 0x1a, 0x5d, 0x48, 0x1f, + 0xe7, 0x41, 0xbb, 0x3d, 0xbb, 0x70, 0x17, 0xd5, 0x0c, 0xde, 0x75, 0x93, 0x51, 0x80, 0xcd, 0x1c, 0x90, 0x0e, 0x5d, + 0x75, 0x2c, 0x0f, 0xcc, 0xfa, 0x36, 0x86, 0x7e, 0xca, 0xf2, 0xcb, 0x05, 0x85, 0x93, 0xe2, 0xdf, 0xf1, 0x70, 0x54, + 0x46, 0xde, 0xa0, 0xc4, 0xc0, 0x62, 0x61, 0xf4, 0xea, 0x8a, 0x5e, 0x93, 0xce, 0x72, 0x6e, 0x8a, 0x79, 0xb8, 0x6b, + 0x1e, 0xcb, 0xde, 0xc7, 0x83, 0xd6, 0x49, 0xc7, 0x9b, 0x74, 0x17, 0x7a, 0x78, 0xce, 0xb3, 0xa9, 0x79, 0x9a, 0xcb, + 0x22, 0x36, 0x64, 0x63, 0x15, 0xb1, 0x94, 0xf5, 0xe2, 0xa4, 0xb6, 0xfc, 0x02, 0xb7, 0x1b, 0xd0, 0x36, 0x8b, 0x78, + 0x40, 0x4c, 0xdb, 0x33, 0xcf, 0x7b, 0x23, 0x3c, 0x49, 0xcf, 0x16, 0xc6, 0x5c, 0x3d, 0xd1, 0x14, 0xe3, 0x82, 0xf5, + 0xbc, 0x9f, 0xd2, 0xa7, 0xee, 0xe6, 0x50, 0x22, 0xac, 0xf0, 0x42, 0x1e, 0xa3, 0xbe, 0xab, 0xf9, 0xe3, 0x52, 0x14, + 0x83, 0x0b, 0xbc, 0xb2, 0x5e, 0xa8, 0x45, 0x51, 0xfb, 0x02, 0xac, 0x1d, 0x02, 0xd3, 0x6e, 0xb6, 0xa2, 0x42, 0x6c, + 0xf5, 0x2e, 0x7c, 0xa1, 0x6d, 0xef, 0x68, 0x36, 0xa3, 0x86, 0x2e, 0x70, 0x23, 0xd9, 0xd0, 0x28, 0x29, 0x28, 0x45, + 0x40, 0x9c, 0xc8, 0xcb, 0x36, 0x92, 0x6d, 0xc5, 0x93, 0x3c, 0xab, 0xa7, 0xdf, 0xb9, 0xfd, 0xff, 0x00, 0x52, 0x99, + 0xa6, 0xd8, 0x89, 0x7b, 0x00, 0x00}; } // namespace web_server } // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f0a7efd12f..5c06a97d47 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -839,6 +839,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf return json::build_json([obj, start_config](JsonObject root) { set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); + int8_t accuracy = traits.get_temperature_accuracy_decimals(); char __buf[16]; if (start_config == DETAIL_ALL) { @@ -873,12 +874,15 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf } } + bool has_state = false; root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); - root["max_temp"] = traits.get_visual_max_temperature(); - root["min_temp"] = traits.get_visual_min_temperature(); + root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), accuracy); + root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), accuracy); root["step"] = traits.get_visual_temperature_step(); if (traits.get_supports_action()) { root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); + root["state"] = root["action"]; + has_state = true; } if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) { root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); @@ -896,14 +900,23 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); } if (traits.get_supports_current_temperature()) { - root["current_temperature"] = obj->current_temperature; + if (!std::isnan(obj->current_temperature)) { + root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, accuracy); + } else { + root["current_temperature"] = "NA"; + } } if (traits.get_supports_two_point_target_temperature()) { - root["current_temperature_low"] = obj->target_temperature_low; - root["current_temperature_high"] = obj->target_temperature_low; + root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, accuracy); + root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, accuracy); + if (!has_state) { + root["state"] = + value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f, accuracy); + } } else { - root["target_temperature"] = obj->target_temperature; - root["state"] = obj->target_temperature; + root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, accuracy); + if (!has_state) + root["state"] = root["target_temperature"]; } }); } From 4759b4fe2e00f704e64d5b102bd907e1c095316b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:57:42 +1200 Subject: [PATCH 015/838] Add vector include (#3707) --- esphome/components/e131/e131.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index 648cfb4585..8bf8999c21 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -4,9 +4,10 @@ #include "esphome/core/component.h" +#include #include #include -#include +#include class UDP; From 7f41b7cd939c1e488fcc2fb66490ab007be6c54a Mon Sep 17 00:00:00 2001 From: Peter Galantha Date: Wed, 10 Aug 2022 19:00:09 -0700 Subject: [PATCH 016/838] Add state_class total (#3608) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 2 ++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 11 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3e9a62f3d8..88a74540d0 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -473,6 +473,7 @@ enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; STATE_CLASS_TOTAL_INCREASING = 2; + STATE_CLASS_TOTAL = 3; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 70f909c07a..b91c9bd600 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -108,6 +108,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_MEASUREMENT"; case enums::STATE_CLASS_TOTAL_INCREASING: return "STATE_CLASS_TOTAL_INCREASING"; + case enums::STATE_CLASS_TOTAL: + return "STATE_CLASS_TOTAL"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ec1cdc35ac..f9981fdbb7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -53,6 +53,7 @@ enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, STATE_CLASS_TOTAL_INCREASING = 2, + STATE_CLASS_TOTAL = 3, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index e88cd05c31..d6ba038057 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -109,6 +109,7 @@ STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, + "total": StateClasses.STATE_CLASS_TOTAL, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index c0869631aa..a729791e7e 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -12,6 +12,8 @@ std::string state_class_to_string(StateClass state_class) { return "measurement"; case STATE_CLASS_TOTAL_INCREASING: return "total_increasing"; + case STATE_CLASS_TOTAL: + return "total"; case STATE_CLASS_NONE: default: return ""; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index f29125ee42..ba9edd68d0 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -36,6 +36,7 @@ enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, STATE_CLASS_TOTAL_INCREASING = 2, + STATE_CLASS_TOTAL = 3, }; std::string state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index 0593c19672..4e1e293e94 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -956,6 +956,9 @@ STATE_CLASS_MEASUREMENT = "measurement" # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" +# The state represents a total amount that can both increase and decrease, e.g. a net energy meter. +STATE_CLASS_TOTAL = "total" + KEY_CORE = "core" KEY_TARGET_PLATFORM = "target_platform" KEY_TARGET_FRAMEWORK = "target_framework" From b918abfd54230cac83f0797ed077f15281bc924c Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Sun, 14 Aug 2022 22:21:08 +0200 Subject: [PATCH 017/838] add gradient color V2.0 (#3709) --- esphome/core/color.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/esphome/core/color.h b/esphome/core/color.h index 4e7733ca85..7596eeb0cf 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -139,9 +139,19 @@ struct Color { return Color(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)), uint8_t((uint16_t(b) * 255U / max_rgb)), w); } - Color fade_to_white(uint8_t amnt) { return Color(255, 255, 255, 255) - (*this * amnt); } - Color fade_to_black(uint8_t amnt) { return *this * amnt; } - Color gradient(const Color &to_color, uint8_t amnt) { return (*this * amnt) + (to_color * (255 - amnt)); } + + Color gradient(const Color &to_color, uint8_t amnt) { + Color new_color; + float amnt_f = float(amnt) / 255.0f; + new_color.r = amnt_f * (to_color.r - (*this).r) + (*this).r; + new_color.g = amnt_f * (to_color.g - (*this).g) + (*this).g; + new_color.b = amnt_f * (to_color.b - (*this).b) + (*this).b; + new_color.w = amnt_f * (to_color.w - (*this).w) + (*this).w; + return new_color; + } + Color fade_to_white(uint8_t amnt) { return (*this).gradient(Color::WHITE, amnt); } + Color fade_to_black(uint8_t amnt) { return (*this).gradient(Color::BLACK, amnt); } + Color lighten(uint8_t delta) { return *this + delta; } Color darken(uint8_t delta) { return *this - delta; } From 84c051d097b010b4175a6244fc61f8862bcdada1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 15 Aug 2022 08:23:35 +1200 Subject: [PATCH 018/838] Bump version to 2022.8.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4e1e293e94..59fa466d11 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.0b1" +__version__ = "2022.8.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 0d0733dd940f65e829734c92b534772e6d06c5d0 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Mon, 15 Aug 2022 01:28:29 +0300 Subject: [PATCH 019/838] Webui small fixes (#3713) --- esphome/components/climate/climate_traits.cpp | 11 +- esphome/components/web_server/server_index.h | 1148 ++++++++--------- esphome/components/web_server/web_server.cpp | 18 +- esphome/core/helpers.cpp | 13 + esphome/core/helpers.h | 3 + 5 files changed, 604 insertions(+), 589 deletions(-) diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 16c9cd05be..38ded6cdf7 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -1,19 +1,10 @@ #include "climate_traits.h" -#include namespace esphome { namespace climate { int8_t ClimateTraits::get_temperature_accuracy_decimals() const { - // use printf %g to find number of digits based on temperature step - char buf[32]; - sprintf(buf, "%.5g", this->visual_temperature_step_); - std::string str{buf}; - size_t dot_pos = str.find('.'); - if (dot_pos == std::string::npos) - return 0; - - return str.length() - dot_pos - 1; + return step_to_accuracy_decimals(this->visual_temperature_step_); } } // namespace climate diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 39c170f15b..75c7130151 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,580 +6,580 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xc9, 0x76, 0xdb, 0xc8, 0xb2, 0xe0, 0xba, - 0xcf, 0xe9, 0x3f, 0xe8, 0x0d, 0x84, 0xd2, 0x93, 0x81, 0x12, 0x08, 0x91, 0x94, 0x65, 0xbb, 0x40, 0x41, 0xbc, 0xf2, - 0x50, 0xd7, 0xae, 0xf2, 0x54, 0x96, 0xec, 0x1a, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02, 0x92, - 0x1a, 0x8a, 0xc2, 0x3b, 0xbd, 0xea, 0x55, 0x9f, 0xd3, 0xe3, 0xe2, 0x6d, 0xfa, 0xbc, 0x5e, 0xf4, 0x47, 0xf4, 0xba, - 0x3f, 0xe5, 0xfe, 0x40, 0xbf, 0x4f, 0xe8, 0x88, 0xc8, 0x01, 0x09, 0x90, 0x1a, 0x5c, 0xaf, 0xfa, 0x1e, 0x0f, 0x02, - 0x72, 0x8c, 0x88, 0x8c, 0x8c, 0x29, 0x23, 0xa1, 0xdd, 0xb5, 0x51, 0x36, 0xe4, 0x97, 0x33, 0x66, 0x4d, 0xf8, 0x34, - 0xd9, 0xdb, 0x95, 0xff, 0xb3, 0x68, 0xb4, 0xb7, 0x9b, 0xc4, 0xe9, 0x27, 0x2b, 0x67, 0x49, 0x18, 0x0f, 0xb3, 0xd4, - 0x9a, 0xe4, 0x6c, 0x1c, 0x8e, 0x22, 0x1e, 0x05, 0xf1, 0x34, 0x3a, 0x65, 0xd6, 0xd6, 0xde, 0xee, 0x94, 0xf1, 0xc8, - 0x1a, 0x4e, 0xa2, 0xbc, 0x60, 0x3c, 0x7c, 0x7f, 0xf8, 0x75, 0xeb, 0xd1, 0xde, 0x6e, 0x31, 0xcc, 0xe3, 0x19, 0xb7, - 0x70, 0xc8, 0x70, 0x9a, 0x8d, 0xe6, 0x09, 0xdb, 0x3b, 0x8b, 0x72, 0xeb, 0x82, 0x85, 0x6f, 0x4e, 0x7e, 0x65, 0x43, - 0xee, 0x8f, 0xd8, 0x38, 0x4e, 0xd9, 0xdb, 0x3c, 0x9b, 0xb1, 0x9c, 0x5f, 0x7a, 0xcf, 0x56, 0x57, 0xc4, 0xac, 0xf0, - 0x0e, 0x74, 0xd5, 0x29, 0xe3, 0x6f, 0xce, 0x53, 0xd5, 0xe7, 0x29, 0x13, 0x93, 0x64, 0x79, 0xe1, 0xf1, 0x6b, 0xda, - 0x1c, 0x5c, 0x4e, 0x4f, 0xb2, 0xa4, 0xf0, 0x3e, 0xe9, 0xfa, 0x59, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0x27, 0x51, 0x61, - 0xb4, 0xf4, 0x9e, 0xac, 0x68, 0x32, 0x93, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x7c, 0xca, 0xf2, 0xe8, 0x24, 0x61, 0x5e, - 0xc1, 0x42, 0x87, 0x79, 0xdc, 0x8b, 0xdd, 0x70, 0x8f, 0x5b, 0x71, 0x6a, 0xb1, 0xfe, 0x05, 0xa3, 0x92, 0x05, 0xd3, - 0xad, 0x82, 0xb5, 0xb6, 0x07, 0xe4, 0x1a, 0xc7, 0xa7, 0x73, 0xfd, 0x7e, 0x9e, 0xc7, 0x5c, 0x3d, 0x9f, 0x45, 0xc9, - 0x9c, 0x05, 0x71, 0xe9, 0x06, 0xec, 0x88, 0x0f, 0xc2, 0xd8, 0x7b, 0x42, 0x83, 0xc2, 0x90, 0x8b, 0x71, 0x96, 0x3b, - 0x48, 0xab, 0x18, 0xc7, 0xe6, 0x57, 0x57, 0x0e, 0x0f, 0x17, 0xa5, 0xeb, 0x7e, 0x62, 0xfe, 0x30, 0x4a, 0x12, 0x07, - 0x27, 0xde, 0xd8, 0x28, 0x70, 0xc6, 0xd8, 0xe3, 0x47, 0xf1, 0xc0, 0xed, 0xc5, 0x63, 0x87, 0x33, 0xb7, 0xea, 0x97, - 0x8d, 0x2d, 0xce, 0x1c, 0xee, 0xba, 0x4f, 0xae, 0xef, 0x93, 0x33, 0x3e, 0xcf, 0x01, 0xf6, 0xd2, 0x7b, 0xa3, 0x66, - 0x7e, 0x86, 0xf5, 0x07, 0xd4, 0xb1, 0x07, 0xb0, 0x17, 0xdc, 0xfa, 0x10, 0x9e, 0xc7, 0xe9, 0x28, 0x3b, 0xf7, 0x0f, - 0x26, 0x11, 0xfc, 0x78, 0x97, 0x65, 0x7c, 0x63, 0xc3, 0x39, 0xcb, 0xe2, 0x91, 0xd5, 0x0e, 0x43, 0xb3, 0xf2, 0xf2, - 0xc9, 0xc1, 0xc1, 0xd5, 0x55, 0xa3, 0xc0, 0x4f, 0x23, 0x1e, 0x9f, 0x31, 0xd1, 0x19, 0x00, 0xb0, 0xe1, 0xe7, 0x8c, - 0xb3, 0xd1, 0x01, 0xbf, 0x4c, 0xa0, 0x94, 0x31, 0x5e, 0xd8, 0x80, 0xe3, 0xd3, 0x6c, 0x08, 0x64, 0x4b, 0x0d, 0xc2, - 0x43, 0xd3, 0x9c, 0xcd, 0x92, 0x68, 0xc8, 0xb0, 0x1e, 0x46, 0xaa, 0x7a, 0x54, 0x8d, 0xbc, 0xaf, 0x43, 0xb1, 0xbc, - 0x8e, 0xeb, 0xc5, 0x2c, 0x4c, 0xd9, 0xb9, 0xf5, 0x2a, 0x9a, 0xf5, 0x86, 0x49, 0x54, 0x14, 0xc0, 0xaf, 0x0b, 0x42, - 0x21, 0x9f, 0x0f, 0x81, 0x41, 0x08, 0xc1, 0x05, 0x92, 0x69, 0x12, 0x17, 0xfe, 0xc7, 0xf5, 0x61, 0x51, 0xbc, 0x63, - 0xc5, 0x3c, 0xe1, 0xeb, 0x21, 0xac, 0x05, 0x5f, 0x0b, 0xc3, 0xaf, 0x5d, 0x3e, 0xc9, 0xb3, 0x73, 0xeb, 0x59, 0x9e, - 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0xb8, 0xb0, 0xd2, 0x8c, 0x5b, 0x7a, 0x30, 0x5c, 0x40, 0xdf, 0x7a, 0x5f, - 0x30, 0xeb, 0x78, 0x9e, 0x16, 0xd1, 0x98, 0x41, 0xd3, 0x63, 0x2b, 0xcb, 0xad, 0x63, 0x18, 0xf4, 0x18, 0x96, 0xac, - 0xe0, 0xb0, 0x6b, 0x7c, 0xdb, 0xed, 0xd1, 0x5c, 0x50, 0x78, 0xc8, 0x2e, 0x78, 0xc8, 0x4a, 0x60, 0x4c, 0xab, 0xd0, - 0x68, 0x38, 0xee, 0x22, 0x81, 0x02, 0x16, 0xc6, 0x0c, 0x59, 0xd6, 0x31, 0x1b, 0xeb, 0xc5, 0xf9, 0xb0, 0xb1, 0xa1, - 0x69, 0x0d, 0x34, 0x71, 0xa0, 0x6d, 0xd1, 0x68, 0xeb, 0x09, 0xc4, 0x6b, 0x24, 0x72, 0x3d, 0xe6, 0x4b, 0xf2, 0x1d, - 0x5c, 0xa6, 0xc3, 0xfa, 0xd8, 0x50, 0x59, 0xf2, 0xec, 0x80, 0xe7, 0x71, 0x7a, 0x0a, 0x40, 0xc8, 0x99, 0xcc, 0x26, - 0x65, 0x29, 0x16, 0xff, 0x0d, 0x0b, 0x59, 0xb8, 0x87, 0xa3, 0xe7, 0xcc, 0xb1, 0x0b, 0xea, 0x61, 0x87, 0x21, 0x92, - 0x1e, 0x18, 0x8c, 0xf5, 0x59, 0xc0, 0x36, 0x6d, 0xdb, 0xfb, 0xda, 0xf5, 0x2e, 0x91, 0x83, 0x7c, 0xdf, 0x27, 0xf6, - 0x15, 0x9d, 0xe3, 0xb0, 0x83, 0x40, 0xfb, 0x09, 0x4b, 0x4f, 0xf9, 0xa4, 0xcf, 0x8e, 0xda, 0x83, 0x80, 0x03, 0x54, - 0xa3, 0xf9, 0x90, 0x39, 0xc8, 0x8f, 0x5e, 0x8e, 0xdb, 0x67, 0xd3, 0x81, 0x29, 0x70, 0x61, 0xd6, 0x08, 0xc7, 0xda, - 0xd2, 0xb8, 0x8a, 0x45, 0x15, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x13, 0x96, 0x1b, 0x70, 0xe8, 0x66, 0xbd, 0xda, - 0x0a, 0xce, 0x61, 0x85, 0xa0, 0x9f, 0x35, 0x9e, 0xa7, 0x43, 0x1e, 0x83, 0xe0, 0xb2, 0x37, 0x01, 0x5c, 0xb1, 0x72, - 0x7a, 0xe1, 0x6c, 0xb7, 0x74, 0x9d, 0xd8, 0xdd, 0x64, 0x47, 0xf9, 0x66, 0x67, 0xe0, 0x21, 0x94, 0x9a, 0xf8, 0x12, - 0xf1, 0x18, 0x10, 0x2c, 0xbd, 0xb7, 0x4c, 0x6f, 0xcf, 0x0f, 0x7d, 0xe6, 0x2f, 0xf3, 0x71, 0xc8, 0xfd, 0x69, 0x34, - 0x43, 0x6c, 0x18, 0xf1, 0x40, 0x94, 0x0e, 0x11, 0xba, 0xda, 0xba, 0x20, 0xc5, 0xfc, 0x8a, 0x05, 0x5c, 0x20, 0x08, - 0xec, 0xd9, 0x67, 0xd1, 0x70, 0x02, 0x5b, 0xbc, 0x22, 0xdc, 0x48, 0x6d, 0x87, 0x61, 0xce, 0x22, 0xce, 0x9e, 0x25, - 0x0c, 0xdf, 0x70, 0x05, 0xa0, 0xa7, 0xed, 0x7a, 0xb9, 0xda, 0x77, 0x49, 0xcc, 0x5f, 0x67, 0x30, 0x4f, 0x4f, 0x30, - 0x09, 0x70, 0x71, 0xbe, 0xb1, 0x11, 0x23, 0x8b, 0xec, 0x73, 0x58, 0xad, 0x93, 0x39, 0x08, 0x01, 0x3b, 0xc5, 0x16, - 0x36, 0x50, 0xdb, 0x8b, 0x7d, 0x0e, 0x44, 0x7c, 0x92, 0xa5, 0x1c, 0x86, 0x03, 0x78, 0x35, 0x07, 0xf9, 0xd1, 0x6c, - 0xc6, 0xd2, 0xd1, 0x93, 0x49, 0x9c, 0x8c, 0x80, 0x1a, 0x25, 0xe0, 0x9b, 0xb1, 0x10, 0xf0, 0x04, 0x64, 0x82, 0x9b, - 0x31, 0xa2, 0xe5, 0x43, 0x46, 0xe6, 0xa1, 0x6d, 0xf7, 0x50, 0x02, 0x49, 0x2c, 0x50, 0x06, 0xd1, 0xc2, 0xbd, 0x03, - 0xd1, 0x5f, 0xb8, 0x7c, 0x33, 0x8c, 0xf5, 0x32, 0x4a, 0x02, 0xbf, 0x41, 0x49, 0x03, 0xf4, 0x67, 0x20, 0x03, 0x7b, - 0x28, 0xb8, 0xbe, 0x97, 0x52, 0x27, 0x65, 0x0a, 0x43, 0x20, 0xc0, 0x10, 0x25, 0x88, 0xa4, 0xc1, 0xdb, 0x2c, 0xb9, - 0x1c, 0xc7, 0x49, 0x72, 0x30, 0x9f, 0xcd, 0xb2, 0x9c, 0x7b, 0xdf, 0x84, 0x0b, 0x9e, 0x55, 0xb8, 0xd2, 0x26, 0x2f, - 0xce, 0x63, 0x8e, 0x04, 0x75, 0x17, 0xc3, 0x08, 0x96, 0xfa, 0x71, 0x96, 0x25, 0x2c, 0x4a, 0x01, 0x0d, 0xd6, 0xb7, - 0xed, 0x20, 0x9d, 0x27, 0x49, 0xef, 0x04, 0x86, 0xfd, 0xd4, 0xa3, 0x6a, 0x21, 0xf1, 0x03, 0x7a, 0xde, 0xcf, 0xf3, - 0xe8, 0x12, 0x1a, 0x62, 0x1b, 0xe0, 0x45, 0x58, 0xad, 0x6f, 0x0e, 0xde, 0xbc, 0xf6, 0x05, 0xe3, 0xc7, 0xe3, 0x4b, - 0x00, 0xb4, 0xac, 0xa4, 0xe6, 0x38, 0xcf, 0xa6, 0x8d, 0xa9, 0x91, 0x0e, 0x71, 0xc8, 0x7a, 0xd7, 0x80, 0x10, 0xd3, - 0xc8, 0xb0, 0x4a, 0xcc, 0x84, 0xe0, 0x35, 0xf1, 0xb3, 0xac, 0xc4, 0x3d, 0xd0, 0xc7, 0x87, 0x40, 0x14, 0xc3, 0x94, - 0x37, 0x43, 0xcb, 0xf3, 0xcb, 0x45, 0x1c, 0x12, 0x9c, 0x33, 0xd4, 0xbf, 0x08, 0xe3, 0x30, 0x82, 0xd9, 0x17, 0x62, - 0xc0, 0x52, 0x41, 0x1c, 0x97, 0xa5, 0x97, 0x68, 0x26, 0x46, 0x89, 0x87, 0x02, 0x85, 0xc3, 0x36, 0xba, 0xba, 0x62, - 0xf0, 0xe2, 0x7a, 0xdf, 0x86, 0x8b, 0x48, 0xe1, 0x83, 0x1a, 0x0a, 0xf7, 0x57, 0x20, 0xe4, 0x04, 0x6a, 0xb2, 0x33, - 0xd0, 0x83, 0x00, 0xe7, 0x37, 0x1e, 0xe8, 0xff, 0x04, 0xa1, 0x58, 0xeb, 0x78, 0xa0, 0x41, 0x9f, 0x4c, 0xa2, 0xf4, - 0x94, 0x8d, 0x82, 0x84, 0x95, 0x52, 0xf2, 0xee, 0x5b, 0xb0, 0xc6, 0xc0, 0x4e, 0x85, 0xf5, 0xfc, 0xf0, 0xd5, 0x4b, - 0xb9, 0x72, 0x35, 0x61, 0x0c, 0x8b, 0x34, 0x07, 0xb5, 0x0a, 0x62, 0x5b, 0x8a, 0xe3, 0x67, 0x5c, 0x49, 0x6f, 0x51, - 0x12, 0x17, 0xef, 0x67, 0x60, 0x62, 0xb0, 0xb7, 0x30, 0x0c, 0x4c, 0x1f, 0xc2, 0x54, 0x54, 0x0e, 0xf3, 0x89, 0x8a, - 0x91, 0x2e, 0x82, 0xce, 0x02, 0x53, 0xf1, 0x9a, 0x39, 0x6e, 0x09, 0xac, 0xca, 0xe3, 0xa1, 0x15, 0x8d, 0x46, 0x2f, - 0xd2, 0x98, 0xc7, 0x51, 0x12, 0xff, 0x4e, 0x94, 0x5c, 0x20, 0x8f, 0xf1, 0x9e, 0x5c, 0x04, 0xc0, 0x9d, 0x7a, 0x24, - 0xae, 0x12, 0xb2, 0x6b, 0x44, 0x0c, 0x21, 0x2d, 0x93, 0xf0, 0x68, 0x20, 0xc1, 0x4b, 0xfc, 0xd9, 0xbc, 0x98, 0x20, - 0x61, 0xe5, 0xc0, 0x28, 0xc8, 0xb3, 0x93, 0x82, 0xe5, 0x67, 0x6c, 0xa4, 0x39, 0xa0, 0x00, 0xac, 0xa8, 0x39, 0x18, - 0x2f, 0x34, 0xa3, 0xa3, 0x74, 0x28, 0x83, 0xa1, 0x7a, 0xa6, 0x98, 0x65, 0x92, 0x99, 0xb5, 0x85, 0xa3, 0xa5, 0x80, - 0x23, 0x8c, 0x0a, 0x29, 0x09, 0xf2, 0x50, 0x61, 0x38, 0x01, 0x29, 0x04, 0x5a, 0xc1, 0xdc, 0xe6, 0x4a, 0x93, 0x3d, - 0x9b, 0x93, 0x4a, 0xc8, 0xa1, 0x23, 0x6c, 0x64, 0x82, 0x34, 0x77, 0x61, 0x57, 0x81, 0x94, 0x97, 0xe0, 0x0a, 0x29, - 0xa2, 0xcc, 0x1c, 0x64, 0x80, 0xf0, 0x5b, 0xa1, 0x0b, 0x7d, 0x6c, 0x41, 0x6c, 0xe0, 0xeb, 0x95, 0x07, 0xc2, 0x4a, - 0xbc, 0x2b, 0x44, 0xbc, 0x6b, 0xc0, 0xc6, 0x89, 0x91, 0x9f, 0xbc, 0x35, 0xee, 0xa7, 0xd9, 0xfe, 0x70, 0xc8, 0x8a, - 0x22, 0x03, 0xd8, 0xd6, 0xa8, 0xfd, 0x75, 0x86, 0x16, 0x50, 0xd2, 0xd5, 0xb2, 0xce, 0x2e, 0x48, 0x83, 0x9b, 0x6a, - 0x45, 0xe9, 0xf4, 0xc0, 0xfe, 0xf8, 0x11, 0x64, 0xb6, 0x27, 0xc9, 0x00, 0x54, 0x5f, 0x36, 0xfc, 0x84, 0x3d, 0x53, - 0xa7, 0xcc, 0x4a, 0xfb, 0xd2, 0xa9, 0x83, 0xe4, 0xc1, 0xb0, 0x6e, 0x69, 0x2c, 0xe8, 0xca, 0xa1, 0x71, 0x35, 0xa4, - 0x82, 0x5c, 0x9c, 0x92, 0xca, 0x36, 0x96, 0x11, 0xac, 0xb6, 0xd2, 0x23, 0xd2, 0x2b, 0x6c, 0x0a, 0x02, 0xf4, 0x88, - 0x0d, 0x7a, 0xb2, 0x3e, 0xcc, 0x05, 0xe5, 0x72, 0xf6, 0xdb, 0x9c, 0x15, 0x5c, 0xb0, 0x2e, 0x8c, 0x5b, 0xc0, 0xb8, - 0xe5, 0x92, 0x75, 0x58, 0xb3, 0x1d, 0x57, 0xc1, 0xf6, 0x66, 0x86, 0x7a, 0xac, 0x40, 0x4e, 0xbe, 0x99, 0x9d, 0x10, - 0x56, 0xe6, 0x5e, 0x5d, 0x7d, 0xab, 0x06, 0xa9, 0x96, 0x52, 0xdb, 0x40, 0x8d, 0x35, 0xb1, 0x55, 0x93, 0x91, 0xed, - 0x4a, 0x85, 0xba, 0xd6, 0xe9, 0xd5, 0xf8, 0x00, 0xf6, 0x5c, 0x5b, 0xb3, 0x74, 0x65, 0x6c, 0xbf, 0x55, 0x34, 0x7d, - 0x23, 0x46, 0x26, 0x6b, 0x94, 0xdd, 0xce, 0x3d, 0x6a, 0xc7, 0x43, 0xdb, 0xa5, 0xba, 0x4a, 0x30, 0xcc, 0xeb, 0x82, - 0xa1, 0x09, 0xf5, 0x4c, 0x77, 0xb1, 0x35, 0x53, 0xb1, 0x50, 0xad, 0xb5, 0x72, 0x20, 0x78, 0x78, 0x04, 0xc6, 0xc9, - 0x4a, 0xff, 0xe0, 0x75, 0x34, 0x65, 0x48, 0x51, 0xef, 0xba, 0x06, 0xd2, 0x81, 0x80, 0x26, 0x83, 0xa6, 0x7a, 0xe3, - 0xae, 0xb0, 0x9a, 0xea, 0xfb, 0x2b, 0x06, 0x2b, 0x02, 0xec, 0xeb, 0x72, 0xc5, 0x12, 0x91, 0xde, 0x14, 0x5c, 0xa2, - 0xe9, 0x23, 0xca, 0xc4, 0x9a, 0x90, 0x82, 0x07, 0xe4, 0x61, 0xf9, 0x1b, 0x0b, 0x27, 0x5b, 0x31, 0x85, 0x23, 0x47, - 0x99, 0x02, 0x74, 0x26, 0x25, 0x00, 0xe2, 0x92, 0x7e, 0xd6, 0x36, 0x16, 0x92, 0xed, 0x00, 0xf9, 0xc0, 0x1f, 0x27, - 0x11, 0x77, 0x3a, 0x5b, 0x6d, 0x17, 0xf8, 0x10, 0x84, 0x38, 0xe8, 0x08, 0x30, 0xef, 0x2b, 0x54, 0x18, 0xa2, 0x12, - 0xbb, 0xdc, 0x07, 0xa3, 0x68, 0x12, 0x8f, 0xb9, 0x93, 0xa1, 0x12, 0x71, 0x4b, 0x96, 0x80, 0x92, 0xd1, 0xfb, 0x0a, - 0xa4, 0x04, 0x17, 0xd2, 0x45, 0x54, 0x6b, 0x81, 0xa6, 0x20, 0x25, 0x29, 0x45, 0x5a, 0x50, 0x41, 0x60, 0x08, 0x95, - 0x9e, 0xe2, 0x28, 0xd0, 0x6f, 0x71, 0x5f, 0x0c, 0x1a, 0x2c, 0x59, 0x94, 0x71, 0x3f, 0x5e, 0x2e, 0x04, 0x35, 0xec, - 0xf3, 0xec, 0x65, 0x76, 0xce, 0xf2, 0x27, 0x11, 0xc2, 0x1e, 0x88, 0xee, 0x25, 0x48, 0x7a, 0x12, 0xe8, 0xac, 0xa7, - 0x78, 0xe5, 0x8c, 0x90, 0x86, 0x85, 0x98, 0xc6, 0xa8, 0x08, 0x41, 0xcb, 0x11, 0xed, 0x53, 0xdc, 0x52, 0xb4, 0xf7, - 0x50, 0x95, 0x30, 0xcd, 0x5b, 0xfb, 0x2f, 0xeb, 0xbc, 0x05, 0x23, 0xcc, 0x14, 0xb7, 0xd6, 0x77, 0xac, 0xeb, 0x49, - 0xdd, 0xec, 0x48, 0xde, 0x32, 0x94, 0x19, 0xe8, 0x8f, 0xab, 0xab, 0xca, 0x48, 0x07, 0x65, 0xaa, 0xa5, 0x39, 0x5a, - 0x4e, 0x62, 0x4b, 0xb8, 0x25, 0x28, 0x23, 0x34, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x5d, 0xe4, 0xc5, 0x3d, 0xa7, 0xa1, - 0x8e, 0x00, 0x8a, 0x69, 0x4d, 0x23, 0xf5, 0x79, 0xa0, 0x2b, 0x50, 0x29, 0x29, 0x6d, 0xe4, 0x55, 0x4d, 0x04, 0xc4, - 0xe9, 0x88, 0xe5, 0xc2, 0x41, 0x93, 0x3a, 0x14, 0x26, 0x4c, 0x81, 0xa1, 0xd9, 0x08, 0x24, 0xbc, 0x42, 0x00, 0xcc, - 0x13, 0x7f, 0x92, 0x15, 0x5c, 0xd7, 0x99, 0xd0, 0xc7, 0x57, 0x57, 0xb1, 0xf0, 0x17, 0x91, 0x01, 0x72, 0x36, 0xcd, - 0xce, 0xd8, 0x0a, 0xa8, 0x7b, 0x6a, 0x30, 0x13, 0x64, 0x63, 0x18, 0x50, 0xa2, 0xa0, 0x5a, 0x66, 0x49, 0x3c, 0x64, - 0x5a, 0x4b, 0x4d, 0x7d, 0x30, 0xe8, 0xd8, 0x05, 0xc8, 0x08, 0xe6, 0xee, 0xed, 0xed, 0xb5, 0xbd, 0x8e, 0x5b, 0x0a, - 0x82, 0x2f, 0x96, 0x28, 0x7a, 0x83, 0x7e, 0x94, 0x26, 0xf8, 0x2a, 0x59, 0xc0, 0x5d, 0x43, 0x29, 0x72, 0xe1, 0x27, - 0x79, 0x52, 0x10, 0xbb, 0xde, 0x08, 0x06, 0xe5, 0x4c, 0x09, 0x6e, 0x34, 0x71, 0xc5, 0xb6, 0x7d, 0xa7, 0xc9, 0xa6, - 0xd9, 0x49, 0xed, 0x30, 0xb5, 0x30, 0x72, 0xcd, 0x0b, 0xed, 0x01, 0x9b, 0xcb, 0x83, 0x56, 0x22, 0x55, 0x03, 0xaf, - 0x03, 0x84, 0xc2, 0xd3, 0x75, 0x96, 0x50, 0xaa, 0x3a, 0x4b, 0x21, 0xae, 0x37, 0xd0, 0x5b, 0x26, 0xc1, 0x5c, 0x45, - 0x82, 0x03, 0x29, 0x10, 0x38, 0x7a, 0x64, 0x62, 0xbd, 0x9e, 0xc0, 0xf2, 0x9c, 0x44, 0xc3, 0x4f, 0x1a, 0xdc, 0x8a, - 0xec, 0x4d, 0x36, 0x70, 0x1a, 0x25, 0xa1, 0x21, 0xae, 0x4c, 0xbc, 0x95, 0x84, 0xae, 0x6d, 0x14, 0x70, 0xc8, 0x96, - 0xd8, 0xbe, 0xb9, 0xd0, 0x4d, 0x6e, 0x97, 0xec, 0xa1, 0xfc, 0x27, 0xcd, 0x25, 0x37, 0xb0, 0x1c, 0x57, 0xd2, 0x80, - 0x2b, 0xc6, 0x83, 0xa5, 0x69, 0x40, 0x02, 0x7c, 0x57, 0x8e, 0xe2, 0xe2, 0x7a, 0x12, 0xfc, 0xa9, 0x60, 0x3e, 0x35, - 0x66, 0xba, 0x15, 0x52, 0x2d, 0xe1, 0xa4, 0x19, 0xac, 0x41, 0x93, 0xc6, 0x83, 0x12, 0x35, 0xdf, 0xa2, 0xa1, 0x42, - 0x1c, 0x7f, 0x2a, 0xaa, 0xd0, 0x04, 0x43, 0x30, 0x72, 0xaf, 0x90, 0x0c, 0x97, 0x2d, 0x8b, 0x16, 0x29, 0x53, 0x63, - 0x52, 0xa9, 0x9a, 0xe5, 0x32, 0x30, 0xb0, 0x68, 0xb7, 0xfa, 0xd2, 0x12, 0x57, 0x22, 0x37, 0x0d, 0xb5, 0x30, 0x29, - 0x94, 0x37, 0xe1, 0xe4, 0xe8, 0x77, 0x29, 0xeb, 0xdd, 0xc4, 0x27, 0x57, 0xf8, 0xe4, 0xbe, 0xe1, 0x43, 0x99, 0xbc, - 0x5d, 0xf4, 0x8b, 0xe0, 0x9b, 0x5a, 0x25, 0xda, 0xa7, 0x3e, 0x0a, 0x66, 0x57, 0x0b, 0x5d, 0x10, 0x28, 0x92, 0x4d, - 0xd2, 0xbe, 0xe4, 0x37, 0x14, 0x1b, 0x95, 0x67, 0x94, 0xb9, 0x62, 0x83, 0xd4, 0xbc, 0xd2, 0xcc, 0x4b, 0xdd, 0x86, - 0xfd, 0x5e, 0x96, 0x92, 0x4e, 0x5c, 0x50, 0x26, 0xf6, 0x6e, 0xa2, 0x8d, 0x97, 0x86, 0x99, 0xb0, 0x7e, 0x85, 0xb1, - 0x53, 0xa3, 0x50, 0x2a, 0x45, 0x20, 0x8e, 0x8d, 0xaf, 0x95, 0x65, 0x90, 0xf9, 0x2b, 0xec, 0x29, 0x00, 0x25, 0x81, - 0xc5, 0xd7, 0x54, 0xf2, 0xa2, 0xb0, 0x4e, 0xc7, 0x35, 0xa2, 0x63, 0x25, 0x42, 0x6b, 0x22, 0x5f, 0xeb, 0xb3, 0xd8, - 0xaf, 0xb9, 0x84, 0x26, 0x25, 0xf3, 0x7e, 0x1e, 0xd8, 0x2a, 0x10, 0x51, 0xe9, 0xb6, 0xa4, 0x9f, 0x90, 0x43, 0xba, - 0x4c, 0xf4, 0xda, 0x48, 0x06, 0xad, 0x53, 0x21, 0xd1, 0xd2, 0x41, 0x18, 0x39, 0xe8, 0xb8, 0xd3, 0x5a, 0x2c, 0x11, - 0xb2, 0x69, 0x6f, 0x12, 0x2b, 0xa2, 0x73, 0x9a, 0xa3, 0x09, 0x67, 0xea, 0x74, 0xc7, 0x01, 0x74, 0x40, 0xec, 0x2f, - 0xb1, 0xde, 0x4a, 0xb3, 0xd3, 0xf5, 0x2b, 0x87, 0xef, 0xea, 0x2a, 0x41, 0x7e, 0x10, 0x06, 0x2f, 0xac, 0x59, 0x5f, - 0xc9, 0xde, 0xfd, 0x97, 0xd8, 0x8a, 0xec, 0xcf, 0xaa, 0xa4, 0xf2, 0x14, 0x6a, 0x9c, 0x5b, 0x5f, 0x27, 0x66, 0x86, - 0x16, 0x55, 0xc5, 0x81, 0x21, 0xd5, 0x0f, 0x94, 0xc2, 0xae, 0x50, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x39, - 0x39, 0x2f, 0x6b, 0xab, 0x5c, 0xc8, 0x8d, 0x0d, 0xd3, 0x67, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x39, 0x17, 0x97, - 0x29, 0x59, 0x30, 0x4f, 0x94, 0x3a, 0x5a, 0x72, 0x40, 0xdb, 0x3d, 0xf4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e, - 0x45, 0x78, 0xda, 0x28, 0x9f, 0xa4, 0xd1, 0x01, 0x78, 0xa1, 0x09, 0x49, 0x4e, 0xb8, 0x69, 0x8b, 0x16, 0xc3, 0x09, - 0xc3, 0x10, 0xb8, 0xb2, 0x27, 0x4c, 0xd9, 0xb3, 0x86, 0x78, 0x8b, 0x81, 0xd9, 0x6a, 0xd8, 0xcb, 0x66, 0xf7, 0x9a, - 0xf9, 0x0f, 0x6b, 0x04, 0xb2, 0x6d, 0xaa, 0xea, 0xca, 0xc6, 0xbb, 0x14, 0x91, 0x18, 0x61, 0x5b, 0x35, 0xb6, 0xb4, - 0xf5, 0x7b, 0x0d, 0xf7, 0xba, 0x72, 0xcc, 0x6b, 0x4a, 0xb5, 0xa1, 0x87, 0x95, 0x9b, 0xc3, 0x4c, 0x47, 0x5e, 0xac, - 0xa0, 0xdb, 0x13, 0x41, 0x21, 0x70, 0x22, 0xb4, 0x3d, 0xa8, 0xb8, 0x81, 0x48, 0xc9, 0x95, 0x56, 0xcd, 0xe6, 0xc9, - 0x48, 0x02, 0x0b, 0x2e, 0x2c, 0x97, 0x7c, 0x74, 0x1e, 0x27, 0x49, 0x55, 0xfa, 0xa7, 0x0a, 0x78, 0x31, 0xec, 0x6d, - 0xa2, 0x5d, 0x60, 0x34, 0x57, 0x20, 0xb8, 0xda, 0x08, 0x7b, 0xef, 0xb8, 0xd5, 0xba, 0x8b, 0x88, 0x23, 0x37, 0xa3, - 0x11, 0x50, 0x8f, 0x11, 0x56, 0xcd, 0xda, 0xfb, 0xcf, 0x30, 0xa4, 0x66, 0xe0, 0x83, 0xea, 0x8c, 0x8a, 0x3f, 0xcb, - 0x9e, 0xfa, 0x4c, 0xf4, 0x6e, 0x55, 0x5d, 0xcd, 0x80, 0x8a, 0x0a, 0x7c, 0x98, 0x21, 0x96, 0xb6, 0x0a, 0x04, 0xe4, - 0x7a, 0x58, 0x94, 0x02, 0x26, 0x69, 0xb0, 0xa0, 0x14, 0x58, 0x6b, 0x65, 0xf7, 0xf2, 0xb6, 0x60, 0x0e, 0x85, 0xc2, - 0x45, 0xff, 0x27, 0xd9, 0x74, 0x86, 0x96, 0x59, 0x83, 0xa9, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa2, 0xac, 0xd6, - 0x87, 0x76, 0x64, 0x8d, 0x9f, 0xb4, 0xa3, 0x0c, 0x0e, 0xd5, 0x5c, 0x17, 0xd5, 0xed, 0xe6, 0xa6, 0x88, 0x59, 0xc5, - 0xe3, 0x3e, 0xe9, 0x6d, 0x6d, 0x4d, 0x7a, 0x9a, 0x06, 0x24, 0x93, 0x24, 0xc3, 0x9b, 0x0c, 0x50, 0x56, 0xc4, 0x59, - 0x94, 0x0d, 0xf2, 0x2d, 0xca, 0x12, 0xd7, 0xef, 0xbb, 0xde, 0x7e, 0xcd, 0xb3, 0xf6, 0xf6, 0xaf, 0x77, 0x91, 0xab, - 0x3a, 0xe9, 0x41, 0x1e, 0x0d, 0xa0, 0x68, 0xc9, 0xa6, 0x0c, 0x17, 0xd3, 0x6c, 0xc4, 0x02, 0x1b, 0xba, 0xa7, 0x76, - 0xa9, 0xb4, 0x32, 0x6c, 0x8e, 0x94, 0x39, 0x8b, 0x77, 0xf5, 0x48, 0x6a, 0xb0, 0x0f, 0x2c, 0xa0, 0xcd, 0x85, 0xef, - 0xc3, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0xa1, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xcb, 0x48, 0x16, 0xab, 0xa1, 0xa4, - 0xbe, 0xef, 0x7f, 0x1f, 0xdc, 0xde, 0xa3, 0xf2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0x8a, 0x88, 0xbd, 0x34, - 0xa7, 0x03, 0x8d, 0x53, 0x00, 0x65, 0x0e, 0x41, 0x93, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0xfd, 0x97, 0xca, 0xa5, 0xce, - 0xc0, 0x85, 0x00, 0x27, 0x3f, 0x89, 0x79, 0x0b, 0xcf, 0x23, 0x6d, 0x6f, 0x21, 0x2a, 0x30, 0xae, 0x48, 0x71, 0xe9, - 0x52, 0x79, 0x83, 0xde, 0xc7, 0xf0, 0x18, 0x9a, 0xad, 0xaf, 0x2f, 0x9c, 0x57, 0x11, 0x9f, 0xf8, 0x79, 0x94, 0x8e, - 0xb2, 0xa9, 0xe3, 0x6e, 0xda, 0xb6, 0xeb, 0x17, 0xe4, 0x89, 0x7c, 0xe5, 0x96, 0xeb, 0xc7, 0xde, 0x84, 0x85, 0x76, - 0xdf, 0xde, 0xfc, 0xe8, 0xbd, 0x67, 0xe1, 0xf1, 0xee, 0xfa, 0x62, 0xc2, 0xca, 0xbd, 0x63, 0xef, 0x42, 0xc7, 0xdc, - 0xbd, 0xb7, 0x28, 0x65, 0xa0, 0x57, 0xb8, 0x77, 0x21, 0xc1, 0x00, 0x76, 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0xf7, 0x74, - 0x20, 0x22, 0xe3, 0xb4, 0x57, 0x57, 0x76, 0x46, 0x11, 0x03, 0x7b, 0x4d, 0x3b, 0xab, 0x1b, 0x1b, 0x95, 0x9a, 0xaf, - 0x4a, 0xbd, 0x21, 0x0b, 0x6b, 0x9e, 0xba, 0x77, 0x48, 0x47, 0x2b, 0xf5, 0x8d, 0x3c, 0x64, 0xa4, 0x34, 0x97, 0xed, - 0x04, 0xc7, 0xd8, 0xe2, 0xab, 0xb7, 0xf5, 0x91, 0x88, 0x52, 0xf8, 0x31, 0x58, 0x2f, 0x11, 0xa8, 0x6f, 0x70, 0x70, - 0xbc, 0xc3, 0x70, 0x6b, 0xd7, 0xe9, 0x07, 0xce, 0x5a, 0xab, 0x75, 0xf5, 0xf3, 0xd6, 0xd1, 0x2f, 0x51, 0xeb, 0xf7, - 0xfd, 0xd6, 0x4f, 0x03, 0xf7, 0xca, 0xf9, 0x79, 0xab, 0x7f, 0x24, 0xdf, 0x8e, 0x7e, 0xd9, 0xfb, 0xb9, 0x18, 0x7c, - 0x29, 0x0a, 0xd7, 0x5d, 0x77, 0xeb, 0x14, 0x3c, 0xa5, 0x70, 0xab, 0xd5, 0xda, 0x83, 0xa7, 0x39, 0x3c, 0xe1, 0xcf, - 0x73, 0xf8, 0x71, 0x75, 0x64, 0xfd, 0x9b, 0x9f, 0xd3, 0x7f, 0xfb, 0x73, 0x3e, 0xc0, 0x31, 0x8f, 0x7e, 0xf9, 0xb9, - 0xb0, 0xef, 0xed, 0x85, 0x5b, 0x83, 0x4d, 0xd7, 0xd1, 0x35, 0x5f, 0x86, 0xd5, 0x23, 0xb4, 0x3a, 0xfa, 0x45, 0xbe, - 0xd9, 0xf7, 0x8e, 0x77, 0xf7, 0xc2, 0xc1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x1d, 0xe7, 0x99, - 0xc1, 0xe8, 0xf7, 0xe0, 0xe7, 0x29, 0xfc, 0xb4, 0xe1, 0xe7, 0x19, 0xfc, 0xfc, 0x05, 0xba, 0x89, 0xf8, 0xdb, 0x15, - 0xc5, 0x42, 0xae, 0xf0, 0xc0, 0x22, 0x82, 0x55, 0x70, 0xd7, 0xb7, 0x62, 0x6f, 0x4c, 0x44, 0x83, 0x7d, 0xe8, 0xfb, - 0x3e, 0x86, 0x49, 0x9d, 0xc5, 0xc7, 0x75, 0x58, 0x74, 0xe4, 0x9c, 0xf5, 0x80, 0x79, 0x22, 0x72, 0x50, 0x04, 0x5c, - 0x9c, 0xad, 0x16, 0x78, 0xb8, 0xea, 0x8d, 0xc2, 0x31, 0x73, 0xc0, 0x28, 0x78, 0xce, 0xf0, 0xa1, 0xeb, 0x7a, 0xcf, - 0xe4, 0x99, 0x21, 0xee, 0x73, 0xc1, 0x5a, 0x69, 0x26, 0x4c, 0x1a, 0xdb, 0xf5, 0x66, 0x2b, 0x2a, 0x61, 0x5b, 0xa7, - 0xa7, 0x50, 0xb7, 0x2e, 0x0e, 0xda, 0xbe, 0x67, 0xd1, 0x27, 0xdc, 0x92, 0xaf, 0x8d, 0x43, 0xe0, 0x25, 0x4b, 0xbe, - 0x69, 0x34, 0x1a, 0x36, 0xa2, 0x70, 0xc7, 0x1e, 0x33, 0x98, 0x61, 0xc9, 0x44, 0xe4, 0xa4, 0x34, 0x85, 0x65, 0x0b, - 0x93, 0xbf, 0x8d, 0x72, 0xbe, 0x5e, 0x19, 0xb6, 0x61, 0xcd, 0x92, 0x6d, 0x5a, 0xfa, 0x77, 0x98, 0x02, 0x4d, 0x4b, - 0x3a, 0xff, 0x30, 0xc7, 0x0f, 0x53, 0x42, 0xeb, 0xb5, 0xc3, 0xc1, 0x43, 0x2f, 0x40, 0xee, 0x88, 0x7e, 0xce, 0x5b, - 0x54, 0x63, 0xf0, 0x57, 0x86, 0x19, 0x3c, 0x31, 0x1f, 0x86, 0x68, 0x16, 0xa5, 0x0e, 0x6e, 0xa5, 0x28, 0xee, 0x5f, - 0xe0, 0xce, 0x48, 0x4b, 0xef, 0x20, 0x54, 0x3b, 0xe6, 0x30, 0x67, 0xec, 0xfb, 0x28, 0xf9, 0xc4, 0x72, 0xe7, 0xc2, - 0xeb, 0x74, 0xbf, 0xa2, 0xce, 0x1e, 0xda, 0x66, 0xaf, 0xaa, 0x63, 0x34, 0x65, 0x16, 0xa8, 0x23, 0xc2, 0x56, 0xc7, - 0xcb, 0x31, 0xaa, 0x85, 0x24, 0x28, 0xbc, 0x2c, 0xec, 0x12, 0x87, 0xdb, 0xbb, 0xc5, 0xd9, 0xe9, 0x9e, 0x1d, 0xd8, - 0x36, 0x58, 0xfc, 0x87, 0x14, 0xb6, 0x12, 0x86, 0x05, 0x18, 0x64, 0xbb, 0x71, 0x8f, 0x6f, 0x6e, 0x56, 0x01, 0x27, - 0x3c, 0x48, 0xa7, 0xee, 0x89, 0x17, 0x79, 0x93, 0x10, 0x06, 0x1c, 0x42, 0x33, 0xec, 0xd2, 0x1b, 0xee, 0xc6, 0x72, - 0x1a, 0x8c, 0x85, 0xf8, 0x49, 0x54, 0xf0, 0x17, 0x18, 0x8f, 0x08, 0x87, 0x68, 0xec, 0xfb, 0xec, 0x82, 0x0d, 0x95, - 0x9d, 0x01, 0x84, 0x8a, 0xdc, 0x9e, 0x3b, 0x0c, 0x8d, 0x66, 0x30, 0x77, 0x18, 0x1e, 0xf6, 0x6d, 0xd8, 0x4b, 0xb0, - 0x2b, 0xc3, 0xe8, 0xa8, 0x33, 0xe8, 0xa7, 0x21, 0xc8, 0x5a, 0x4d, 0x5b, 0x59, 0x34, 0xaf, 0x15, 0x75, 0x07, 0x7d, - 0xe7, 0x0c, 0x8c, 0x74, 0xb0, 0xc5, 0x1d, 0x7c, 0xc3, 0x08, 0x45, 0x11, 0xbe, 0x63, 0xa7, 0xcf, 0x2e, 0x66, 0x8e, - 0xbd, 0xbb, 0x65, 0x6f, 0x62, 0xa9, 0x67, 0x03, 0x7b, 0xc1, 0xdc, 0xe1, 0xb9, 0x6b, 0x76, 0xde, 0x1e, 0x20, 0xa8, - 0x58, 0x88, 0x93, 0x9f, 0xf7, 0xed, 0x3d, 0x31, 0x75, 0x1b, 0x06, 0x4d, 0xe5, 0xf2, 0xe3, 0x8a, 0x1e, 0x12, 0xaa, - 0xaa, 0xab, 0x82, 0x0e, 0xca, 0xba, 0x81, 0x33, 0x31, 0x91, 0x68, 0xe1, 0x64, 0x92, 0x0a, 0xe0, 0xf0, 0x60, 0x33, - 0x98, 0xd4, 0xe8, 0xb6, 0x3d, 0xe8, 0x9f, 0x07, 0xf7, 0xec, 0x7b, 0xea, 0xe5, 0x94, 0x05, 0xe0, 0x5d, 0xd0, 0xf4, - 0xa7, 0xa8, 0x45, 0xe0, 0xe7, 0x8c, 0x01, 0x92, 0xe7, 0x54, 0x34, 0x92, 0x45, 0x73, 0x2c, 0x3a, 0x0c, 0x10, 0x54, - 0xaf, 0xd0, 0xd6, 0x9f, 0x58, 0x93, 0x51, 0x48, 0xb0, 0x6f, 0x6c, 0xc0, 0xd2, 0x6c, 0x76, 0x06, 0x78, 0xde, 0x90, - 0xf3, 0xe2, 0xfb, 0x98, 0x83, 0x4a, 0xd8, 0xda, 0xb3, 0xdd, 0xbe, 0x6d, 0xe1, 0xd2, 0xf6, 0xb2, 0xcd, 0x50, 0x50, - 0x38, 0xde, 0x7c, 0xcf, 0x82, 0xc9, 0x5e, 0xd8, 0xee, 0x3b, 0xb9, 0x50, 0x1d, 0x09, 0x9e, 0x5b, 0x0a, 0x09, 0xde, - 0xf6, 0x26, 0x20, 0xd0, 0x91, 0x73, 0xd7, 0xed, 0x4d, 0x55, 0x08, 0x45, 0x1f, 0x37, 0x47, 0x6e, 0x10, 0xc3, 0x0f, - 0xa7, 0x85, 0x4c, 0x33, 0xd1, 0x7d, 0xb5, 0x66, 0x76, 0x83, 0x91, 0xb2, 0xc8, 0x93, 0x30, 0xdb, 0x74, 0x30, 0x42, - 0x0b, 0x92, 0x76, 0xb7, 0x0f, 0x30, 0x6c, 0x3a, 0x8a, 0xd3, 0xb6, 0x14, 0xab, 0x29, 0xfb, 0xfc, 0xa8, 0x5a, 0x0e, - 0xd6, 0x8f, 0x98, 0x5f, 0x69, 0x1f, 0x00, 0x2b, 0x48, 0xbc, 0x7c, 0xa0, 0xce, 0xbc, 0x9e, 0xd7, 0xce, 0xb7, 0x16, - 0x4a, 0x14, 0x31, 0xcf, 0x90, 0x50, 0xbc, 0xd4, 0x6e, 0x98, 0x30, 0xb7, 0x67, 0x48, 0x0c, 0xcd, 0xf2, 0x61, 0x1b, - 0x98, 0x5e, 0x05, 0xd8, 0x53, 0x73, 0x5b, 0x24, 0x61, 0xd5, 0xdc, 0x3b, 0x02, 0xd6, 0x1e, 0x84, 0xaf, 0xc4, 0x89, - 0x63, 0x4f, 0x45, 0xf3, 0x59, 0x12, 0x3e, 0x6f, 0x1c, 0x17, 0x47, 0x78, 0x22, 0x74, 0xe0, 0x0f, 0xe7, 0x39, 0xc8, - 0x03, 0xfe, 0x1a, 0x2c, 0x83, 0x50, 0x36, 0x45, 0x47, 0x0f, 0x8f, 0x80, 0x3d, 0x42, 0xbc, 0x11, 0x36, 0x37, 0xaa, - 0xd1, 0xa2, 0x24, 0xe3, 0x85, 0x0e, 0x86, 0x7b, 0x5c, 0xba, 0xf6, 0x28, 0x18, 0xe4, 0x89, 0xb1, 0x83, 0x67, 0xfe, - 0xfe, 0x10, 0xab, 0x71, 0x82, 0xc2, 0x2d, 0x69, 0xb7, 0x55, 0xe2, 0xef, 0xc0, 0x4f, 0x41, 0x82, 0x63, 0x1d, 0xf8, - 0x59, 0x1b, 0x1b, 0x89, 0x44, 0x6a, 0x37, 0xed, 0xd1, 0x49, 0x04, 0xc6, 0x83, 0x73, 0x3f, 0x85, 0x6a, 0x24, 0x11, - 0x15, 0xe5, 0x68, 0x81, 0x9a, 0xa7, 0x6a, 0x15, 0x7c, 0x47, 0x66, 0x04, 0x9e, 0x63, 0xd8, 0x9a, 0xfc, 0x54, 0xdd, - 0x58, 0xc4, 0xf2, 0x5d, 0x97, 0x8e, 0xb6, 0xf0, 0x00, 0x52, 0x30, 0x9a, 0x60, 0x18, 0x97, 0x82, 0x92, 0x15, 0xff, - 0x7d, 0x34, 0x62, 0xe5, 0x93, 0xa3, 0x6c, 0x73, 0x73, 0x20, 0xce, 0x2d, 0x88, 0x71, 0xb8, 0x11, 0x5d, 0x8d, 0x2b, - 0x00, 0xea, 0xd3, 0x39, 0x71, 0x3d, 0x30, 0xad, 0x58, 0xd3, 0xa5, 0xd8, 0x27, 0x87, 0x19, 0x80, 0x82, 0x5b, 0xce, - 0x91, 0xdf, 0xff, 0xcb, 0x00, 0xdc, 0x63, 0xff, 0x4b, 0x77, 0x4b, 0x09, 0x9a, 0x9e, 0x3c, 0x53, 0x5c, 0xd0, 0x19, - 0x6b, 0xc7, 0xa3, 0xd8, 0x68, 0x50, 0x78, 0x29, 0x60, 0x00, 0xda, 0x1c, 0x64, 0x42, 0xc5, 0x41, 0xc8, 0x51, 0x81, - 0xed, 0xe3, 0xe6, 0xe7, 0xb8, 0xb3, 0x9f, 0x82, 0x85, 0xd7, 0xd7, 0x6f, 0x8f, 0xe1, 0xed, 0x2f, 0xfa, 0xed, 0x25, - 0x0b, 0x7e, 0x2d, 0x65, 0xe8, 0xbe, 0x36, 0xc5, 0x03, 0x35, 0x45, 0x29, 0x96, 0xc8, 0xa0, 0x21, 0x73, 0xf3, 0xa5, - 0x98, 0x0d, 0x77, 0x4b, 0x54, 0x3b, 0x52, 0x74, 0xe5, 0x3e, 0x8f, 0x4e, 0x91, 0xb8, 0xae, 0x49, 0x0a, 0x23, 0x97, - 0xc0, 0x44, 0xb8, 0xe2, 0x5b, 0x62, 0xce, 0xbd, 0x36, 0xd8, 0xe0, 0xb5, 0xbc, 0x03, 0xb4, 0xef, 0xd8, 0x74, 0xc6, - 0x2f, 0x0f, 0x48, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0xb7, 0x7b, 0xf1, 0x2e, 0xef, 0xc5, 0x20, 0xd5, 0x73, - 0xc5, 0x62, 0xb8, 0x57, 0xbd, 0xb7, 0x18, 0xa5, 0x34, 0x99, 0xc9, 0xab, 0xa1, 0xd7, 0x95, 0xe8, 0x6d, 0x6e, 0x02, - 0x82, 0x3d, 0xa3, 0x2b, 0x17, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, 0x7a, 0x54, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, - 0x43, 0xc1, 0xc1, 0xdc, 0x95, 0xa3, 0xe2, 0x98, 0xd8, 0x5d, 0x26, 0xec, 0x00, 0x66, 0xc4, 0xe5, 0xad, 0x8e, 0x88, - 0x0e, 0x8b, 0xfe, 0x3a, 0xbe, 0xfd, 0xd1, 0x63, 0x9b, 0x1d, 0x17, 0x34, 0x48, 0x6d, 0xac, 0x87, 0xd5, 0x58, 0x50, - 0x1f, 0x7e, 0xd4, 0x54, 0x2a, 0x8b, 0xcd, 0xcd, 0xb2, 0x7e, 0x54, 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0xa2, 0x99, - 0x0d, 0xc2, 0x81, 0x88, 0x09, 0x14, 0x68, 0x69, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0x94, 0x4f, 0x21, 0xf3, 0xe2, - 0xb2, 0xd4, 0xa9, 0x2f, 0x32, 0x1e, 0x19, 0xe2, 0xa9, 0x27, 0x19, 0x2b, 0xa0, 0x60, 0xbd, 0xd4, 0x4b, 0x68, 0x89, - 0x00, 0xf3, 0x67, 0x2a, 0x87, 0x46, 0x58, 0x20, 0x51, 0x68, 0x98, 0x25, 0xca, 0xf8, 0x2c, 0xc2, 0x18, 0xb4, 0xfd, - 0x93, 0x5a, 0xec, 0xab, 0x50, 0x46, 0x47, 0x71, 0x94, 0x0f, 0x02, 0xaa, 0x9f, 0x4b, 0x09, 0x36, 0x09, 0xdf, 0x03, - 0x1b, 0x55, 0x8e, 0x27, 0x09, 0xc2, 0xa7, 0x71, 0xce, 0xc8, 0x53, 0x58, 0x97, 0x30, 0x4b, 0xd3, 0x36, 0x52, 0xed, - 0x22, 0x33, 0x08, 0xe5, 0xc2, 0xfc, 0x13, 0xe3, 0xec, 0x22, 0x0b, 0x97, 0x5a, 0x83, 0xf9, 0xf1, 0xc6, 0x04, 0x28, - 0xbb, 0xba, 0xca, 0x84, 0x8f, 0x1b, 0x91, 0xbd, 0xa1, 0x2b, 0x26, 0x7d, 0x85, 0x54, 0xe0, 0x44, 0x64, 0xf1, 0xd0, - 0x19, 0x0a, 0x8d, 0x70, 0x48, 0xa7, 0xc8, 0xb9, 0x6b, 0x6c, 0xfa, 0xbc, 0xaf, 0x7d, 0xa3, 0x34, 0x74, 0x12, 0x10, - 0x02, 0x02, 0x77, 0xc3, 0x9a, 0x4a, 0xfb, 0x69, 0x90, 0x50, 0x29, 0xfa, 0x39, 0x80, 0x7f, 0x18, 0x49, 0x0a, 0x80, - 0xfd, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, 0x2e, 0x00, 0xcd, 0x75, 0x80, 0x2b, 0xe1, 0x0b, 0x03, 0x15, 0xa6, 0xa7, - 0x59, 0x79, 0x29, 0x94, 0xc8, 0xbb, 0x15, 0x29, 0x6b, 0x24, 0x93, 0xcf, 0xd0, 0xe1, 0x53, 0xde, 0xf5, 0x6b, 0x89, - 0x87, 0x2e, 0x78, 0x0a, 0xcb, 0xaa, 0x9e, 0x5f, 0x85, 0x9c, 0x9c, 0x6b, 0xd0, 0x15, 0x52, 0xe8, 0x2f, 0x39, 0xc9, - 0xfb, 0xaf, 0xfc, 0xaa, 0x96, 0x1a, 0x43, 0xd9, 0xfb, 0x55, 0xcd, 0xb0, 0xbc, 0x9c, 0x55, 0x61, 0x0a, 0x02, 0x6e, - 0xc1, 0x92, 0x60, 0x21, 0x35, 0x04, 0x58, 0xd8, 0x1e, 0x69, 0xa5, 0x20, 0x2f, 0x75, 0x78, 0xe7, 0x29, 0x58, 0x01, - 0xc6, 0xa1, 0x96, 0x4a, 0xa6, 0x91, 0xc4, 0x97, 0x4a, 0x14, 0x98, 0xf2, 0x60, 0x08, 0x7e, 0x6a, 0xf3, 0xa4, 0xeb, - 0xc2, 0xf5, 0xe3, 0x29, 0xa6, 0xf6, 0x10, 0xe8, 0xb1, 0xb7, 0x06, 0xa6, 0x44, 0x5d, 0x87, 0x15, 0xc4, 0xa1, 0x59, - 0x4d, 0xb3, 0x80, 0x19, 0xd3, 0x06, 0x2d, 0xd9, 0x06, 0x5b, 0x2e, 0x07, 0xfb, 0x48, 0x6c, 0xcf, 0x6a, 0x05, 0x84, - 0xae, 0x41, 0x03, 0x43, 0xee, 0x52, 0xa1, 0x85, 0x79, 0xaf, 0x4b, 0x45, 0xb8, 0x3f, 0xfb, 0x5c, 0x5a, 0xc1, 0x99, - 0x97, 0xd1, 0xc0, 0x07, 0xf1, 0x49, 0x82, 0x89, 0x2f, 0x88, 0x15, 0xd8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x38, 0x15, - 0x17, 0x29, 0x83, 0x65, 0x45, 0xa9, 0x0d, 0x3f, 0xa4, 0xc8, 0xd6, 0x5d, 0x1e, 0xe8, 0x2e, 0xc4, 0x02, 0xd8, 0xe9, - 0x57, 0x8c, 0x7c, 0xcb, 0x7a, 0x19, 0x30, 0x38, 0xd3, 0x1a, 0x07, 0x81, 0xdf, 0xdc, 0x4c, 0x06, 0x65, 0x4a, 0x6c, - 0xd7, 0x64, 0x75, 0x01, 0x39, 0x0c, 0xd5, 0xc4, 0x1d, 0x84, 0xa5, 0xb2, 0xc7, 0x8b, 0x72, 0x8a, 0xcb, 0xa5, 0x2c, - 0xe4, 0xe6, 0x79, 0x35, 0xcd, 0xe7, 0x56, 0x9a, 0x4d, 0xc7, 0x5b, 0xf1, 0x45, 0xc1, 0xdf, 0x77, 0x62, 0x69, 0xd5, - 0x53, 0x6a, 0x85, 0x47, 0x99, 0x5b, 0xb2, 0x4e, 0x49, 0xad, 0xae, 0x1b, 0xa8, 0x46, 0x78, 0x9a, 0x86, 0x8d, 0x40, - 0x88, 0x09, 0x2e, 0x7e, 0xdd, 0x64, 0x62, 0xda, 0x5b, 0x42, 0xea, 0x08, 0xbb, 0x87, 0x72, 0x82, 0xbb, 0x9a, 0x67, - 0x9f, 0x87, 0xb3, 0xeb, 0x99, 0x7b, 0xdf, 0x60, 0xee, 0xc7, 0x21, 0x37, 0x18, 0x3d, 0x96, 0x09, 0x3f, 0x32, 0xf6, - 0x91, 0xab, 0xaa, 0x27, 0xa7, 0x61, 0x25, 0xb2, 0xc4, 0x93, 0x71, 0xd4, 0x61, 0x9c, 0x8a, 0xd6, 0x04, 0xd9, 0xd5, - 0x55, 0x61, 0xee, 0x05, 0x0a, 0x9a, 0x7a, 0xbc, 0x1e, 0xa7, 0xad, 0xd8, 0xd9, 0x88, 0x44, 0xee, 0xbf, 0xaa, 0x45, - 0x22, 0x2b, 0x3e, 0xc7, 0x91, 0xae, 0x39, 0xc8, 0x7d, 0x72, 0xba, 0xbc, 0x49, 0x85, 0x6e, 0xd1, 0x68, 0x1b, 0x7b, - 0x54, 0x1f, 0x48, 0xea, 0x19, 0x15, 0x58, 0xd5, 0xd8, 0x1b, 0x1b, 0x1d, 0x91, 0x6e, 0xa9, 0x14, 0x1b, 0x2c, 0x2d, - 0x8c, 0x66, 0x8c, 0x82, 0x41, 0x49, 0x91, 0x81, 0x1a, 0xe5, 0xd7, 0x08, 0x86, 0x7d, 0x6a, 0x00, 0x8a, 0x73, 0x75, - 0xf5, 0xe3, 0x52, 0xb2, 0x85, 0x80, 0xc4, 0x5d, 0x30, 0x10, 0x6b, 0x82, 0x99, 0x91, 0x4f, 0xde, 0x03, 0xe7, 0xf5, - 0x19, 0xfa, 0x08, 0xe0, 0x17, 0x88, 0x4d, 0x0f, 0x26, 0xb6, 0x4d, 0x44, 0xd1, 0x67, 0x03, 0xcf, 0x01, 0xd8, 0x59, - 0x15, 0x1a, 0x7d, 0x57, 0xa5, 0x80, 0x21, 0x1b, 0xb8, 0x01, 0xab, 0xc2, 0x72, 0xfb, 0xcf, 0xc1, 0x6d, 0x80, 0xd7, - 0x67, 0xb2, 0xf9, 0x3a, 0xe6, 0x09, 0x56, 0x67, 0x17, 0x7e, 0x65, 0x59, 0x8b, 0x73, 0xa7, 0xc3, 0x46, 0xbd, 0xa2, - 0x84, 0xa8, 0x3d, 0xc0, 0xda, 0x43, 0x8c, 0xb0, 0x88, 0xf7, 0x57, 0xf8, 0xae, 0xc7, 0x2d, 0xf7, 0x35, 0x5a, 0x84, - 0xe9, 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, 0xbd, 0x3c, 0x10, 0xb1, 0xe0, 0x0a, 0x47, 0x56, 0x85, 0x14, - 0x1b, 0x48, 0xd2, 0xd3, 0x3e, 0x1d, 0xb0, 0xaf, 0x37, 0x7b, 0x01, 0x65, 0xde, 0x57, 0xa4, 0x92, 0x90, 0xd2, 0xec, - 0x86, 0x48, 0x12, 0xd6, 0x8a, 0x3c, 0x75, 0x3e, 0x70, 0xb4, 0xcf, 0xad, 0x24, 0x82, 0x11, 0x9c, 0x84, 0xe9, 0x58, - 0x79, 0xd8, 0x14, 0xe0, 0x2a, 0x3a, 0x62, 0xfa, 0x26, 0x20, 0xbf, 0x19, 0xc8, 0xed, 0xa5, 0xe4, 0xda, 0x5c, 0xc3, - 0xf0, 0x04, 0x09, 0x56, 0x45, 0x22, 0xf0, 0x88, 0x1a, 0x70, 0xcc, 0x57, 0x79, 0x1e, 0x60, 0xc2, 0xd7, 0xf6, 0x26, - 0x00, 0x94, 0x93, 0xab, 0xe2, 0x2c, 0x05, 0xba, 0x01, 0xcb, 0xd5, 0x71, 0x6a, 0x54, 0x24, 0x2e, 0x6e, 0x4c, 0x57, - 0xb7, 0xf4, 0xa7, 0x68, 0x39, 0x93, 0x21, 0xa6, 0x83, 0x20, 0x20, 0x53, 0xdf, 0x31, 0x47, 0xc8, 0x5c, 0x61, 0x7d, - 0xce, 0x9c, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x49, 0x2d, 0x5e, 0xa7, 0x4d, 0x29, 0x11, 0x93, 0x12, 0x73, 0x5d, - 0xa4, 0x62, 0x33, 0x25, 0xee, 0xdc, 0xfa, 0x46, 0x0b, 0x69, 0xa3, 0xad, 0x8b, 0x1c, 0x6c, 0x56, 0xc9, 0x7b, 0x02, - 0xe3, 0xb9, 0x20, 0x7c, 0xf9, 0x8a, 0x92, 0x74, 0x98, 0x63, 0x22, 0x58, 0xbd, 0x98, 0x8a, 0xfc, 0x9d, 0xa3, 0xd3, - 0xec, 0x0d, 0x7a, 0x90, 0x7a, 0x03, 0x89, 0x59, 0x13, 0xdf, 0x85, 0x34, 0xd4, 0x11, 0x02, 0x95, 0x51, 0x2d, 0xd3, - 0x71, 0x62, 0x15, 0xbe, 0x11, 0x7c, 0xf5, 0x56, 0x1f, 0xe7, 0x1b, 0xcf, 0x8d, 0xd5, 0x08, 0x62, 0xf0, 0x16, 0xf2, - 0x81, 0x27, 0x45, 0x38, 0x10, 0x2e, 0xdf, 0xdc, 0xec, 0xe5, 0xbb, 0xbc, 0x0a, 0x91, 0x54, 0x30, 0xc6, 0x98, 0x51, - 0x8c, 0x7b, 0xa2, 0xa6, 0x16, 0x73, 0x18, 0x58, 0xb6, 0x0e, 0x73, 0x3c, 0x00, 0x80, 0x96, 0xa6, 0xf4, 0xaa, 0xa9, - 0x50, 0x79, 0x9e, 0x4b, 0xf8, 0x54, 0x87, 0xa8, 0xaa, 0xf1, 0xdb, 0xd5, 0x19, 0x28, 0x04, 0xf7, 0x5a, 0xc7, 0xc3, - 0x43, 0x08, 0x58, 0x45, 0x21, 0x0b, 0xf4, 0x06, 0xed, 0x55, 0x89, 0x50, 0xcc, 0x9c, 0xac, 0xc7, 0x0c, 0x27, 0x15, - 0x6c, 0xa1, 0x12, 0x96, 0x4a, 0x0b, 0xfc, 0x6a, 0x23, 0x34, 0x4f, 0x19, 0xf7, 0x5f, 0x55, 0x38, 0x83, 0xfe, 0x60, - 0xde, 0x32, 0xa3, 0xbe, 0x5d, 0x3a, 0x91, 0xa9, 0xc0, 0xc4, 0xcd, 0x2c, 0xb5, 0xdf, 0xaf, 0xab, 0xb4, 0x9f, 0x57, - 0xc8, 0x7d, 0x4e, 0x9a, 0xaf, 0x73, 0x07, 0xcd, 0x27, 0xc3, 0xfd, 0x4a, 0xf9, 0xa1, 0x85, 0x51, 0x53, 0x7e, 0x79, - 0x5d, 0xf9, 0x15, 0x9e, 0x0a, 0x6f, 0xed, 0x75, 0x51, 0xe8, 0xa2, 0x3e, 0x07, 0x43, 0x48, 0x3f, 0x82, 0x6b, 0x68, - 0xf0, 0xa0, 0x48, 0x16, 0x8b, 0xb5, 0x0b, 0xe2, 0xfa, 0x98, 0x53, 0xed, 0x50, 0xc6, 0x18, 0xf1, 0xb4, 0xe4, 0x20, - 0xc9, 0xe0, 0x60, 0xfc, 0x06, 0x06, 0xc4, 0xa4, 0x24, 0xa4, 0x43, 0xe8, 0xac, 0xcc, 0x44, 0x54, 0xee, 0xe2, 0xed, - 0xc6, 0x65, 0x4d, 0xa1, 0x08, 0x3b, 0xc1, 0x4c, 0xa5, 0x54, 0x10, 0x48, 0x93, 0x6f, 0xad, 0x53, 0x0b, 0x86, 0x16, - 0xae, 0xa9, 0x80, 0xbc, 0xb6, 0xeb, 0x41, 0x93, 0xf7, 0x14, 0x43, 0x5f, 0xa5, 0x46, 0xbc, 0xcc, 0xe0, 0x6b, 0xd8, - 0xfc, 0x35, 0x51, 0x92, 0x87, 0x4c, 0xc4, 0x5e, 0xc1, 0x27, 0x42, 0x36, 0x05, 0x3b, 0x13, 0xe8, 0x87, 0x76, 0x65, - 0x2f, 0xdd, 0x2d, 0x2a, 0x97, 0x16, 0x8d, 0xad, 0x44, 0xcd, 0x9a, 0x1f, 0xc5, 0x9b, 0x29, 0xec, 0x67, 0x8f, 0x12, - 0x08, 0x48, 0x53, 0x39, 0x49, 0x35, 0xef, 0x51, 0x3a, 0x00, 0x90, 0x60, 0xf7, 0x13, 0x58, 0xe8, 0x37, 0x25, 0x26, - 0x58, 0x54, 0x8d, 0xdd, 0x66, 0xa0, 0x35, 0x67, 0xa4, 0xf9, 0x66, 0xa8, 0xb5, 0x37, 0x95, 0xf5, 0x8c, 0xd9, 0x01, - 0xb6, 0xed, 0x6e, 0x16, 0x47, 0xe9, 0x66, 0x67, 0x60, 0x08, 0x2e, 0x3c, 0xfe, 0x4f, 0x4a, 0x4c, 0x03, 0xc9, 0xa5, - 0x6e, 0xfc, 0x84, 0x3a, 0x0c, 0xff, 0x5b, 0x90, 0x02, 0xee, 0xd7, 0x56, 0x63, 0xc9, 0xb9, 0x57, 0x1c, 0x25, 0x97, - 0x55, 0xb5, 0xab, 0x25, 0x68, 0xe8, 0x46, 0x32, 0x26, 0x8a, 0x79, 0x4e, 0x00, 0x8c, 0x62, 0xf3, 0xa7, 0x4c, 0x27, - 0x79, 0xff, 0xba, 0x32, 0xb5, 0xdb, 0xf7, 0xfd, 0x28, 0x3f, 0xa5, 0x23, 0x15, 0x95, 0xcd, 0x49, 0xcc, 0xbf, 0x2d, - 0xc0, 0x34, 0x27, 0x3e, 0xd2, 0x73, 0x0d, 0x42, 0x01, 0xbe, 0xb2, 0xa1, 0xd4, 0x6c, 0x8f, 0xff, 0xe8, 0x6c, 0xf7, - 0x25, 0x51, 0x04, 0x0b, 0x34, 0xe8, 0x72, 0x0d, 0xbe, 0x80, 0x65, 0x70, 0x47, 0xfa, 0x29, 0xf8, 0x5e, 0x5e, 0x07, - 0x9f, 0xb1, 0xff, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x4e, 0xd3, 0xb0, 0x12, 0xe2, 0x12, 0x15, 0x66, 0x15, 0xe7, - 0x8f, 0xeb, 0xbc, 0x6e, 0x5a, 0x96, 0x18, 0x94, 0x9f, 0xb9, 0x86, 0x1b, 0xdf, 0x6b, 0xe4, 0x8f, 0xef, 0x3f, 0x07, - 0xdd, 0x4e, 0xa4, 0xdd, 0xd8, 0xc8, 0xd7, 0xc8, 0x42, 0xc3, 0x7b, 0x61, 0x33, 0x68, 0x8b, 0x74, 0xc9, 0xd5, 0x33, - 0x16, 0xe3, 0x6d, 0x11, 0x2a, 0xc3, 0x07, 0x2c, 0x98, 0x01, 0x86, 0xe0, 0xb1, 0x53, 0x99, 0x7c, 0x86, 0x8d, 0xa6, - 0xd8, 0x35, 0x17, 0x06, 0x1f, 0xa8, 0xca, 0x42, 0xf2, 0x62, 0x9d, 0x6c, 0xcf, 0xce, 0xe0, 0xf9, 0x65, 0x5c, 0x00, - 0x75, 0x00, 0xfd, 0x8a, 0xca, 0x62, 0x03, 0xb9, 0xb8, 0x29, 0x6b, 0xbd, 0xa2, 0xd1, 0xe8, 0xc6, 0x2e, 0xac, 0xae, - 0xc0, 0x27, 0x51, 0x3a, 0x4a, 0xc4, 0x24, 0x66, 0x52, 0xe5, 0x8a, 0x5c, 0x1b, 0xdd, 0x4b, 0x5b, 0x34, 0xcf, 0x85, - 0x04, 0xaf, 0x08, 0xdc, 0x10, 0xfa, 0x4a, 0x5f, 0xae, 0x36, 0x50, 0xf0, 0xa8, 0xbd, 0xb9, 0x08, 0x26, 0x26, 0x1e, - 0x33, 0xa4, 0xa6, 0x5f, 0x87, 0x53, 0x2b, 0x8b, 0x25, 0x87, 0x5f, 0xe7, 0x8c, 0x35, 0x14, 0x00, 0xf1, 0xc9, 0x83, - 0xeb, 0xdd, 0xa4, 0x57, 0x4a, 0x3b, 0x28, 0x8d, 0x10, 0xdf, 0x55, 0xf8, 0xba, 0x0b, 0xc5, 0x57, 0xae, 0xba, 0xf7, - 0x35, 0x65, 0xc6, 0x05, 0xa3, 0xe7, 0x7c, 0x9a, 0x34, 0xae, 0xdd, 0xd0, 0x5d, 0x9d, 0x9f, 0xbc, 0x1f, 0x65, 0xde, - 0xc2, 0x14, 0xd8, 0x04, 0x54, 0xc1, 0x73, 0xef, 0xb5, 0x71, 0xa2, 0xfc, 0x9d, 0x79, 0xc4, 0x2b, 0x87, 0x59, 0x75, - 0x92, 0xfc, 0x5d, 0xff, 0xbb, 0xe0, 0xfa, 0x96, 0xc6, 0x09, 0x72, 0x57, 0x9d, 0x20, 0x13, 0xe5, 0xa6, 0x7a, 0xc3, - 0xed, 0xdf, 0x55, 0x20, 0x88, 0x53, 0x31, 0x7d, 0x54, 0x8e, 0xeb, 0x47, 0x0b, 0x54, 0x2a, 0x22, 0x3e, 0x55, 0xb9, - 0x2b, 0xd7, 0xa6, 0x86, 0x7a, 0x5c, 0x27, 0xb3, 0xd0, 0x34, 0x2b, 0x72, 0x29, 0x9b, 0x1e, 0x23, 0xd3, 0xec, 0x54, - 0x9b, 0xdf, 0xbd, 0xf6, 0x90, 0x8e, 0xa1, 0xb9, 0x58, 0xab, 0x05, 0xf7, 0xbb, 0x8a, 0xc2, 0xbb, 0x5e, 0x6c, 0xa4, - 0x32, 0xd4, 0xac, 0x47, 0xd1, 0xc7, 0x71, 0x9b, 0xb9, 0x3c, 0xca, 0xfe, 0xac, 0x01, 0x60, 0x3a, 0xc2, 0xa2, 0xbb, - 0xe9, 0x19, 0x7b, 0x02, 0x3d, 0x3d, 0x91, 0x41, 0xa2, 0xd7, 0x3a, 0x5f, 0xb5, 0x4a, 0x2c, 0x5d, 0x41, 0x60, 0xf7, - 0x86, 0x8c, 0x55, 0x49, 0xbb, 0xe5, 0xfa, 0xe5, 0x3c, 0x9f, 0xa7, 0x7c, 0x29, 0xcf, 0xa7, 0x66, 0xd1, 0xad, 0xb5, - 0xdd, 0x9b, 0x53, 0x43, 0xc5, 0x5c, 0xab, 0x9b, 0xfc, 0x81, 0xe9, 0x3a, 0x18, 0x6a, 0x11, 0x64, 0x56, 0xbb, 0xea, - 0x59, 0x59, 0x4e, 0xeb, 0x99, 0x1c, 0x53, 0xe1, 0x9b, 0x4a, 0x77, 0x88, 0x6e, 0x98, 0xaa, 0x99, 0x7e, 0x6a, 0x6c, - 0x0b, 0xd9, 0xe6, 0xf9, 0xe5, 0x28, 0x07, 0x4a, 0xcb, 0xfd, 0x65, 0xc2, 0xf0, 0xd3, 0xd5, 0xd5, 0x4f, 0x42, 0x4e, - 0x55, 0x1d, 0xbd, 0xc5, 0x4b, 0xdd, 0x33, 0x98, 0x96, 0xca, 0x89, 0x38, 0x61, 0xab, 0x07, 0x6f, 0xee, 0x5e, 0x01, - 0xcb, 0x09, 0x60, 0x77, 0xc2, 0x9c, 0xc6, 0x50, 0xd5, 0x06, 0xfe, 0x71, 0xf5, 0x60, 0xab, 0xf6, 0xf0, 0x8f, 0xfd, - 0x1f, 0x83, 0x1b, 0x1b, 0x1b, 0xdb, 0x78, 0xbb, 0x96, 0x08, 0xf2, 0x0a, 0x0f, 0xf4, 0xf1, 0xea, 0xa3, 0xa0, 0xe5, - 0x2a, 0xb1, 0xdd, 0x77, 0x28, 0x6c, 0x0d, 0xf2, 0x4d, 0xca, 0xa4, 0xe1, 0xbc, 0xe0, 0xd9, 0x54, 0xce, 0x50, 0xc8, - 0x6b, 0x3e, 0x0e, 0xda, 0x8e, 0xf0, 0x37, 0x70, 0x6a, 0xc7, 0xcb, 0x8b, 0x4f, 0xd0, 0x07, 0x3c, 0x5d, 0x29, 0x4d, - 0x45, 0x9c, 0x52, 0x6e, 0xd1, 0xe5, 0x3a, 0x0f, 0x46, 0x8a, 0x8b, 0x09, 0x2a, 0x1d, 0x77, 0x71, 0xe3, 0x6c, 0xe4, - 0xf4, 0x97, 0x78, 0x75, 0x91, 0x2e, 0x1f, 0x89, 0x6c, 0xd5, 0xd2, 0xfb, 0x5d, 0x9f, 0x6e, 0xdb, 0x53, 0xc6, 0x27, - 0xd9, 0x88, 0x0e, 0x66, 0x7c, 0x9c, 0x08, 0xaf, 0x4f, 0x8c, 0xf4, 0xdd, 0x22, 0x30, 0xdd, 0x1c, 0x9b, 0xfc, 0x70, - 0xbc, 0xde, 0x6c, 0xd6, 0xb8, 0xfd, 0x37, 0xce, 0x13, 0x67, 0x51, 0x62, 0x44, 0x65, 0xa1, 0xe1, 0x01, 0xad, 0x10, - 0x37, 0xef, 0x99, 0xc0, 0xb8, 0xec, 0x92, 0xa4, 0xb6, 0x1b, 0x08, 0x5c, 0xec, 0x71, 0xcc, 0x92, 0x91, 0xed, 0x41, - 0x79, 0xa0, 0x2f, 0x46, 0xd3, 0x2d, 0x60, 0x5a, 0x5e, 0x3b, 0x3b, 0x4f, 0x6d, 0xaf, 0x9a, 0x2a, 0x80, 0x59, 0xb2, - 0x3c, 0x3e, 0x45, 0xd6, 0xfd, 0x16, 0xba, 0x88, 0x01, 0x63, 0xe3, 0xca, 0x9c, 0xbb, 0x58, 0xb5, 0x22, 0xbe, 0xd1, - 0x44, 0x9a, 0xd4, 0x47, 0xd4, 0x77, 0x10, 0xd6, 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, - 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, 0xe2, 0x9a, 0x61, 0xaf, 0xb8, 0x0f, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, - 0x6f, 0xe7, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, 0x68, 0x6e, 0xb8, 0xc1, 0xef, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, - 0x92, 0xdf, 0x8d, 0x7b, 0x55, 0x63, 0x47, 0xae, 0x02, 0x88, 0xbe, 0x05, 0x5d, 0x92, 0xc3, 0x2b, 0x59, 0xae, 0x3a, - 0x43, 0xfe, 0x0d, 0xd6, 0x59, 0x2f, 0x4e, 0xc0, 0x4c, 0x9a, 0xf2, 0x12, 0x13, 0x53, 0xc4, 0xe5, 0x66, 0x19, 0xf3, - 0x34, 0x7d, 0x16, 0xed, 0xe0, 0xe4, 0x46, 0x02, 0x47, 0xec, 0x1b, 0xcb, 0xd0, 0x4c, 0xd8, 0x88, 0x89, 0x34, 0x2a, - 0xa5, 0x84, 0xf7, 0xe5, 0x52, 0x4b, 0xfe, 0x32, 0x97, 0x57, 0x5f, 0x6e, 0x13, 0x1c, 0x90, 0xd7, 0xc0, 0x72, 0x68, - 0x1c, 0xb7, 0x0c, 0x24, 0x62, 0x31, 0x20, 0x46, 0xad, 0xca, 0xe5, 0x64, 0x54, 0x27, 0xf3, 0x15, 0x72, 0xa1, 0x22, - 0x0f, 0x6e, 0x09, 0x94, 0xfc, 0x39, 0xa6, 0x0e, 0x66, 0xa5, 0x76, 0xd3, 0x62, 0x93, 0xe4, 0x3d, 0x33, 0x20, 0xb9, - 0xfa, 0x1a, 0x1e, 0x1a, 0xbf, 0x78, 0x65, 0x4e, 0x09, 0x5f, 0x94, 0xb1, 0xb4, 0x34, 0xe6, 0xd2, 0xbf, 0x90, 0xf7, - 0x69, 0x25, 0x60, 0xbf, 0x81, 0x98, 0x32, 0x70, 0x89, 0x8d, 0x0b, 0x92, 0xf2, 0x5a, 0x9e, 0xb2, 0xfb, 0x1a, 0xca, - 0x77, 0xc9, 0xa4, 0xab, 0x54, 0xd6, 0x35, 0x56, 0xdd, 0x6f, 0x73, 0x96, 0x5f, 0x1e, 0x30, 0xcc, 0x4d, 0x46, 0x83, - 0x6c, 0xc9, 0xcc, 0xa6, 0xfc, 0x6a, 0xef, 0xc6, 0xaf, 0x3c, 0x94, 0x74, 0xa8, 0x56, 0xe9, 0xe6, 0xa5, 0x1b, 0x8e, - 0x71, 0xe3, 0x86, 0x23, 0x80, 0x8d, 0x61, 0xa7, 0x8a, 0xd4, 0x3a, 0xff, 0x7d, 0x39, 0xfc, 0x44, 0x7b, 0x6d, 0xa0, - 0x77, 0xdd, 0x60, 0x65, 0x7a, 0xfa, 0x0d, 0xa8, 0x1a, 0x59, 0x42, 0x37, 0xa1, 0x8a, 0xc9, 0x48, 0x94, 0x98, 0xae, - 0x52, 0x1e, 0xf5, 0x35, 0xe2, 0x1c, 0xc4, 0x0d, 0xe5, 0x2f, 0xfe, 0x35, 0xbc, 0x3c, 0x0e, 0xd0, 0x88, 0x5a, 0x8c, - 0xb3, 0x94, 0xb7, 0xc6, 0xd1, 0x34, 0x4e, 0x2e, 0x83, 0x79, 0xdc, 0x9a, 0x66, 0x69, 0x56, 0xcc, 0x80, 0x2b, 0xbd, - 0xe2, 0x12, 0x6c, 0xf8, 0x69, 0x6b, 0x1e, 0x7b, 0xcf, 0x59, 0x72, 0xc6, 0x78, 0x3c, 0x8c, 0x3c, 0x7b, 0x3f, 0x07, - 0xf1, 0x60, 0xbd, 0x8e, 0xf2, 0x3c, 0x3b, 0xb7, 0xbd, 0x77, 0xd9, 0x09, 0x30, 0xad, 0xf7, 0xe6, 0xe2, 0xf2, 0x94, - 0xa5, 0xde, 0xfb, 0x93, 0x79, 0xca, 0xe7, 0x5e, 0x11, 0xa5, 0x45, 0xab, 0x60, 0x79, 0x3c, 0x06, 0x35, 0x91, 0x64, - 0x79, 0x0b, 0xf3, 0x9f, 0xa7, 0x2c, 0x48, 0xe2, 0xd3, 0x09, 0xb7, 0x46, 0x51, 0xfe, 0xa9, 0xd7, 0x6a, 0xcd, 0xf2, - 0x78, 0x1a, 0xe5, 0x97, 0x2d, 0x6a, 0x11, 0x7c, 0xd1, 0xde, 0x8e, 0xbe, 0x1a, 0xdf, 0xef, 0xf1, 0x1c, 0xfa, 0xc6, - 0x48, 0xc5, 0x00, 0x84, 0x8f, 0xb5, 0xbd, 0xd3, 0x9e, 0x16, 0x6b, 0xe2, 0x44, 0x29, 0x4a, 0x79, 0x79, 0xec, 0x7d, - 0x64, 0x00, 0xb7, 0x7f, 0xc2, 0x53, 0x0f, 0x7c, 0x39, 0x9e, 0xa5, 0x8b, 0xe1, 0x3c, 0x2f, 0x60, 0x80, 0x59, 0x16, - 0xa7, 0x9c, 0xe5, 0xbd, 0x93, 0x2c, 0x07, 0xb2, 0xb5, 0xf2, 0x68, 0x14, 0xcf, 0x8b, 0xe0, 0xfe, 0xec, 0xa2, 0x87, - 0xb6, 0xc2, 0x69, 0x9e, 0xcd, 0xd3, 0x91, 0x9c, 0x2b, 0x4e, 0x61, 0x63, 0xc4, 0xdc, 0xac, 0xa0, 0x2f, 0xa1, 0x00, - 0x7c, 0x29, 0x8b, 0xf2, 0xd6, 0x29, 0x76, 0x46, 0x43, 0xbf, 0x3d, 0x62, 0xa7, 0x5e, 0x7e, 0x7a, 0x12, 0x39, 0x9d, - 0xee, 0x43, 0x4f, 0xfd, 0xf3, 0x77, 0x5c, 0x30, 0xdc, 0x57, 0x16, 0x77, 0xda, 0xed, 0x7f, 0x70, 0x7b, 0x8d, 0x59, - 0x08, 0xa0, 0xa0, 0x33, 0xbb, 0xb0, 0x8a, 0x2c, 0x81, 0xf5, 0x59, 0xd5, 0xb3, 0x37, 0x03, 0xbf, 0x29, 0x4e, 0x4f, - 0x83, 0xee, 0xec, 0xa2, 0x44, 0xec, 0x02, 0x91, 0x90, 0x29, 0x91, 0x94, 0x6f, 0x8b, 0x3f, 0x0a, 0xf1, 0xa3, 0xd5, - 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd5, 0x5b, 0x23, 0xd8, 0x07, 0x44, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x80, 0x13, 0x98, - 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d, 0x0d, 0xc6, 0xa3, 0xdb, 0xc0, 0xc8, 0xd3, 0xd1, 0xa2, 0xbe, 0xae, - 0x1d, 0x70, 0x4e, 0x7b, 0x13, 0x86, 0xfc, 0x14, 0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf1, 0x89, 0x78, 0x24, 0x76, 0x3e, - 0x17, 0x75, 0x3b, 0xed, 0xb6, 0x78, 0x2f, 0x40, 0xa1, 0x05, 0x1d, 0x1f, 0x1b, 0x00, 0x13, 0x7d, 0xb8, 0xee, 0x23, - 0x36, 0x5f, 0xdf, 0xfa, 0xa5, 0x1a, 0xef, 0x5c, 0xe5, 0x0d, 0x0a, 0x15, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0xf9, - 0x5e, 0x47, 0x1f, 0x54, 0xf5, 0xbf, 0x66, 0xa4, 0xf5, 0x02, 0xee, 0x99, 0xb9, 0x40, 0xbd, 0x74, 0x0f, 0x43, 0x52, - 0xad, 0x96, 0x0b, 0x7a, 0x83, 0x61, 0x08, 0x89, 0x0e, 0x04, 0x9d, 0xbc, 0x5f, 0xd0, 0x37, 0x35, 0x32, 0x37, 0x28, - 0x9c, 0xcc, 0x85, 0x2d, 0x9f, 0x69, 0xb9, 0x0e, 0x4a, 0x1a, 0xbc, 0xec, 0x0f, 0x4c, 0x36, 0x00, 0xe9, 0x4d, 0xa1, - 0xae, 0x1f, 0x42, 0xe1, 0x4a, 0x29, 0x47, 0x6a, 0x7a, 0xd3, 0x15, 0x7d, 0x58, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x06, - 0xff, 0x0a, 0xc3, 0xde, 0xd1, 0x8e, 0x65, 0x91, 0xcd, 0xf3, 0x21, 0x45, 0xea, 0x96, 0x3d, 0x7e, 0x9b, 0x14, 0xae, - 0xed, 0x31, 0x2d, 0xe7, 0xd1, 0x0d, 0xae, 0x7d, 0xe4, 0x80, 0xd3, 0x21, 0x88, 0xb8, 0x63, 0x20, 0xa3, 0x1c, 0x0a, - 0x42, 0x54, 0x5d, 0x23, 0xca, 0x77, 0xa3, 0xfb, 0x97, 0xfe, 0x3c, 0x8d, 0x41, 0xd2, 0x7d, 0x8c, 0x47, 0x74, 0xef, - 0x24, 0x1e, 0xd1, 0x41, 0x44, 0x8b, 0x12, 0x8f, 0x30, 0xb2, 0x0d, 0x05, 0xea, 0x3b, 0x2c, 0xf0, 0x2c, 0x13, 0x59, - 0xec, 0x96, 0x8d, 0x87, 0x09, 0x86, 0xaa, 0x1c, 0x65, 0xd3, 0x28, 0x4e, 0x03, 0xfc, 0x3e, 0x88, 0xa7, 0x47, 0x0c, - 0xb0, 0x8b, 0x07, 0x3f, 0x99, 0xcc, 0x45, 0xeb, 0xb8, 0xfe, 0xaf, 0x20, 0x47, 0xa8, 0x7f, 0x29, 0xfd, 0x30, 0x0d, - 0x97, 0x3a, 0xe6, 0xad, 0x97, 0x82, 0xec, 0xe1, 0xca, 0x66, 0x65, 0x14, 0xe7, 0xd8, 0xe5, 0xf4, 0xa3, 0xdf, 0xea, - 0x04, 0x1d, 0xed, 0xba, 0xd6, 0x6e, 0xa3, 0x8a, 0x5c, 0x16, 0x79, 0xa3, 0x91, 0x60, 0xd0, 0xcf, 0x02, 0xce, 0xea, - 0x5d, 0xc3, 0xea, 0x49, 0xbe, 0xc4, 0x00, 0xce, 0x69, 0xea, 0xd4, 0x80, 0xa0, 0xb3, 0x80, 0x6b, 0xa6, 0x72, 0xcb, - 0x88, 0x94, 0xd2, 0x63, 0xda, 0xc0, 0xf5, 0xbb, 0x44, 0x78, 0x6f, 0xa8, 0x9e, 0x02, 0xa5, 0x58, 0x6e, 0x74, 0xbc, - 0x2b, 0x76, 0xbc, 0x45, 0x3c, 0x16, 0xda, 0xb0, 0x05, 0x6d, 0xeb, 0x2f, 0x43, 0xa0, 0xd2, 0xa7, 0xd0, 0x5e, 0x5f, - 0x38, 0x2a, 0xb1, 0x3e, 0x87, 0xb9, 0xf6, 0x85, 0xd6, 0xa3, 0x1b, 0xf9, 0xf6, 0xde, 0xfa, 0x82, 0x97, 0xbb, 0x5b, - 0xa2, 0xf7, 0xde, 0x71, 0x59, 0x90, 0x82, 0x32, 0x03, 0x69, 0xd5, 0x14, 0xa2, 0x0e, 0x86, 0xa5, 0xf4, 0x5d, 0x1c, - 0x37, 0xd7, 0x46, 0x97, 0x88, 0x18, 0x4b, 0xb6, 0x2b, 0x30, 0x5d, 0x29, 0xca, 0x61, 0x4f, 0xea, 0x84, 0x94, 0x42, - 0xe4, 0x60, 0xf4, 0x96, 0xa1, 0x38, 0x46, 0x08, 0xfa, 0xeb, 0x8b, 0xb8, 0x0c, 0xd7, 0x17, 0x59, 0x79, 0x0c, 0x96, - 0x09, 0x42, 0x95, 0xab, 0xcf, 0xbb, 0xc0, 0xc4, 0x22, 0xc8, 0x62, 0xd1, 0x08, 0x38, 0x2d, 0x2b, 0x6d, 0xab, 0x2f, - 0xa0, 0x01, 0x0f, 0x10, 0x0b, 0xc0, 0x76, 0xa3, 0x5e, 0x0c, 0x70, 0x11, 0xad, 0xf7, 0x60, 0xa0, 0xdd, 0x2d, 0xd1, - 0x08, 0xf0, 0xca, 0x11, 0xe4, 0x0a, 0x2d, 0x4c, 0xc7, 0x31, 0x51, 0x1b, 0xc5, 0x67, 0x9a, 0x74, 0x94, 0x9b, 0xbc, - 0xb7, 0x9b, 0x44, 0x27, 0x2c, 0x81, 0x21, 0x8b, 0xab, 0xab, 0x36, 0x8c, 0x24, 0x5e, 0xad, 0xdd, 0x38, 0x9d, 0xcd, - 0xe5, 0x67, 0xb6, 0x60, 0xe2, 0x0e, 0x9e, 0x7c, 0xe2, 0x2d, 0x80, 0xbe, 0x3a, 0xca, 0x0b, 0xe4, 0x00, 0x00, 0x22, - 0x9d, 0x22, 0x20, 0x74, 0x15, 0x5b, 0x40, 0x69, 0x3c, 0x5a, 0x2e, 0xd3, 0x98, 0xe5, 0xf0, 0x02, 0xdb, 0x72, 0x86, - 0xcf, 0x29, 0x3c, 0x4f, 0xe3, 0x14, 0x1f, 0x0b, 0x7c, 0x8c, 0x2e, 0xf0, 0x31, 0x2b, 0xed, 0xbf, 0xcb, 0x0a, 0x58, - 0x9a, 0x00, 0xd9, 0xd5, 0x15, 0xc8, 0x7b, 0x4d, 0x82, 0xdd, 0x2d, 0x20, 0x16, 0x72, 0x8a, 0xf8, 0xe8, 0x0a, 0x33, - 0xc9, 0xc8, 0x8a, 0x59, 0x4b, 0x94, 0x5b, 0xa4, 0x55, 0x43, 0x70, 0xba, 0x72, 0xa7, 0x61, 0x3c, 0x78, 0x32, 0xbd, - 0xe4, 0x09, 0xbe, 0xb8, 0xb6, 0x25, 0xbe, 0x8a, 0x21, 0x88, 0x42, 0x8f, 0x88, 0xa1, 0x2e, 0xe3, 0xf2, 0x7b, 0x37, - 0x71, 0x68, 0xe3, 0x2c, 0x60, 0xbf, 0xa1, 0x16, 0xe0, 0x51, 0x9c, 0x88, 0xc6, 0x2b, 0xf0, 0x69, 0xe4, 0x09, 0x12, - 0x3a, 0xbb, 0x5b, 0x15, 0x6c, 0x00, 0xfc, 0x50, 0xdc, 0xb2, 0x76, 0x44, 0x0e, 0xa4, 0x2d, 0xca, 0xe9, 0xec, 0x5c, - 0x6e, 0x69, 0x19, 0xd9, 0x15, 0xb1, 0x72, 0x8d, 0x2a, 0xe5, 0x2c, 0xda, 0xe3, 0x28, 0x5d, 0xd5, 0x14, 0xa0, 0x9f, - 0x31, 0x36, 0xf2, 0x6c, 0x0b, 0x64, 0xa9, 0x78, 0xfe, 0x98, 0xb0, 0x33, 0x26, 0xbf, 0x94, 0xa2, 0x07, 0xd1, 0x95, - 0x43, 0x50, 0xc9, 0x5c, 0x5e, 0xe2, 0x94, 0xec, 0xa9, 0x70, 0x94, 0x94, 0xa8, 0x23, 0xe2, 0xd9, 0xc6, 0xa0, 0xcd, - 0x39, 0xda, 0xf5, 0x61, 0xbd, 0x0e, 0x58, 0xbb, 0xb6, 0x80, 0x97, 0xec, 0xb8, 0xdb, 0x91, 0x83, 0x01, 0xd8, 0x78, - 0x0c, 0xdb, 0x45, 0x45, 0x96, 0xb5, 0x2c, 0x10, 0x50, 0x81, 0x53, 0xea, 0xd9, 0xa2, 0x85, 0x5d, 0xb5, 0xd5, 0x4f, - 0x92, 0x38, 0x49, 0x36, 0xfc, 0xb4, 0x62, 0x2e, 0xe0, 0x8e, 0x75, 0x11, 0x69, 0x6d, 0xc8, 0x37, 0xfb, 0x5f, 0xfe, - 0xf9, 0xbf, 0xff, 0x67, 0x18, 0x98, 0xfa, 0xb9, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x06, 0xad, 0xe6, 0xe9, 0x2d, 0xed, - 0xfe, 0xf6, 0x1f, 0xfe, 0x0b, 0x34, 0xa3, 0x1b, 0x39, 0x6e, 0x79, 0x4c, 0x10, 0x0d, 0xd1, 0x08, 0xfa, 0x2c, 0x90, - 0x6a, 0x83, 0x5c, 0x3b, 0xd3, 0x3f, 0x21, 0xd8, 0x05, 0xcf, 0x66, 0x37, 0x82, 0x83, 0x50, 0x0f, 0x93, 0xac, 0x60, - 0x1a, 0x1e, 0x21, 0x6b, 0x3f, 0x0f, 0x20, 0x9a, 0x6b, 0x06, 0x5c, 0x5e, 0x58, 0x7a, 0x1c, 0xb1, 0x3c, 0xab, 0xc6, - 0x69, 0xac, 0x5e, 0xc1, 0x38, 0xa1, 0x43, 0x71, 0x05, 0x58, 0x2f, 0xf1, 0x04, 0x0f, 0x24, 0x10, 0xdc, 0xfa, 0x57, - 0xbe, 0x56, 0x0f, 0xa6, 0xf9, 0x53, 0x8c, 0x25, 0x42, 0x29, 0x6a, 0x04, 0xf8, 0x09, 0x72, 0xea, 0xa3, 0x7e, 0x8e, - 0x2e, 0xf4, 0x33, 0xca, 0x32, 0x31, 0x01, 0xe8, 0xa2, 0x29, 0x9a, 0x19, 0xe6, 0x0c, 0x22, 0x0d, 0xa0, 0xf2, 0x23, - 0x8d, 0x6c, 0x12, 0x21, 0xbc, 0x3e, 0x62, 0xd2, 0x25, 0x5e, 0xb1, 0x99, 0xe7, 0xec, 0x63, 0x92, 0x9d, 0x63, 0x70, - 0x0a, 0x89, 0x74, 0x5d, 0x7d, 0x69, 0xf9, 0xbe, 0x75, 0x4d, 0xf5, 0x04, 0x76, 0x01, 0xd8, 0x92, 0x3c, 0xd4, 0x54, - 0x6e, 0x20, 0xb5, 0x7a, 0x48, 0xc4, 0x72, 0x75, 0x8d, 0x44, 0x1b, 0x4b, 0x25, 0xd6, 0xf2, 0x11, 0x91, 0xf6, 0xda, - 0x15, 0x53, 0xdc, 0x3e, 0x0f, 0xc2, 0x76, 0xcd, 0x44, 0x58, 0x75, 0xeb, 0x4c, 0xc7, 0x6e, 0x70, 0x03, 0x65, 0x3e, - 0x13, 0xeb, 0xd5, 0x80, 0xdc, 0x01, 0x06, 0x0f, 0x34, 0x80, 0xd0, 0x11, 0x4f, 0x84, 0x84, 0x0f, 0x68, 0x2d, 0xa4, - 0xb8, 0xaf, 0x41, 0xe4, 0x59, 0xff, 0xe7, 0x7f, 0x1e, 0xd2, 0x1c, 0xd8, 0x26, 0xae, 0xf4, 0x2b, 0x99, 0x23, 0x27, - 0xb9, 0x52, 0xbf, 0xaf, 0xf0, 0x82, 0x1d, 0x8d, 0x82, 0x4a, 0xb6, 0x20, 0x9b, 0x80, 0x93, 0x4d, 0x60, 0xea, 0x61, - 0xf4, 0x32, 0xb2, 0xd5, 0xfa, 0xf6, 0x23, 0xf6, 0xab, 0x69, 0x4c, 0x2e, 0x35, 0x66, 0x2c, 0xad, 0x59, 0xa9, 0x31, - 0xf1, 0xfb, 0x29, 0x0d, 0x8d, 0x19, 0x5f, 0xab, 0x31, 0x91, 0x76, 0x7d, 0x1c, 0x39, 0xb4, 0x37, 0x31, 0x94, 0x62, - 0x68, 0x70, 0x8e, 0xb6, 0x09, 0xf8, 0xcf, 0xc3, 0x4f, 0xd2, 0x34, 0x21, 0xc8, 0x31, 0x96, 0x20, 0x8d, 0x2d, 0xb2, - 0x4b, 0x04, 0x76, 0xc7, 0xa5, 0xde, 0xf8, 0xf8, 0x68, 0x4c, 0xc0, 0xdd, 0xc5, 0x98, 0xa1, 0x58, 0x3e, 0xde, 0xc2, - 0x21, 0xc4, 0x7e, 0x29, 0xe9, 0x19, 0x90, 0xda, 0xa2, 0x71, 0xbc, 0x85, 0x08, 0x4a, 0x05, 0xf6, 0xdb, 0x37, 0x07, - 0x87, 0xb6, 0x77, 0x92, 0x8d, 0x2e, 0x03, 0x1b, 0x1c, 0x0a, 0x30, 0x3e, 0x5c, 0x9f, 0x4f, 0x58, 0xea, 0x28, 0x93, - 0x3e, 0x4b, 0xc0, 0xa5, 0xc9, 0x4e, 0xc5, 0x37, 0x14, 0x9a, 0x01, 0x75, 0x20, 0x26, 0x7d, 0x64, 0x71, 0x6f, 0x97, - 0x8b, 0xef, 0x8f, 0xf2, 0x1c, 0x1f, 0xf7, 0x30, 0xa5, 0x60, 0x77, 0x0b, 0x1e, 0xf0, 0xe5, 0x00, 0xd5, 0x91, 0x7e, - 0x13, 0x70, 0x16, 0xe2, 0x7d, 0x0b, 0xdb, 0x6f, 0xa9, 0xbe, 0x08, 0xc5, 0x9e, 0x64, 0x35, 0x6d, 0x75, 0x57, 0xa6, - 0x1c, 0x8d, 0x3d, 0x42, 0x4b, 0x8d, 0xac, 0x6e, 0x20, 0x05, 0x1f, 0xe9, 0x12, 0xa1, 0xfd, 0x8d, 0x22, 0x1a, 0xa5, - 0xd2, 0xd5, 0xb2, 0x0a, 0x27, 0x24, 0x2a, 0x8a, 0xc9, 0xe0, 0x27, 0x81, 0x7f, 0x6c, 0x7e, 0x2f, 0x4c, 0x7c, 0xda, - 0x47, 0x23, 0x79, 0xf4, 0x57, 0xef, 0x23, 0xf3, 0x2e, 0x8f, 0xa9, 0xa5, 0x72, 0x4e, 0x31, 0x6a, 0x82, 0x4e, 0x7c, - 0x5b, 0x45, 0x21, 0xc0, 0x3c, 0x49, 0xa2, 0x59, 0xc1, 0x02, 0xf5, 0x20, 0xfd, 0x54, 0x74, 0x77, 0x97, 0x03, 0x03, - 0xa6, 0x19, 0x53, 0xf2, 0xc9, 0xc2, 0x74, 0x64, 0x1f, 0x80, 0x23, 0x8b, 0x49, 0xf8, 0xad, 0x08, 0x94, 0x6f, 0x1a, - 0x24, 0x6c, 0xcc, 0x4b, 0x8e, 0xb7, 0xbc, 0x17, 0x2a, 0x72, 0xe0, 0x77, 0x77, 0xc0, 0xb9, 0xb5, 0x7c, 0xfc, 0xff, - 0xb6, 0xb1, 0x47, 0x41, 0x0a, 0xce, 0x28, 0x5d, 0xfb, 0xc0, 0x2b, 0x75, 0x00, 0x91, 0xf9, 0xbe, 0x30, 0x26, 0x1a, - 0x32, 0x8c, 0xaa, 0x94, 0x3c, 0x07, 0xb1, 0xed, 0xf1, 0xdc, 0x6c, 0x07, 0xe2, 0x76, 0x29, 0xb4, 0xb2, 0xec, 0xdc, - 0x6f, 0xbb, 0xd2, 0x05, 0x58, 0x6e, 0xac, 0x22, 0x24, 0xf5, 0xb7, 0x25, 0x0a, 0x19, 0xb1, 0x9c, 0x52, 0x68, 0x9a, - 0x85, 0xe8, 0x61, 0xe2, 0xb4, 0x1c, 0xe5, 0xb9, 0xd5, 0x50, 0x2c, 0x69, 0xc7, 0x1f, 0xd1, 0x8e, 0x27, 0x19, 0x36, - 0x58, 0x88, 0xb9, 0x87, 0x51, 0x32, 0x74, 0x10, 0x00, 0xab, 0x65, 0x3d, 0x02, 0x6a, 0xba, 0x2a, 0xd2, 0xe0, 0x3f, - 0x44, 0xe2, 0x96, 0x42, 0xe2, 0xad, 0xa0, 0xd2, 0xf1, 0xa0, 0x2c, 0x7b, 0xe7, 0xcc, 0x39, 0xfa, 0x5d, 0x5e, 0x1a, - 0x10, 0x77, 0x45, 0xf5, 0xf7, 0xf6, 0xda, 0xa5, 0x3b, 0xf0, 0x7e, 0x30, 0x3e, 0x62, 0x66, 0x2b, 0x86, 0xb6, 0x3d, - 0x58, 0x86, 0x1f, 0x42, 0xec, 0xfb, 0xca, 0xb1, 0xd1, 0xb2, 0xa4, 0x9a, 0xcb, 0x16, 0xf1, 0x97, 0x8d, 0xdd, 0x44, - 0xc8, 0xfb, 0xfb, 0xeb, 0x22, 0x17, 0xdf, 0xdc, 0x1e, 0xb9, 0x60, 0x77, 0x8c, 0x5c, 0x7c, 0xf3, 0x27, 0x47, 0x2e, - 0xbe, 0x6f, 0x46, 0x2e, 0x7e, 0xfb, 0x9c, 0xc8, 0x45, 0x9e, 0x9d, 0x17, 0x61, 0x47, 0x9e, 0x94, 0x83, 0xcc, 0xf9, - 0xfb, 0x84, 0x30, 0x60, 0xa2, 0x46, 0x00, 0x83, 0x22, 0x16, 0x22, 0xb9, 0x0f, 0x24, 0xbb, 0x8c, 0x17, 0xb4, 0x75, - 0x16, 0x5d, 0xeb, 0xbe, 0xba, 0x36, 0x04, 0x1e, 0x9b, 0xab, 0x2f, 0xbc, 0x75, 0x55, 0x44, 0x21, 0xa0, 0xef, 0x7e, - 0xea, 0x8e, 0xdd, 0x4d, 0x95, 0xbe, 0x65, 0x8e, 0xd0, 0x53, 0x51, 0x79, 0xc1, 0x3e, 0x0b, 0xfb, 0xdf, 0x1d, 0x75, - 0x7a, 0xdb, 0x9d, 0x29, 0xf4, 0x06, 0x2d, 0x0a, 0x6f, 0xed, 0xde, 0xf6, 0x36, 0xbe, 0x9d, 0xab, 0xb7, 0x2e, 0xbe, - 0xc5, 0xea, 0x6d, 0x07, 0xdf, 0x86, 0xea, 0xed, 0x01, 0xbe, 0x8d, 0xd4, 0xdb, 0x43, 0x7c, 0x3b, 0xb3, 0xcb, 0x23, - 0xae, 0x81, 0x7b, 0x08, 0x7c, 0x45, 0xc6, 0x7e, 0xa0, 0xca, 0x60, 0xd3, 0xe2, 0x75, 0xbb, 0xe8, 0x34, 0x88, 0x3d, - 0xe1, 0x14, 0x05, 0xb9, 0x77, 0x0e, 0x92, 0x3f, 0xa0, 0xec, 0xb2, 0xa7, 0xf8, 0xdd, 0x05, 0xf0, 0x21, 0x0e, 0xe3, - 0x29, 0x53, 0x1f, 0xa0, 0x55, 0x58, 0x83, 0x1d, 0x79, 0xd4, 0x1e, 0x94, 0x3d, 0xbd, 0x4e, 0x22, 0x60, 0xa2, 0x4e, - 0xef, 0x69, 0xe5, 0xaa, 0x3a, 0x31, 0x5d, 0x4b, 0xaf, 0xf0, 0x35, 0x7a, 0xc4, 0x70, 0xa3, 0xc7, 0x60, 0x21, 0xb5, - 0x2e, 0xc0, 0xf1, 0x5a, 0xa9, 0x5b, 0x10, 0x22, 0xad, 0x4d, 0x08, 0x27, 0xfd, 0x76, 0x18, 0x9d, 0xea, 0xe7, 0x57, - 0x60, 0xf0, 0x46, 0xa7, 0xec, 0x36, 0x3d, 0x43, 0x20, 0x9a, 0x3a, 0x46, 0x01, 0x41, 0xf6, 0x10, 0x2c, 0x0d, 0x3a, - 0x80, 0x52, 0xc7, 0x20, 0x75, 0xea, 0x5a, 0x87, 0xa6, 0xaf, 0x17, 0x01, 0x45, 0xab, 0x82, 0x5d, 0xb0, 0xbb, 0xa9, - 0x54, 0x50, 0x18, 0x2a, 0xb0, 0xe0, 0x46, 0x55, 0xa4, 0x7d, 0xe4, 0x6b, 0x15, 0x92, 0xa5, 0x74, 0x91, 0x19, 0xcd, - 0xd7, 0xa1, 0xfc, 0x65, 0xf1, 0xf8, 0x45, 0x67, 0x88, 0x7f, 0xa4, 0xf0, 0xfd, 0x62, 0x3c, 0x1e, 0xdf, 0xa8, 0x9b, - 0xbe, 0x18, 0x8d, 0x59, 0x97, 0xed, 0xf4, 0x30, 0xd2, 0xdb, 0x92, 0xe2, 0xb0, 0x53, 0x12, 0xed, 0x16, 0x77, 0x6b, - 0x8c, 0x92, 0x13, 0xd4, 0xd5, 0xdd, 0x95, 0x58, 0x09, 0x54, 0x59, 0x80, 0xf0, 0x3e, 0x8d, 0xd3, 0xa0, 0x5d, 0xfa, - 0x67, 0x52, 0xea, 0x7f, 0xf1, 0xe8, 0xd1, 0xa3, 0xd2, 0x1f, 0xa9, 0xb7, 0xf6, 0x68, 0x54, 0xfa, 0xc3, 0x85, 0x46, - 0xa3, 0xdd, 0x1e, 0x8f, 0x4b, 0x3f, 0x56, 0x05, 0xdb, 0xdd, 0xe1, 0x68, 0xbb, 0x5b, 0xfa, 0xe7, 0x46, 0x8b, 0xd2, - 0x67, 0xf2, 0x2d, 0x67, 0xa3, 0x5a, 0xb8, 0xf8, 0x61, 0x1b, 0x2a, 0x05, 0xa3, 0x2d, 0xd0, 0xc9, 0x13, 0x8f, 0x41, - 0x34, 0xe7, 0x59, 0x79, 0x0c, 0xb2, 0x9d, 0x81, 0x7c, 0x1e, 0x4b, 0xd9, 0x2e, 0xbe, 0xef, 0x8a, 0x12, 0xfd, 0x37, - 0x53, 0xa2, 0x23, 0x33, 0x93, 0x34, 0x67, 0xa4, 0x07, 0x9a, 0xd5, 0xc8, 0x59, 0x54, 0xfd, 0x5b, 0xc8, 0x2a, 0x61, - 0x8f, 0xd2, 0x06, 0x5b, 0x0a, 0x19, 0xff, 0xed, 0x75, 0x32, 0xfe, 0xbb, 0xdb, 0x65, 0xfc, 0xc9, 0xdd, 0x44, 0xfc, - 0x77, 0x7f, 0xb2, 0x88, 0xff, 0xd6, 0x14, 0xf1, 0x42, 0x88, 0x5d, 0x82, 0xf5, 0x4a, 0x66, 0xeb, 0x49, 0x76, 0xd1, - 0xc2, 0x2d, 0x91, 0xdb, 0x24, 0x3d, 0xd7, 0xef, 0x24, 0xfc, 0x57, 0xe4, 0xff, 0xa8, 0xc1, 0x8c, 0x8f, 0xc5, 0xf2, - 0xec, 0xf4, 0x34, 0x61, 0x4a, 0xc6, 0x1b, 0x15, 0x64, 0x0e, 0xbf, 0x49, 0x43, 0xfb, 0x0d, 0x38, 0xa8, 0x46, 0xc9, - 0x78, 0x0c, 0x45, 0xe3, 0xb1, 0xad, 0xf2, 0x63, 0x41, 0x9e, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5, 0xf5, 0xd7, - 0x66, 0x99, 0x59, 0x20, 0x23, 0x51, 0xa6, 0x31, 0x21, 0x6b, 0x46, 0x71, 0x81, 0x7b, 0xb0, 0xfa, 0xb8, 0x2d, 0xda, - 0x2b, 0x53, 0x50, 0x2a, 0xf1, 0x10, 0xbf, 0x9a, 0xd2, 0xfc, 0x90, 0x88, 0xc8, 0x65, 0x5e, 0x46, 0xae, 0x3a, 0xef, - 0x34, 0xbe, 0x59, 0x57, 0x9d, 0x71, 0xc2, 0xe2, 0xcb, 0x7c, 0x86, 0xc7, 0x97, 0x2f, 0x46, 0xce, 0x25, 0xd8, 0xb1, - 0x71, 0xf1, 0x26, 0x6d, 0xe4, 0x89, 0x09, 0xb0, 0xc3, 0xd0, 0xc4, 0xb4, 0x14, 0x04, 0xab, 0x12, 0xe6, 0xab, 0xca, - 0x9e, 0xd1, 0x49, 0xa6, 0x13, 0xe1, 0x90, 0xfd, 0x1a, 0x59, 0x02, 0x73, 0x30, 0xa9, 0x0b, 0xe9, 0xe3, 0xe5, 0x22, - 0xc9, 0xe2, 0x4c, 0x7e, 0xe5, 0x9a, 0xa2, 0xff, 0x0b, 0xa9, 0x3f, 0xe4, 0xf1, 0x7b, 0xd5, 0x13, 0x03, 0xee, 0x62, - 0x86, 0x51, 0xa9, 0x82, 0xec, 0x40, 0xb8, 0x19, 0x7e, 0x92, 0x47, 0x0c, 0xa1, 0x62, 0xd9, 0x15, 0xf5, 0xf0, 0x13, - 0x28, 0xd5, 0x97, 0x21, 0x6b, 0x5f, 0x11, 0x6c, 0xf0, 0x00, 0x7e, 0xdd, 0x9f, 0xa3, 0x36, 0xc8, 0xe6, 0xdc, 0x71, - 0xa8, 0x95, 0xe3, 0x96, 0x5e, 0x77, 0x07, 0x3c, 0x4a, 0xd7, 0x17, 0xdf, 0xfd, 0x71, 0x74, 0x67, 0x89, 0xef, 0x75, - 0xa1, 0xf3, 0xa5, 0xef, 0x70, 0x69, 0x12, 0xe3, 0x87, 0x42, 0x04, 0xa2, 0xc6, 0x5d, 0x11, 0xb5, 0x88, 0xcd, 0x77, - 0x5f, 0xb9, 0x6f, 0x06, 0x61, 0xdd, 0x55, 0x1c, 0x2c, 0xe3, 0x64, 0xf5, 0x42, 0x6c, 0x2b, 0xac, 0x9a, 0x65, 0x70, - 0x6e, 0xd1, 0x99, 0xc5, 0xb9, 0x11, 0x77, 0xae, 0x6d, 0x83, 0x52, 0x05, 0x9e, 0x45, 0xf4, 0xf8, 0x12, 0x63, 0xa4, - 0xc2, 0xf7, 0x55, 0x40, 0xd7, 0xbd, 0x4e, 0x03, 0x72, 0xf4, 0x47, 0x35, 0xa3, 0xab, 0x2a, 0x55, 0x50, 0x9a, 0xa7, - 0x04, 0x06, 0x32, 0x14, 0x00, 0x86, 0x35, 0x4e, 0x85, 0xde, 0x82, 0x69, 0x48, 0x00, 0x6b, 0x8f, 0x0c, 0xdd, 0x12, - 0x5b, 0x81, 0x2d, 0xa4, 0x05, 0x28, 0x3d, 0xec, 0xb0, 0x67, 0xd5, 0x40, 0x4f, 0x97, 0xe3, 0xc6, 0x37, 0x39, 0x69, - 0x97, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb, 0xcb, 0x05, 0x48, 0xf9, 0x93, 0x4f, 0x31, 0x07, 0x9b, 0x7a, 0xd6, - 0xc2, 0x28, 0x08, 0x85, 0x31, 0xa5, 0x3a, 0xa4, 0xa3, 0x47, 0x71, 0x45, 0xa8, 0x37, 0x2f, 0xd0, 0x97, 0x23, 0xa7, - 0x25, 0x48, 0xb3, 0x94, 0xf5, 0xea, 0x47, 0xcc, 0xa6, 0xdf, 0xa0, 0x88, 0x35, 0x58, 0x64, 0xe8, 0xfb, 0xf1, 0x4b, - 0xf0, 0xfd, 0x84, 0x1a, 0x6d, 0x2b, 0xa7, 0xa1, 0xbd, 0xb2, 0x7d, 0x20, 0x69, 0xbb, 0x49, 0xd6, 0x42, 0xbe, 0xec, - 0x1c, 0x5d, 0xe7, 0xdc, 0xdc, 0x76, 0xe0, 0xda, 0xdd, 0xd9, 0xf1, 0xd4, 0x3f, 0xe3, 0xa4, 0xba, 0x59, 0x4c, 0x07, - 0xae, 0x77, 0x81, 0x2c, 0x88, 0xc6, 0xf8, 0x45, 0xbd, 0xbb, 0xb4, 0x3c, 0xa1, 0x6c, 0xc7, 0x05, 0xaa, 0xf5, 0xa0, - 0xf3, 0x08, 0xbc, 0xb5, 0x3b, 0x0f, 0x7f, 0x33, 0xfa, 0xa5, 0xa4, 0x91, 0xba, 0xb4, 0x6a, 0xdb, 0x3d, 0x94, 0x17, - 0x49, 0x74, 0x09, 0x4e, 0x23, 0xd9, 0x18, 0x27, 0x18, 0xc4, 0xed, 0xcd, 0x32, 0x99, 0x39, 0x90, 0xb3, 0x84, 0x7e, - 0x6d, 0x85, 0x5c, 0x8a, 0xed, 0x07, 0xb3, 0x0b, 0xb5, 0x1a, 0x9d, 0x46, 0x46, 0xc0, 0x9f, 0x7a, 0xf0, 0x7f, 0x7d, - 0xa6, 0x41, 0xfd, 0xf0, 0x7a, 0x07, 0x60, 0x10, 0x86, 0x4d, 0x2b, 0x17, 0x50, 0xb5, 0xa1, 0xc4, 0x48, 0x7d, 0xa8, - 0x06, 0xb2, 0xfc, 0x6d, 0x50, 0x95, 0x51, 0xc1, 0x7a, 0xf8, 0x69, 0xc3, 0x18, 0x5c, 0x53, 0x69, 0x3c, 0x4d, 0xe3, - 0xd1, 0x28, 0x61, 0x3d, 0x65, 0x1f, 0x59, 0x9d, 0x07, 0x98, 0x39, 0x60, 0x2e, 0x59, 0x7d, 0x55, 0x0c, 0xe2, 0x69, - 0x3a, 0x45, 0x27, 0x60, 0xaf, 0xe1, 0xf7, 0x09, 0x57, 0x92, 0x53, 0x1e, 0xa9, 0xb7, 0x2b, 0xe2, 0xd1, 0x73, 0x1d, - 0x97, 0x1d, 0x30, 0x16, 0x69, 0xc1, 0xdb, 0x3d, 0x9e, 0xcd, 0x82, 0xd6, 0x76, 0x1d, 0x11, 0xac, 0xd2, 0x28, 0x78, - 0x2b, 0xd0, 0xf2, 0xd0, 0x3a, 0x10, 0x5a, 0xce, 0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0xe0, 0xe9, 0xa2, 0xb2, - 0x8e, 0xcc, 0xff, 0x9f, 0xdd, 0xf2, 0xd5, 0xea, 0xdd, 0xf2, 0x95, 0xda, 0x2d, 0x37, 0x73, 0xec, 0x17, 0xe3, 0x0e, - 0xfe, 0xe9, 0x55, 0x08, 0xc1, 0xaa, 0x00, 0x39, 0x2c, 0xb4, 0x8b, 0x5b, 0x5d, 0xf8, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, - 0xc7, 0x07, 0x0b, 0xb0, 0x6d, 0x61, 0x21, 0xfe, 0x6b, 0xd7, 0xaa, 0x3a, 0xf7, 0xb1, 0x0e, 0x7b, 0xed, 0x2c, 0xd7, - 0x75, 0x6f, 0xde, 0xb4, 0x20, 0xaf, 0xb8, 0x13, 0x28, 0x61, 0x0c, 0xae, 0x5a, 0x74, 0x72, 0x02, 0xa5, 0xe3, 0x6c, - 0x38, 0x2f, 0xfe, 0x51, 0xc2, 0x2f, 0x89, 0x78, 0xe3, 0x96, 0x6e, 0x8c, 0xa3, 0xba, 0x8a, 0x34, 0x14, 0x35, 0xc2, - 0x42, 0xaf, 0x53, 0x50, 0x00, 0x63, 0x32, 0xa7, 0xeb, 0xdf, 0x5f, 0xb1, 0x09, 0xfe, 0x2e, 0x6b, 0xb3, 0x12, 0x99, - 0x7f, 0x2d, 0x31, 0x6e, 0x24, 0xc2, 0x67, 0xd1, 0xc0, 0x5c, 0xc3, 0xf6, 0xa3, 0xd5, 0xe0, 0x1e, 0xa9, 0x99, 0x06, - 0x4a, 0x29, 0x48, 0xbd, 0x03, 0x5e, 0x40, 0x34, 0x4f, 0xf8, 0xcd, 0xa3, 0x5e, 0xc7, 0x19, 0x4b, 0xa3, 0xde, 0x20, - 0xd0, 0xab, 0xb6, 0x77, 0x94, 0xd2, 0x5f, 0x7c, 0x75, 0x1f, 0xff, 0x88, 0xc0, 0xd7, 0x49, 0xe5, 0x1b, 0x89, 0xd8, - 0x00, 0xfa, 0x46, 0xeb, 0x35, 0xe7, 0x47, 0x68, 0x70, 0xf2, 0x7f, 0xee, 0xda, 0x1a, 0x8d, 0xf5, 0x3b, 0x35, 0x97, - 0x56, 0xe9, 0x67, 0xb5, 0xfe, 0xbc, 0xc1, 0xef, 0xd8, 0x76, 0x28, 0x1c, 0x82, 0x7a, 0x5b, 0xf9, 0x2b, 0x44, 0x96, - 0x1a, 0x2b, 0x8a, 0xdf, 0xb5, 0x7d, 0x65, 0x12, 0x53, 0x8f, 0xf5, 0xf0, 0x44, 0x3b, 0x91, 0xf2, 0xcc, 0x19, 0x7b, - 0x08, 0x3f, 0xf2, 0x27, 0x16, 0xde, 0xc3, 0xaf, 0x6f, 0x59, 0x17, 0xd3, 0x24, 0x05, 0xb3, 0x6a, 0xc2, 0xf9, 0x2c, - 0xd8, 0xda, 0x3a, 0x3f, 0x3f, 0xf7, 0xcf, 0xb7, 0xfd, 0x2c, 0x3f, 0xdd, 0xea, 0xb6, 0xdb, 0x6d, 0xfc, 0x68, 0x92, - 0x6d, 0x9d, 0xc5, 0xec, 0xfc, 0x31, 0xb8, 0x1f, 0xf6, 0x43, 0xeb, 0x91, 0xf5, 0x70, 0xdb, 0xda, 0x79, 0x60, 0x5b, - 0xa4, 0x00, 0xa0, 0x64, 0xdb, 0xb6, 0x84, 0x02, 0x08, 0x6d, 0x28, 0xde, 0xdb, 0x3d, 0x55, 0x36, 0x1c, 0x26, 0xa5, - 0x0b, 0x0b, 0x09, 0xfc, 0xb7, 0xec, 0x13, 0xab, 0x6f, 0x75, 0x51, 0xd6, 0x92, 0x6a, 0x44, 0xbd, 0xe2, 0x7e, 0x1f, - 0x46, 0xb3, 0x80, 0xd8, 0xc8, 0x2c, 0xc4, 0x30, 0x99, 0x28, 0xa5, 0x29, 0xd0, 0x2e, 0x3d, 0x81, 0x27, 0x70, 0x0b, - 0x26, 0x16, 0x3c, 0xbf, 0xea, 0x3e, 0x04, 0x1d, 0x77, 0xd6, 0xba, 0x3f, 0x6c, 0xb7, 0x3a, 0x56, 0xa7, 0xd5, 0xf5, - 0x1f, 0x5a, 0x5d, 0xf1, 0x3f, 0xc8, 0xc8, 0x6d, 0xab, 0x03, 0x4f, 0xdb, 0x16, 0xbc, 0x9f, 0xdd, 0x17, 0x29, 0x11, - 0x91, 0xbd, 0xb5, 0xb7, 0x8b, 0xbf, 0x43, 0x08, 0x90, 0xfa, 0xca, 0x16, 0xbf, 0xf5, 0xcc, 0xfe, 0xc2, 0x2c, 0xed, - 0x3c, 0x5a, 0x59, 0xdc, 0x7d, 0xb8, 0xb2, 0x78, 0xfb, 0xc1, 0xca, 0xe2, 0xfb, 0x3b, 0xf5, 0xe2, 0xad, 0x53, 0x51, - 0xa5, 0xe5, 0x42, 0x68, 0x4f, 0x23, 0x60, 0x94, 0x0b, 0xa7, 0x03, 0x70, 0xb6, 0xad, 0x16, 0xfe, 0x78, 0xd8, 0x75, - 0x75, 0xaf, 0x13, 0xec, 0xa5, 0xb1, 0x7c, 0xf8, 0x08, 0xb0, 0x7c, 0xde, 0x7d, 0x30, 0xc4, 0x76, 0x84, 0x28, 0xfc, - 0x3b, 0xdb, 0x7e, 0x34, 0x04, 0x8d, 0x60, 0xe1, 0x3f, 0xf8, 0x33, 0xd9, 0xe9, 0x0e, 0xc5, 0x4b, 0x1b, 0xeb, 0x3f, - 0x74, 0x1e, 0x16, 0xd0, 0x14, 0xff, 0xfc, 0xae, 0x4d, 0x68, 0x34, 0xe0, 0xcd, 0x71, 0xef, 0x03, 0x8d, 0x1e, 0x4d, - 0xba, 0xfe, 0x57, 0x67, 0x0f, 0xfd, 0x47, 0x93, 0xce, 0xc3, 0x0f, 0xe2, 0x2d, 0x01, 0x0a, 0x7e, 0x85, 0xff, 0x3e, - 0x6c, 0xb7, 0x27, 0xad, 0x8e, 0xff, 0xe8, 0x6c, 0xdb, 0xdf, 0x4e, 0x5a, 0x0f, 0xfc, 0x47, 0xf8, 0xaf, 0x1a, 0x6e, - 0x92, 0x4d, 0x99, 0x6d, 0xe1, 0x7a, 0x37, 0xfc, 0x5e, 0x73, 0x8e, 0xee, 0x7d, 0x6b, 0xe7, 0xfe, 0xf3, 0x47, 0xb0, - 0x46, 0x93, 0x4e, 0x17, 0xfe, 0xbf, 0xee, 0xf1, 0x03, 0x12, 0x5e, 0x0e, 0x1c, 0x31, 0x4c, 0x27, 0x55, 0x84, 0xa3, - 0x0f, 0x78, 0xdd, 0xf3, 0x7e, 0xbc, 0x2e, 0x00, 0xf2, 0xd7, 0xdb, 0x03, 0x20, 0x3f, 0xdc, 0x31, 0xc8, 0xfd, 0xd7, - 0x3f, 0x39, 0x02, 0xf2, 0x63, 0x33, 0xc8, 0xbd, 0xcf, 0x96, 0x02, 0x1d, 0x4d, 0x67, 0xed, 0x39, 0x73, 0x8e, 0x7e, - 0x62, 0x03, 0x4c, 0x9d, 0x86, 0xd6, 0x3f, 0xd4, 0xe2, 0x41, 0x19, 0x6e, 0xe4, 0x7d, 0x26, 0x76, 0x32, 0xe3, 0xd7, - 0x10, 0x84, 0xf3, 0x5b, 0x09, 0xf2, 0xe2, 0x6e, 0xf4, 0xe0, 0xfc, 0xcf, 0xa5, 0x07, 0x7d, 0xbd, 0x5f, 0xd1, 0xa3, - 0x16, 0x71, 0xa7, 0x88, 0x01, 0x39, 0xfa, 0x07, 0xf4, 0xee, 0xd8, 0x5b, 0x0c, 0xdf, 0x0a, 0x5b, 0xe4, 0x03, 0xbe, - 0xfb, 0x9c, 0xd3, 0x01, 0x91, 0x59, 0x1c, 0xda, 0x32, 0x00, 0x33, 0xc3, 0xef, 0xd3, 0xaa, 0x97, 0x33, 0x71, 0x7b, - 0x25, 0xa4, 0xab, 0x67, 0x3b, 0x3a, 0x78, 0x83, 0xc9, 0xde, 0xe1, 0x22, 0xe3, 0x11, 0xfe, 0x02, 0x24, 0x1e, 0xf3, - 0x04, 0x2f, 0xc6, 0xca, 0x4b, 0x64, 0x98, 0x9b, 0xfc, 0x1d, 0xe6, 0x55, 0xab, 0x43, 0x82, 0x29, 0x06, 0x0c, 0x5e, - 0xb1, 0x51, 0x1c, 0x39, 0xb6, 0x33, 0x83, 0x1d, 0x0b, 0x63, 0xb6, 0x6a, 0x49, 0xcd, 0x94, 0xcf, 0xec, 0xda, 0xea, - 0x77, 0xee, 0xe4, 0xf8, 0x0d, 0xb3, 0xf0, 0x48, 0x06, 0x18, 0x6d, 0xe9, 0x01, 0xc0, 0xf8, 0xaa, 0x24, 0x47, 0x61, - 0x5f, 0x59, 0x0d, 0xb6, 0x30, 0x1b, 0x3a, 0x7e, 0x17, 0xdc, 0x08, 0x2a, 0xc6, 0x6f, 0x42, 0xfd, 0xe8, 0xb4, 0xb6, - 0xc1, 0xac, 0x31, 0xba, 0xe9, 0x81, 0x06, 0x4b, 0x61, 0x24, 0x11, 0x1c, 0x68, 0x94, 0x7a, 0xfa, 0x97, 0x90, 0x55, - 0xe1, 0xa2, 0xe2, 0xf1, 0xe5, 0xa1, 0xbc, 0xf7, 0x6d, 0x63, 0xe4, 0x96, 0x22, 0xf6, 0xd5, 0x37, 0xa7, 0x36, 0x41, - 0x5d, 0xd0, 0x6f, 0x82, 0xa4, 0x73, 0x6f, 0xd4, 0x08, 0x98, 0x76, 0x6d, 0x49, 0xcf, 0x21, 0xb4, 0x85, 0x3e, 0x18, - 0xb1, 0xb3, 0x78, 0x28, 0xc5, 0xba, 0x67, 0xc9, 0xeb, 0x22, 0x2d, 0xc2, 0x22, 0xec, 0x78, 0xc2, 0x77, 0x86, 0x17, - 0xd4, 0x6a, 0x61, 0x9a, 0xd9, 0x7b, 0xf7, 0x7a, 0x1a, 0x92, 0x7a, 0xc6, 0xba, 0x8d, 0xbf, 0x96, 0xf2, 0x08, 0x7c, - 0xb5, 0x7f, 0x0c, 0xef, 0xe1, 0x2f, 0xa6, 0xbc, 0x37, 0xb0, 0x5d, 0x9f, 0x84, 0xe2, 0xbd, 0xea, 0xb7, 0x53, 0xa2, - 0x44, 0xd8, 0x04, 0xfd, 0xe5, 0xdd, 0x55, 0x91, 0x49, 0xa5, 0xd5, 0xdd, 0xa9, 0x94, 0x16, 0x3c, 0x1b, 0x52, 0x0a, - 0x04, 0x68, 0xd7, 0xdf, 0x31, 0x44, 0xe1, 0x59, 0x0b, 0x7f, 0xd6, 0x84, 0xe1, 0x7d, 0x68, 0xa0, 0xa4, 0xe1, 0x4b, - 0x68, 0xbe, 0x2d, 0x04, 0x2f, 0xf4, 0xfb, 0x89, 0x44, 0x95, 0x10, 0x53, 0x75, 0x8e, 0x59, 0x71, 0x88, 0x24, 0x72, - 0x04, 0x6c, 0xcf, 0x88, 0x37, 0x09, 0x76, 0x95, 0xd1, 0x94, 0xa7, 0xd0, 0xd7, 0xd1, 0x9f, 0x72, 0x5e, 0x55, 0xe7, - 0xd5, 0x76, 0xce, 0x8a, 0x29, 0x90, 0xe1, 0x1b, 0x07, 0x55, 0x74, 0x7d, 0x41, 0x7c, 0xd2, 0x4c, 0x6c, 0xe3, 0xea, - 0xa3, 0x6f, 0x2b, 0x32, 0x70, 0xcd, 0x4d, 0xc1, 0x2a, 0xa6, 0xa1, 0x7d, 0x81, 0x69, 0x33, 0xf8, 0xb3, 0x2a, 0x56, - 0x0f, 0x92, 0xa1, 0xfc, 0x24, 0xc2, 0xdf, 0xc8, 0x42, 0x3f, 0xca, 0x6a, 0x03, 0x72, 0xfa, 0x66, 0x25, 0x41, 0xfa, - 0x62, 0x54, 0x36, 0x91, 0x00, 0x7b, 0x01, 0x7f, 0xc1, 0x5f, 0x75, 0x5d, 0x42, 0xde, 0x83, 0xc4, 0x9c, 0x82, 0x51, - 0x9c, 0xd3, 0xf5, 0x5a, 0x85, 0x7f, 0x2d, 0xa2, 0x59, 0x91, 0x9a, 0x76, 0x25, 0x2b, 0xfa, 0x36, 0x16, 0xd9, 0x81, - 0x4c, 0x48, 0x33, 0x3f, 0xda, 0x6c, 0xde, 0x7f, 0x1c, 0x89, 0x5c, 0x34, 0xfc, 0xa8, 0xbd, 0x2d, 0x88, 0x6c, 0x83, - 0x18, 0xbb, 0x16, 0x27, 0x32, 0x6e, 0xf0, 0xda, 0x60, 0xf5, 0x5b, 0x8a, 0xcc, 0x0d, 0x6f, 0x9b, 0xab, 0xa5, 0xc7, - 0xa5, 0x75, 0x70, 0x65, 0xfc, 0xee, 0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0x4f, 0x72, 0x0c, 0xb1, 0xe0, 0x75, 0xd8, - 0xb6, 0x5b, 0x82, 0xe4, 0x31, 0x7e, 0x8d, 0x93, 0x20, 0xbd, 0x0f, 0x85, 0x55, 0xd2, 0xd6, 0xee, 0xa4, 0xbb, 0xf7, - 0xe6, 0x70, 0xdf, 0x12, 0xbb, 0x79, 0x77, 0x0b, 0x5e, 0x77, 0xc9, 0x1d, 0x16, 0xf9, 0x19, 0xa1, 0xc8, 0xcf, 0xb0, - 0x44, 0x52, 0x57, 0x68, 0x6f, 0x09, 0x34, 0x6d, 0x8b, 0xa5, 0x43, 0x11, 0xc3, 0x9b, 0x82, 0xbb, 0x10, 0xe3, 0x87, - 0xbd, 0xb6, 0xb0, 0x5b, 0x0b, 0x57, 0x1a, 0xb6, 0x80, 0x08, 0xf8, 0x29, 0x03, 0x81, 0xa7, 0x2a, 0xe2, 0x07, 0x6a, - 0x9d, 0xa9, 0x64, 0x17, 0x39, 0x94, 0xce, 0x4b, 0x5d, 0x6e, 0x5d, 0xcc, 0x4f, 0xa6, 0x20, 0x87, 0x54, 0x82, 0xca, - 0x7b, 0xd9, 0x61, 0x97, 0xa6, 0xa2, 0x0c, 0x94, 0xa5, 0x88, 0xe4, 0xa4, 0xb3, 0xb7, 0x1b, 0x49, 0x7b, 0x07, 0xf7, - 0x6e, 0x01, 0x9b, 0x17, 0xd4, 0x1c, 0x1a, 0x15, 0x7e, 0x9c, 0x6d, 0x9d, 0xb3, 0x93, 0x56, 0x34, 0x8b, 0xab, 0xf0, - 0x1f, 0x6a, 0xbf, 0xbd, 0x5d, 0xa5, 0x08, 0x65, 0xaa, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24, 0x1c, 0x31, 0x68, - 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0, 0xb4, 0xf7, 0xfe, - 0xf6, 0x3f, 0xfe, 0x97, 0x0c, 0x2b, 0x02, 0x59, 0x01, 0x2c, 0x4c, 0x83, 0xa9, 0x4e, 0x18, 0xd9, 0x05, 0x38, 0xa2, - 0xf1, 0xa8, 0x35, 0x89, 0x92, 0x31, 0x40, 0x50, 0x30, 0x71, 0x9d, 0x49, 0xd6, 0x03, 0x17, 0x48, 0xb0, 0xcc, 0xc3, - 0x79, 0x09, 0x5e, 0xbd, 0x08, 0x57, 0xec, 0x0f, 0xe5, 0xae, 0xaa, 0x9c, 0x61, 0x62, 0x68, 0x23, 0x93, 0xd5, 0xe0, - 0xb9, 0x5a, 0x36, 0xab, 0xfa, 0x25, 0x49, 0x52, 0x78, 0xb0, 0x5a, 0x2a, 0x2b, 0xb4, 0xd4, 0x07, 0x21, 0xff, 0xf2, - 0xcf, 0xff, 0xf1, 0xbf, 0xaa, 0x57, 0x3c, 0xdf, 0xf8, 0xdb, 0x3f, 0xfd, 0xfb, 0xff, 0xfb, 0xbf, 0xff, 0x13, 0x26, - 0x07, 0xcb, 0x33, 0x10, 0xda, 0x4a, 0x56, 0x75, 0x00, 0x22, 0xf6, 0x94, 0x55, 0x39, 0x1c, 0xf5, 0x94, 0xd7, 0x03, - 0x9a, 0x90, 0x78, 0x53, 0x42, 0x47, 0x7c, 0x4d, 0x29, 0xd2, 0x44, 0xb5, 0x1b, 0xc8, 0x07, 0x4b, 0x69, 0xd1, 0xb1, - 0xbe, 0xbd, 0xd3, 0xb6, 0xab, 0xe5, 0xdd, 0x33, 0xfa, 0x6e, 0xe1, 0xc2, 0xdc, 0x29, 0x03, 0xc7, 0xd7, 0xcb, 0xb6, - 0x50, 0x61, 0x2c, 0x2c, 0x29, 0xab, 0x72, 0x0b, 0xe3, 0xcb, 0x0b, 0x7c, 0x0d, 0xba, 0x46, 0x31, 0xad, 0x72, 0xad, - 0x4f, 0xef, 0xd7, 0x39, 0x20, 0x3a, 0xc6, 0xa5, 0x11, 0xc1, 0x32, 0x3a, 0x3b, 0x6d, 0xa1, 0x75, 0x92, 0x5c, 0x96, - 0x34, 0x8a, 0xf0, 0x66, 0xee, 0x3f, 0xfa, 0x87, 0xf2, 0x2f, 0x53, 0xb4, 0x0a, 0x2c, 0x67, 0x1a, 0x5d, 0x48, 0x1f, - 0xe7, 0x41, 0xbb, 0x3d, 0xbb, 0x70, 0x17, 0xd5, 0x0c, 0xde, 0x75, 0x93, 0x51, 0x80, 0xcd, 0x1c, 0x90, 0x0e, 0x5d, - 0x75, 0x2c, 0x0f, 0xcc, 0xfa, 0x36, 0x86, 0x7e, 0xca, 0xf2, 0xcb, 0x05, 0x85, 0x93, 0xe2, 0xdf, 0xf1, 0x70, 0x54, - 0x46, 0xde, 0xa0, 0xc4, 0xc0, 0x62, 0x61, 0xf4, 0xea, 0x8a, 0x5e, 0x93, 0xce, 0x72, 0x6e, 0x8a, 0x79, 0xb8, 0x6b, - 0x1e, 0xcb, 0xde, 0xc7, 0x83, 0xd6, 0x49, 0xc7, 0x9b, 0x74, 0x17, 0x7a, 0x78, 0xce, 0xb3, 0xa9, 0x79, 0x9a, 0xcb, - 0x22, 0x36, 0x64, 0x63, 0x15, 0xb1, 0x94, 0xf5, 0xe2, 0xa4, 0xb6, 0xfc, 0x02, 0xb7, 0x1b, 0xd0, 0x36, 0x8b, 0x78, - 0x40, 0x4c, 0xdb, 0x33, 0xcf, 0x7b, 0x23, 0x3c, 0x49, 0xcf, 0x16, 0xc6, 0x5c, 0x3d, 0xd1, 0x14, 0xe3, 0x82, 0xf5, - 0xbc, 0x9f, 0xd2, 0xa7, 0xee, 0xe6, 0x50, 0x22, 0xac, 0xf0, 0x42, 0x1e, 0xa3, 0xbe, 0xab, 0xf9, 0xe3, 0x52, 0x14, - 0x83, 0x0b, 0xbc, 0xb2, 0x5e, 0xa8, 0x45, 0x51, 0xfb, 0x02, 0xac, 0x1d, 0x02, 0xd3, 0x6e, 0xb6, 0xa2, 0x42, 0x6c, - 0xf5, 0x2e, 0x7c, 0xa1, 0x6d, 0xef, 0x68, 0x36, 0xa3, 0x86, 0x2e, 0x70, 0x23, 0xd9, 0xd0, 0x28, 0x29, 0x28, 0x45, - 0x40, 0x9c, 0xc8, 0xcb, 0x36, 0x92, 0x6d, 0xc5, 0x93, 0x3c, 0xab, 0xa7, 0xdf, 0xb9, 0xfd, 0xff, 0x00, 0x52, 0x99, - 0xa6, 0xd8, 0x89, 0x7b, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3, + 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x25, 0x10, 0x22, 0x29, 0xcb, 0x76, 0x81, 0x02, 0x79, 0xe5, + 0xa5, 0xae, 0x5d, 0xe5, 0xad, 0x2c, 0xd9, 0x75, 0xab, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02, + 0x92, 0x5a, 0x8a, 0x42, 0x9f, 0x7e, 0xea, 0xa7, 0x39, 0x67, 0xd6, 0x87, 0x7e, 0x99, 0xd3, 0xfd, 0x30, 0x1f, 0x31, + 0xcf, 0xfd, 0x29, 0xf7, 0x07, 0xa6, 0x3f, 0x61, 0x22, 0x22, 0x17, 0x24, 0x40, 0x6a, 0x71, 0x75, 0xcd, 0x3d, 0x5e, + 0x04, 0xe4, 0x1a, 0x11, 0x19, 0x19, 0x5b, 0x46, 0x42, 0xbb, 0x77, 0xc6, 0xd9, 0x88, 0x5f, 0xcc, 0x99, 0x35, 0xe5, + 0xb3, 0xa4, 0xbf, 0x2b, 0xff, 0x67, 0xd1, 0xb8, 0xbf, 0x9b, 0xc4, 0xe9, 0x27, 0x2b, 0x67, 0x49, 0x18, 0x8f, 0xb2, + 0xd4, 0x9a, 0xe6, 0x6c, 0x12, 0x8e, 0x23, 0x1e, 0x05, 0xf1, 0x2c, 0x3a, 0x61, 0xd6, 0x56, 0x7f, 0x77, 0xc6, 0x78, + 0x64, 0x8d, 0xa6, 0x51, 0x5e, 0x30, 0x1e, 0xbe, 0x3f, 0xf8, 0xba, 0xf5, 0xa8, 0xbf, 0x5b, 0x8c, 0xf2, 0x78, 0xce, + 0x2d, 0x1c, 0x32, 0x9c, 0x65, 0xe3, 0x45, 0xc2, 0xfa, 0xa7, 0x51, 0x6e, 0x9d, 0xb3, 0xf0, 0xcd, 0xf1, 0x2f, 0x6c, + 0xc4, 0xfd, 0x31, 0x9b, 0xc4, 0x29, 0x7b, 0x9b, 0x67, 0x73, 0x96, 0xf3, 0x0b, 0xef, 0xd9, 0xfa, 0x8a, 0x98, 0x15, + 0xde, 0xbe, 0xae, 0x3a, 0x61, 0xfc, 0xcd, 0x59, 0xaa, 0xfa, 0x3c, 0x65, 0x62, 0x92, 0x2c, 0x2f, 0x3c, 0x7e, 0x45, + 0x9b, 0xfd, 0x8b, 0xd9, 0x71, 0x96, 0x14, 0xde, 0x27, 0x5d, 0x3f, 0xcf, 0x33, 0x9e, 0x21, 0x58, 0xfe, 0x34, 0x2a, + 0x8c, 0x96, 0xde, 0x93, 0x35, 0x4d, 0xe6, 0xb2, 0xf2, 0x45, 0xf1, 0x2c, 0x5d, 0xcc, 0x58, 0x1e, 0x1d, 0x27, 0xcc, + 0x2b, 0x58, 0xe8, 0x30, 0x8f, 0x7b, 0xb1, 0x1b, 0xf6, 0xb9, 0x15, 0xa7, 0x16, 0x1b, 0x9c, 0x33, 0x2a, 0x59, 0x32, + 0xdd, 0x2a, 0xb8, 0xd3, 0xf6, 0x80, 0x5c, 0x93, 0xf8, 0x64, 0xa1, 0xdf, 0xcf, 0xf2, 0x98, 0xab, 0xe7, 0xd3, 0x28, + 0x59, 0xb0, 0x20, 0x2e, 0xdd, 0x80, 0x1d, 0xf2, 0x61, 0x18, 0x7b, 0x4f, 0x68, 0x50, 0x18, 0x72, 0x39, 0xc9, 0x72, + 0x07, 0x69, 0x15, 0xe3, 0xd8, 0xfc, 0xf2, 0xd2, 0xe1, 0xe1, 0xb2, 0x74, 0xdd, 0x4f, 0xcc, 0x1f, 0x45, 0x49, 0xe2, + 0xe0, 0xc4, 0x77, 0xef, 0x16, 0x38, 0x63, 0xec, 0xf1, 0xc3, 0x78, 0xe8, 0xf6, 0xe2, 0x89, 0xc3, 0x99, 0x5b, 0xf5, + 0xcb, 0x26, 0x16, 0x67, 0x0e, 0x77, 0xdd, 0x27, 0x57, 0xf7, 0xc9, 0x19, 0x5f, 0xe4, 0x00, 0x7b, 0xe9, 0xbd, 0x51, + 0x33, 0x3f, 0xc3, 0xfa, 0x7d, 0xea, 0xd8, 0x03, 0xd8, 0x0b, 0x6e, 0x7d, 0x08, 0xcf, 0xe2, 0x74, 0x9c, 0x9d, 0xf9, + 0xfb, 0xd3, 0x08, 0x7e, 0xbc, 0xcb, 0x32, 0x7e, 0xf7, 0xae, 0x73, 0x9a, 0xc5, 0x63, 0xab, 0x1d, 0x86, 0x66, 0xe5, + 0xc5, 0x93, 0xfd, 0xfd, 0xcb, 0xcb, 0x46, 0x81, 0x9f, 0x46, 0x3c, 0x3e, 0x65, 0xa2, 0x33, 0x00, 0x60, 0xc3, 0xcf, + 0x39, 0x67, 0xe3, 0x7d, 0x7e, 0x91, 0x40, 0x29, 0x63, 0xbc, 0xb0, 0x01, 0xc7, 0xa7, 0xd9, 0x08, 0xc8, 0x96, 0x1a, + 0x84, 0x87, 0xa6, 0x39, 0x9b, 0x27, 0xd1, 0x88, 0x61, 0x3d, 0x8c, 0x54, 0xf5, 0xa8, 0x1a, 0x79, 0x5f, 0x87, 0x62, + 0x79, 0x1d, 0xd7, 0x8b, 0x59, 0x98, 0xb2, 0x33, 0xeb, 0x55, 0x34, 0xef, 0x8d, 0x92, 0xa8, 0x28, 0x80, 0x5f, 0x97, + 0x84, 0x42, 0xbe, 0x18, 0x01, 0x83, 0x10, 0x82, 0x4b, 0x24, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x1b, 0xa3, 0xa2, 0x78, + 0xc7, 0x8a, 0x45, 0xc2, 0x37, 0x42, 0x58, 0x0b, 0x7e, 0x27, 0x0c, 0xbf, 0x76, 0xf9, 0x34, 0xcf, 0xce, 0xac, 0x67, + 0x79, 0x0e, 0xcd, 0x6d, 0x98, 0x52, 0x34, 0xb0, 0xe2, 0xc2, 0x4a, 0x33, 0x6e, 0xe9, 0xc1, 0x70, 0x01, 0x7d, 0xeb, + 0x7d, 0xc1, 0xac, 0xa3, 0x45, 0x5a, 0x44, 0x13, 0x06, 0x4d, 0x8f, 0xac, 0x2c, 0xb7, 0x8e, 0x60, 0xd0, 0x23, 0x58, + 0xb2, 0x82, 0xc3, 0xae, 0xf1, 0x6d, 0xb7, 0x47, 0x73, 0x41, 0xe1, 0x01, 0x3b, 0xe7, 0x21, 0x2b, 0x81, 0x31, 0xad, + 0x42, 0xa3, 0xe1, 0xb8, 0xcb, 0x04, 0x0a, 0x58, 0x18, 0x33, 0x64, 0x59, 0xc7, 0x6c, 0xac, 0x17, 0xe7, 0xc3, 0xdd, + 0xbb, 0x9a, 0xd6, 0x40, 0x13, 0x07, 0xda, 0x16, 0x8d, 0xb6, 0x9e, 0x40, 0xbc, 0x46, 0x22, 0xd7, 0x63, 0xbe, 0x24, + 0xdf, 0xfe, 0x45, 0x3a, 0xaa, 0x8f, 0x0d, 0x95, 0x25, 0xcf, 0xf6, 0x79, 0x1e, 0xa7, 0x27, 0x00, 0x84, 0x9c, 0xc9, + 0x6c, 0x52, 0x96, 0x62, 0xf1, 0xdf, 0xb0, 0x90, 0x85, 0x7d, 0x1c, 0x3d, 0x67, 0x8e, 0x5d, 0x50, 0x0f, 0x3b, 0x0c, + 0x91, 0xf4, 0xc0, 0x60, 0x6c, 0xc0, 0x02, 0xb6, 0x69, 0xdb, 0xde, 0xd7, 0xae, 0x77, 0x81, 0x1c, 0xe4, 0xfb, 0x3e, + 0xb1, 0xaf, 0xe8, 0x1c, 0x87, 0x1d, 0x04, 0xda, 0x4f, 0x58, 0x7a, 0xc2, 0xa7, 0x03, 0x76, 0xd8, 0x1e, 0x06, 0x1c, + 0xa0, 0x1a, 0x2f, 0x46, 0xcc, 0x41, 0x7e, 0xf4, 0x72, 0xdc, 0x3e, 0x9b, 0x0e, 0x4c, 0x81, 0x0b, 0x73, 0x87, 0x70, + 0xac, 0x2d, 0x8d, 0xab, 0x58, 0x54, 0x01, 0x86, 0x7c, 0x6e, 0xc3, 0x0e, 0x3b, 0x66, 0xb9, 0x01, 0x87, 0x6e, 0xd6, + 0xab, 0xad, 0xe0, 0x02, 0x56, 0x08, 0xfa, 0x59, 0x93, 0x45, 0x3a, 0xe2, 0x31, 0x08, 0x2e, 0x7b, 0x13, 0xc0, 0x15, + 0x2b, 0xa7, 0x17, 0xce, 0x76, 0x4b, 0xd7, 0x89, 0xdd, 0x4d, 0x76, 0x98, 0x6f, 0x76, 0x86, 0x1e, 0x42, 0xa9, 0x89, + 0x2f, 0x11, 0x8f, 0x01, 0xc1, 0xd2, 0x7b, 0xcb, 0xf4, 0xf6, 0xfc, 0x30, 0x60, 0xfe, 0x2a, 0x1f, 0x87, 0xdc, 0x9f, + 0x45, 0x73, 0xc4, 0x86, 0x11, 0x0f, 0x44, 0xe9, 0x08, 0xa1, 0xab, 0xad, 0x0b, 0x52, 0xcc, 0xaf, 0x58, 0xc0, 0x05, + 0x82, 0xc0, 0x9e, 0x7d, 0x16, 0x8d, 0xa6, 0xb0, 0xc5, 0x2b, 0xc2, 0x8d, 0xd5, 0x76, 0x18, 0xe5, 0x2c, 0xe2, 0xec, + 0x59, 0xc2, 0xf0, 0x0d, 0x57, 0x00, 0x7a, 0xda, 0xae, 0x97, 0xab, 0x7d, 0x97, 0xc4, 0xfc, 0x75, 0x06, 0xf3, 0xf4, + 0x04, 0x93, 0x00, 0x17, 0xe7, 0x77, 0xef, 0xc6, 0xc8, 0x22, 0x7b, 0x1c, 0x56, 0xeb, 0x78, 0x01, 0x42, 0xc0, 0x4e, + 0xb1, 0x85, 0x0d, 0xd4, 0xf6, 0x62, 0x9f, 0x03, 0x11, 0x9f, 0x64, 0x29, 0x87, 0xe1, 0x00, 0x5e, 0xcd, 0x41, 0x7e, + 0x34, 0x9f, 0xb3, 0x74, 0xfc, 0x64, 0x1a, 0x27, 0x63, 0xa0, 0x46, 0x09, 0xf8, 0x66, 0x2c, 0x04, 0x3c, 0x01, 0x99, + 0xe0, 0x7a, 0x8c, 0x68, 0xf9, 0x90, 0x91, 0x79, 0x68, 0xdb, 0x3d, 0x94, 0x40, 0x12, 0x0b, 0x94, 0x41, 0xb4, 0x70, + 0xef, 0x40, 0xf4, 0x17, 0x2e, 0xdf, 0x0c, 0x63, 0xbd, 0x8c, 0x92, 0xc0, 0x6f, 0x50, 0xd2, 0x00, 0xfd, 0x19, 0xc8, + 0xc0, 0x1e, 0x0a, 0xae, 0xef, 0xa5, 0xd4, 0x49, 0x99, 0xc2, 0x10, 0x08, 0x30, 0x42, 0x09, 0x22, 0x69, 0xf0, 0x36, + 0x4b, 0x2e, 0x26, 0x71, 0x92, 0xec, 0x2f, 0xe6, 0xf3, 0x2c, 0xe7, 0xde, 0x37, 0xe1, 0x92, 0x67, 0x15, 0xae, 0xb4, + 0xc9, 0x8b, 0xb3, 0x98, 0x23, 0x41, 0xdd, 0xe5, 0x28, 0x82, 0xa5, 0x7e, 0x9c, 0x65, 0x09, 0x8b, 0x52, 0x40, 0x83, + 0x0d, 0x6c, 0x3b, 0x48, 0x17, 0x49, 0xd2, 0x3b, 0x86, 0x61, 0x3f, 0xf5, 0xa8, 0x5a, 0x48, 0xfc, 0x80, 0x9e, 0xf7, + 0xf2, 0x3c, 0xba, 0x80, 0x86, 0xd8, 0x06, 0x78, 0x11, 0x56, 0xeb, 0x9b, 0xfd, 0x37, 0xaf, 0x7d, 0xc1, 0xf8, 0xf1, + 0xe4, 0x02, 0x00, 0x2d, 0x2b, 0xa9, 0x39, 0xc9, 0xb3, 0x59, 0x63, 0x6a, 0xa4, 0x43, 0x1c, 0xb2, 0xde, 0x15, 0x20, + 0xc4, 0x34, 0x32, 0xac, 0x12, 0x33, 0x21, 0x78, 0x4d, 0xfc, 0x2c, 0x2b, 0x71, 0x0f, 0x0c, 0xf0, 0x21, 0x10, 0xc5, + 0x30, 0xe5, 0xf5, 0xd0, 0xf2, 0xfc, 0x62, 0x19, 0x87, 0x04, 0xe7, 0x1c, 0xf5, 0x2f, 0xc2, 0x38, 0x8a, 0x60, 0xf6, + 0xa5, 0x18, 0xb0, 0x54, 0x10, 0xc7, 0x65, 0xe9, 0x25, 0x9a, 0x89, 0x51, 0xe2, 0xa1, 0x40, 0xe1, 0xb0, 0x8d, 0x2e, + 0x2f, 0x19, 0xbc, 0xb8, 0xde, 0xb7, 0xe1, 0x32, 0x52, 0xf8, 0xa0, 0x86, 0xc2, 0xfd, 0x15, 0x08, 0x39, 0x81, 0x9a, + 0xec, 0x14, 0xf4, 0x20, 0xc0, 0xf9, 0x8d, 0x07, 0xfa, 0x3f, 0x41, 0x28, 0xee, 0x74, 0x3c, 0xd0, 0xa0, 0x4f, 0xa6, + 0x51, 0x7a, 0xc2, 0xc6, 0x41, 0xc2, 0x4a, 0x29, 0x79, 0xf7, 0x2c, 0x58, 0x63, 0x60, 0xa7, 0xc2, 0x7a, 0x7e, 0xf0, + 0xea, 0xa5, 0x5c, 0xb9, 0x9a, 0x30, 0x86, 0x45, 0x5a, 0x80, 0x5a, 0x05, 0xb1, 0x2d, 0xc5, 0xf1, 0x33, 0xae, 0xa4, + 0xb7, 0x28, 0x89, 0x8b, 0xf7, 0x73, 0x30, 0x31, 0xd8, 0x5b, 0x18, 0x06, 0xa6, 0x0f, 0x61, 0x2a, 0x2a, 0x87, 0xf9, + 0x44, 0xc5, 0x58, 0x17, 0x41, 0x67, 0x81, 0xa9, 0x78, 0xcd, 0x1c, 0xb7, 0x04, 0x56, 0xe5, 0xf1, 0xc8, 0x8a, 0xc6, + 0xe3, 0x17, 0x69, 0xcc, 0xe3, 0x28, 0x89, 0x7f, 0x23, 0x4a, 0x2e, 0x91, 0xc7, 0x78, 0x4f, 0x2e, 0x02, 0xe0, 0x4e, + 0x3d, 0x12, 0x57, 0x09, 0xd9, 0x3b, 0x44, 0x0c, 0x21, 0x2d, 0x93, 0xf0, 0x70, 0x28, 0xc1, 0x4b, 0xfc, 0xf9, 0xa2, + 0x98, 0x22, 0x61, 0xe5, 0xc0, 0x28, 0xc8, 0xb3, 0xe3, 0x82, 0xe5, 0xa7, 0x6c, 0xac, 0x39, 0xa0, 0x00, 0xac, 0xa8, + 0x39, 0x18, 0x2f, 0x34, 0xa3, 0xa3, 0x74, 0x28, 0x83, 0xa1, 0x7a, 0xa6, 0x98, 0x65, 0x92, 0x99, 0xb5, 0x85, 0xa3, + 0xa5, 0x80, 0x23, 0x8c, 0x0a, 0x29, 0x09, 0xf2, 0x50, 0x61, 0x38, 0x05, 0x29, 0x04, 0x5a, 0xc1, 0xdc, 0xe6, 0x4a, + 0x93, 0x3d, 0x5b, 0x90, 0x4a, 0xc8, 0xa1, 0x23, 0x6c, 0x64, 0x82, 0x34, 0x77, 0x61, 0x57, 0x81, 0x94, 0x97, 0xe0, + 0x0a, 0x29, 0xa2, 0xcc, 0x1c, 0x64, 0x80, 0xf0, 0x5b, 0xa1, 0x0b, 0x7d, 0x6c, 0x41, 0x6c, 0xe0, 0xeb, 0x95, 0x07, + 0xc2, 0x4a, 0xbc, 0x2b, 0x44, 0xbc, 0x2b, 0xc0, 0xc6, 0x89, 0x91, 0x9f, 0xbc, 0x3b, 0xdc, 0x4f, 0xb3, 0xbd, 0xd1, + 0x88, 0x15, 0x45, 0x06, 0xb0, 0xdd, 0xa1, 0xf6, 0x57, 0x19, 0x5a, 0x40, 0x49, 0x57, 0xcb, 0x3a, 0xbb, 0x20, 0x0d, + 0x6e, 0xaa, 0x15, 0xa5, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0x90, 0xd9, 0x9e, 0x24, 0x03, 0x50, 0x7d, 0xd5, 0xf0, 0x13, + 0xf6, 0x4c, 0x9d, 0x32, 0x6b, 0xed, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa0, 0x6b, 0x87, 0xc6, + 0xd5, 0x90, 0x0a, 0x72, 0x79, 0x42, 0x2a, 0xdb, 0x58, 0x46, 0xb0, 0xda, 0x4a, 0x8f, 0x48, 0xaf, 0xb0, 0x29, 0x08, + 0xd0, 0x43, 0x36, 0xec, 0xc9, 0xfa, 0x30, 0x17, 0x94, 0xcb, 0xd9, 0xaf, 0x0b, 0x56, 0x70, 0xc1, 0xba, 0x30, 0x6e, + 0x01, 0xe3, 0x96, 0x2b, 0xd6, 0x61, 0xcd, 0x76, 0x5c, 0x07, 0xdb, 0x9b, 0x39, 0xea, 0xb1, 0x02, 0x39, 0xf9, 0x7a, + 0x76, 0x42, 0x58, 0x99, 0x7b, 0x79, 0xf9, 0xad, 0x1a, 0xa4, 0x5a, 0x4a, 0x6d, 0x03, 0x35, 0xd6, 0xc4, 0x56, 0x4d, + 0xc6, 0xb6, 0x2b, 0x15, 0xea, 0x9d, 0x4e, 0xaf, 0xc6, 0x07, 0xb0, 0xe7, 0xda, 0x9a, 0xa5, 0x2b, 0x63, 0xfb, 0xad, + 0xa2, 0xe9, 0x1b, 0x31, 0x32, 0x59, 0xa3, 0xec, 0x66, 0xee, 0x51, 0x3b, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x82, 0x61, + 0x51, 0x17, 0x0c, 0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x99, 0x8a, 0x85, 0x6a, 0xad, 0x95, 0x03, 0xc1, 0xc3, 0x43, + 0x30, 0x4e, 0xd6, 0xfa, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8a, 0x7a, 0x57, 0x35, 0x90, 0x0e, 0x04, 0x34, 0x19, 0x36, + 0xd5, 0x1b, 0x77, 0x85, 0xd5, 0x54, 0xdf, 0x5f, 0x31, 0x58, 0x11, 0x60, 0x5f, 0x97, 0x6b, 0x96, 0x88, 0xf4, 0xa6, + 0xe0, 0x12, 0x4d, 0x1f, 0x51, 0x26, 0xd6, 0x84, 0x14, 0x3c, 0x20, 0x0f, 0xcb, 0xdf, 0x58, 0x38, 0xd9, 0x8a, 0x29, + 0x1c, 0x39, 0xca, 0x14, 0xa0, 0x33, 0x29, 0x01, 0x10, 0x97, 0xf4, 0xb3, 0xb6, 0xb1, 0x90, 0x6c, 0xfb, 0xc8, 0x07, + 0xfe, 0x24, 0x89, 0xb8, 0xd3, 0xd9, 0x6a, 0xbb, 0xc0, 0x87, 0x20, 0xc4, 0x41, 0x47, 0x80, 0x79, 0x5f, 0xa1, 0xc2, + 0x10, 0x95, 0xd8, 0xe5, 0x3e, 0x18, 0x45, 0xd3, 0x78, 0xc2, 0x9d, 0x0c, 0x95, 0x88, 0x5b, 0xb2, 0x04, 0x94, 0x8c, + 0xde, 0x57, 0x20, 0x25, 0xb8, 0x90, 0x2e, 0xa2, 0x5a, 0x0b, 0x34, 0x05, 0x29, 0x49, 0x29, 0xd2, 0x82, 0x0a, 0x02, + 0x43, 0xa8, 0xf4, 0x14, 0x47, 0x81, 0x7e, 0x8b, 0x07, 0x62, 0xd0, 0x60, 0xc5, 0xa2, 0x8c, 0x07, 0xf1, 0x6a, 0x21, + 0xa8, 0x61, 0x9f, 0x67, 0x2f, 0xb3, 0x33, 0x96, 0x3f, 0x89, 0x10, 0xf6, 0x40, 0x74, 0x2f, 0x41, 0xd2, 0x93, 0x40, + 0x67, 0x3d, 0xc5, 0x2b, 0xa7, 0x84, 0x34, 0x2c, 0xc4, 0x2c, 0x46, 0x45, 0x08, 0x5a, 0x8e, 0x68, 0x9f, 0xe2, 0x96, + 0xa2, 0xbd, 0x87, 0xaa, 0x84, 0x69, 0xde, 0xda, 0x7b, 0x59, 0xe7, 0x2d, 0x18, 0x61, 0xae, 0xb8, 0xb5, 0xbe, 0x63, + 0x5d, 0x4f, 0xea, 0x66, 0x47, 0xf2, 0x96, 0xa1, 0xcc, 0x40, 0x7f, 0x5c, 0x5e, 0x56, 0x46, 0x3a, 0x28, 0x53, 0x2d, + 0xcd, 0xd1, 0x72, 0x12, 0x5b, 0xc2, 0x2d, 0x41, 0x19, 0xa1, 0xe1, 0x95, 0x67, 0x49, 0x62, 0xe8, 0x22, 0x2f, 0xee, + 0x39, 0x0d, 0x75, 0x04, 0x50, 0xcc, 0x6a, 0x1a, 0x69, 0xc0, 0x03, 0x5d, 0x81, 0x4a, 0x49, 0x69, 0x23, 0xaf, 0x6a, + 0x22, 0x20, 0x4e, 0xc7, 0x2c, 0x17, 0x0e, 0x9a, 0xd4, 0xa1, 0x30, 0x61, 0x0a, 0x0c, 0xcd, 0xc6, 0x20, 0xe1, 0x15, + 0x02, 0x60, 0x9e, 0xf8, 0xd3, 0xac, 0xe0, 0xba, 0xce, 0x84, 0x3e, 0xbe, 0xbc, 0x8c, 0x85, 0xbf, 0x88, 0x0c, 0x90, + 0xb3, 0x59, 0x76, 0xca, 0xd6, 0x40, 0xdd, 0x53, 0x83, 0x99, 0x20, 0x1b, 0xc3, 0x80, 0x12, 0x05, 0xd5, 0x32, 0x4f, + 0xe2, 0x11, 0xd3, 0x5a, 0x6a, 0xe6, 0x83, 0x41, 0xc7, 0xce, 0x41, 0x46, 0x30, 0xb7, 0xdf, 0xef, 0xb7, 0xbd, 0x8e, + 0x5b, 0x0a, 0x82, 0x2f, 0x57, 0x28, 0x7a, 0x8d, 0x7e, 0x94, 0x26, 0xf8, 0x3a, 0x59, 0xc0, 0x5d, 0x43, 0x29, 0x72, + 0xe1, 0x27, 0x79, 0x52, 0x10, 0xbb, 0xde, 0x18, 0x06, 0xe5, 0x4c, 0x09, 0x6e, 0x34, 0x71, 0xc5, 0xb6, 0x7d, 0xa7, + 0xc9, 0xa6, 0xd9, 0x49, 0xed, 0x30, 0xb5, 0x30, 0x72, 0xcd, 0x0b, 0xed, 0x01, 0x9b, 0xcb, 0x83, 0x56, 0x22, 0x55, + 0x03, 0xaf, 0x03, 0x84, 0xc2, 0xd3, 0x75, 0x56, 0x50, 0xaa, 0x3a, 0x4b, 0x21, 0xae, 0x37, 0xd0, 0x5b, 0x26, 0xc1, + 0x5c, 0x47, 0x82, 0x7d, 0x29, 0x10, 0x38, 0x7a, 0x64, 0x62, 0xbd, 0x9e, 0xc0, 0xf2, 0x1c, 0x47, 0xa3, 0x4f, 0x1a, + 0xdc, 0x8a, 0xec, 0x4d, 0x36, 0x70, 0x1a, 0x25, 0xa1, 0x21, 0xae, 0x4c, 0xbc, 0x95, 0x84, 0xae, 0x6d, 0x14, 0x70, + 0xc8, 0x56, 0xd8, 0xbe, 0xb9, 0xd0, 0x4d, 0x6e, 0x97, 0xec, 0xa1, 0xfc, 0x27, 0xcd, 0x25, 0xd7, 0xb0, 0x1c, 0x57, + 0xd2, 0x80, 0x2b, 0xc6, 0x83, 0xa5, 0x69, 0x40, 0x02, 0x7c, 0x57, 0x8e, 0xe3, 0xe2, 0x6a, 0x12, 0xfc, 0xa1, 0x60, + 0x3e, 0x35, 0x66, 0xba, 0x11, 0x52, 0x2d, 0xe1, 0xa4, 0x19, 0xac, 0x41, 0x93, 0xc6, 0x83, 0x12, 0x35, 0xdf, 0xa2, + 0xa1, 0x42, 0x1c, 0x7f, 0x22, 0xaa, 0xd0, 0x04, 0x43, 0x30, 0x72, 0xaf, 0x90, 0x0c, 0x97, 0xad, 0x8a, 0x16, 0x29, + 0x53, 0x63, 0x52, 0xa9, 0x9a, 0xe5, 0x32, 0x30, 0xb0, 0x68, 0xb7, 0xfa, 0xd2, 0x12, 0x57, 0x22, 0x37, 0x0d, 0xb5, + 0x30, 0x29, 0x94, 0x37, 0xe1, 0xe4, 0xe8, 0x77, 0x29, 0xeb, 0xdd, 0xc4, 0x27, 0x57, 0xf8, 0xe4, 0xbe, 0xe1, 0x43, + 0x99, 0xbc, 0x5d, 0x0c, 0x8a, 0xe0, 0x9b, 0x5a, 0x25, 0xda, 0xa7, 0x3e, 0x0a, 0x66, 0x57, 0x0b, 0x5d, 0x10, 0x28, + 0x92, 0x4d, 0xd2, 0x81, 0xe4, 0x37, 0x14, 0x1b, 0x95, 0x67, 0x94, 0xb9, 0x62, 0x83, 0xd4, 0xbc, 0xd2, 0xcc, 0x4b, + 0xdd, 0x86, 0xfd, 0x5e, 0x96, 0x92, 0x4e, 0x5c, 0x50, 0x26, 0xf6, 0xae, 0xa3, 0x8d, 0x97, 0x86, 0x99, 0xb0, 0x7e, + 0x85, 0xb1, 0x53, 0xa3, 0x50, 0x2a, 0x45, 0x20, 0x8e, 0x8d, 0xaf, 0x95, 0x65, 0x90, 0xf9, 0x6b, 0xec, 0x29, 0x00, + 0x25, 0x81, 0xc5, 0xd7, 0x54, 0xf2, 0xa2, 0xb0, 0x4e, 0xc7, 0x3b, 0x44, 0xc7, 0x4a, 0x84, 0xd6, 0x44, 0xbe, 0xd6, + 0x67, 0xb1, 0x5f, 0x73, 0x09, 0x4d, 0x4a, 0xe6, 0x83, 0x3c, 0xb0, 0x55, 0x20, 0xa2, 0xd2, 0x6d, 0xc9, 0x20, 0x21, + 0x87, 0x74, 0x95, 0xe8, 0xb5, 0x91, 0x0c, 0x5a, 0xa7, 0x42, 0xa2, 0xa5, 0xc3, 0x30, 0x72, 0xd0, 0x71, 0xa7, 0xb5, + 0x58, 0x21, 0x64, 0xd3, 0xde, 0x24, 0x56, 0x44, 0xe7, 0x34, 0x47, 0x13, 0xce, 0xd4, 0xe9, 0x8e, 0x03, 0xe8, 0x80, + 0xd8, 0x5f, 0x61, 0xbd, 0xb5, 0x66, 0xa7, 0xeb, 0x57, 0x0e, 0xdf, 0xe5, 0x65, 0x82, 0xfc, 0x20, 0x0c, 0x5e, 0x58, + 0xb3, 0x81, 0x92, 0xbd, 0x7b, 0x2f, 0xb1, 0x15, 0xd9, 0x9f, 0x55, 0x49, 0xe5, 0x29, 0xd4, 0x38, 0xb7, 0xbe, 0x4e, + 0xcc, 0x0c, 0x2d, 0xaa, 0x8a, 0x7d, 0x43, 0xaa, 0xef, 0x2b, 0x85, 0x5d, 0xa1, 0xbc, 0x2f, 0x87, 0x8e, 0x5d, 0xd7, + 0x0d, 0x72, 0x72, 0x5e, 0xee, 0xac, 0x73, 0x21, 0xef, 0xde, 0x35, 0x7d, 0xa6, 0x53, 0x3d, 0xfc, 0x13, 0x07, 0x95, + 0x73, 0x71, 0x91, 0x92, 0x05, 0xf3, 0x44, 0xa9, 0xa3, 0x15, 0x07, 0xb4, 0xdd, 0x43, 0x4f, 0x3b, 0x3a, 0x8b, 0x62, + 0x6e, 0xe9, 0x51, 0x84, 0xa7, 0x8d, 0xf2, 0x49, 0x1a, 0x1d, 0x80, 0x17, 0x9a, 0x90, 0xe4, 0x84, 0x9b, 0xb6, 0x68, + 0x31, 0x9a, 0x32, 0x0c, 0x81, 0x2b, 0x7b, 0xc2, 0x94, 0x3d, 0x77, 0x10, 0x6f, 0x31, 0x30, 0x5b, 0x0f, 0x7b, 0xd9, + 0xec, 0x5e, 0x33, 0xff, 0x61, 0x8d, 0x40, 0xb6, 0xcd, 0x54, 0x5d, 0xd9, 0x78, 0x97, 0x22, 0x12, 0x23, 0x6c, 0xeb, + 0xc6, 0x96, 0xb6, 0x7e, 0xaf, 0xe1, 0x5e, 0x57, 0x8e, 0x79, 0x4d, 0xa9, 0x36, 0xf4, 0xb0, 0x72, 0x73, 0x98, 0xe9, + 0xc8, 0x8b, 0x15, 0x74, 0x7b, 0x22, 0x28, 0x04, 0x4e, 0x84, 0xb6, 0x07, 0x15, 0x37, 0x10, 0x29, 0xb9, 0xd2, 0xaa, + 0xd9, 0x22, 0x19, 0x4b, 0x60, 0xc1, 0x85, 0xe5, 0x92, 0x8f, 0xce, 0xe2, 0x24, 0xa9, 0x4a, 0xff, 0x50, 0x01, 0x2f, + 0x86, 0xbd, 0x49, 0xb4, 0x0b, 0x8c, 0x16, 0x0a, 0x04, 0x57, 0x1b, 0x61, 0xef, 0x1d, 0xb7, 0x5a, 0x77, 0x11, 0x71, + 0xe4, 0x66, 0x34, 0x02, 0xea, 0x31, 0xc2, 0xaa, 0x59, 0x7b, 0xef, 0x19, 0x86, 0xd4, 0x0c, 0x7c, 0x50, 0x9d, 0x51, + 0xf1, 0x67, 0xd9, 0x53, 0x9f, 0x89, 0xde, 0x8d, 0xaa, 0xab, 0x19, 0x50, 0x51, 0x81, 0x0f, 0x33, 0xc4, 0xd2, 0x56, + 0x81, 0x80, 0x5c, 0x0f, 0x8b, 0x52, 0xc0, 0x24, 0x0d, 0x16, 0x94, 0x02, 0x6b, 0xad, 0xec, 0x5e, 0xde, 0x14, 0xcc, + 0xa1, 0x50, 0xb8, 0xe8, 0xff, 0x24, 0x9b, 0xcd, 0xd1, 0x32, 0x6b, 0x30, 0x35, 0x34, 0x78, 0xdf, 0xa8, 0x2f, 0xd7, + 0x94, 0xd5, 0xfa, 0xd0, 0x8e, 0xac, 0xf1, 0x93, 0x76, 0x94, 0xc1, 0xa1, 0x5a, 0xe8, 0xa2, 0xba, 0xdd, 0xdc, 0x14, + 0x31, 0xeb, 0x78, 0xdc, 0x27, 0xbd, 0xad, 0xad, 0x49, 0x4f, 0xd3, 0x80, 0x64, 0x92, 0x64, 0x78, 0x93, 0x01, 0xca, + 0x8a, 0x38, 0xcb, 0xb2, 0x41, 0xbe, 0x65, 0x59, 0xe2, 0xfa, 0x7d, 0xd7, 0xdb, 0xab, 0x79, 0xd6, 0xde, 0xde, 0xd5, + 0x2e, 0x72, 0x55, 0x27, 0x3d, 0xc8, 0xc3, 0x21, 0x14, 0xad, 0xd8, 0x94, 0xe1, 0x72, 0x96, 0x8d, 0x59, 0x60, 0x43, + 0xf7, 0xd4, 0x2e, 0x95, 0x56, 0x86, 0xcd, 0x91, 0x32, 0x67, 0xf9, 0xae, 0x1e, 0x49, 0x0d, 0xf6, 0x80, 0x05, 0xb4, + 0xb9, 0xf0, 0x7d, 0x78, 0x92, 0x64, 0xc7, 0x51, 0x72, 0x20, 0x14, 0x78, 0xad, 0xe5, 0x07, 0x70, 0x19, 0xc9, 0x62, + 0x35, 0x94, 0xd4, 0xf7, 0x83, 0xef, 0x83, 0x9b, 0x7b, 0x54, 0xde, 0x8a, 0xdd, 0xf1, 0xdb, 0x7e, 0xc7, 0x56, 0x11, + 0xb1, 0x97, 0xe6, 0x74, 0xa0, 0x71, 0x0a, 0xa0, 0xcc, 0x01, 0x68, 0xb2, 0xc2, 0x8b, 0x58, 0xf8, 0x72, 0xf0, 0x52, + 0xb9, 0xd4, 0x19, 0xb8, 0x10, 0xe0, 0xe4, 0x27, 0x31, 0x6f, 0xe1, 0x79, 0xa4, 0xed, 0x2d, 0x45, 0x05, 0xc6, 0x15, + 0x29, 0x2e, 0x5d, 0x2a, 0x6f, 0xd0, 0xfb, 0x18, 0x1e, 0x41, 0xb3, 0x8d, 0x8d, 0xa5, 0xf3, 0x2a, 0xe2, 0x53, 0x3f, + 0x8f, 0xd2, 0x71, 0x36, 0x73, 0xdc, 0x4d, 0xdb, 0x76, 0xfd, 0x82, 0x3c, 0x91, 0xaf, 0xdc, 0x72, 0xe3, 0xc8, 0x9b, + 0xb2, 0xd0, 0x1e, 0xd8, 0x9b, 0x1f, 0xbd, 0xf7, 0x2c, 0x3c, 0xda, 0xdd, 0x58, 0x4e, 0x59, 0xd9, 0x3f, 0xf2, 0xce, + 0x75, 0xcc, 0xdd, 0x7b, 0x8b, 0x52, 0x06, 0x7a, 0x85, 0xfd, 0x73, 0x09, 0x06, 0xb0, 0x1b, 0xc5, 0xdf, 0x41, 0xca, + 0xbd, 0xa7, 0x03, 0x11, 0x19, 0xa7, 0xbd, 0xbc, 0xb4, 0x33, 0x8a, 0x18, 0xd8, 0x77, 0xb4, 0xb3, 0x7a, 0xf7, 0x6e, + 0xa5, 0xe6, 0xab, 0x52, 0x6f, 0xc4, 0xc2, 0x9a, 0xa7, 0xee, 0x1d, 0xd0, 0xd1, 0x4a, 0x7d, 0x23, 0x8f, 0x18, 0x29, + 0xcd, 0x55, 0x3b, 0xc1, 0x31, 0xb6, 0xf8, 0xfa, 0x6d, 0x7d, 0x28, 0xa2, 0x14, 0x7e, 0x0c, 0xd6, 0x4b, 0x04, 0xea, + 0x1b, 0x1c, 0x1c, 0xef, 0x20, 0xdc, 0xda, 0x75, 0x06, 0x81, 0x73, 0xa7, 0xd5, 0xba, 0xfc, 0x69, 0xeb, 0xf0, 0xe7, + 0xa8, 0xf5, 0xdb, 0x5e, 0xeb, 0xc7, 0xa1, 0x7b, 0xe9, 0xfc, 0xb4, 0x35, 0x38, 0x94, 0x6f, 0x87, 0x3f, 0xf7, 0x7f, + 0x2a, 0x86, 0x5f, 0x8a, 0xc2, 0x0d, 0xd7, 0xdd, 0x3a, 0x01, 0x4f, 0x29, 0xdc, 0x6a, 0xb5, 0xfa, 0xf0, 0xb4, 0x80, + 0x27, 0xfc, 0x79, 0x06, 0x3f, 0x2e, 0x0f, 0xad, 0xff, 0xf0, 0x53, 0xfa, 0x1f, 0x7f, 0xca, 0x87, 0x38, 0xe6, 0xe1, + 0xcf, 0x3f, 0x15, 0xf6, 0xbd, 0x7e, 0xb8, 0x35, 0xdc, 0x74, 0x1d, 0x5d, 0xf3, 0x65, 0x58, 0x3d, 0x42, 0xab, 0xc3, + 0x9f, 0xe5, 0x9b, 0x7d, 0xef, 0x68, 0xb7, 0x1f, 0x0e, 0x2f, 0x1d, 0xfb, 0xf2, 0x9e, 0x7b, 0xe9, 0xba, 0x97, 0x1b, + 0x38, 0xcf, 0x1c, 0x46, 0xbf, 0x07, 0x3f, 0x4f, 0xe0, 0xa7, 0x0d, 0x3f, 0x4f, 0xe1, 0xe7, 0xcf, 0xd0, 0x4d, 0xc4, + 0xdf, 0x2e, 0x29, 0x16, 0x72, 0x89, 0x07, 0x16, 0x11, 0xac, 0x82, 0xbb, 0xb1, 0x15, 0x7b, 0x13, 0x22, 0x1a, 0xec, + 0x43, 0xdf, 0xf7, 0x31, 0x4c, 0xea, 0x2c, 0x3f, 0x6e, 0xc0, 0xa2, 0x23, 0xe7, 0x6c, 0x04, 0xcc, 0x13, 0x91, 0x83, + 0x22, 0xe0, 0xe2, 0x6c, 0xb5, 0xc0, 0xc3, 0x55, 0x6f, 0x1c, 0x4e, 0x98, 0x03, 0x46, 0xc1, 0x73, 0x86, 0x0f, 0x5d, + 0xd7, 0x7b, 0x26, 0xcf, 0x0c, 0x71, 0x9f, 0x0b, 0xd6, 0x4a, 0x33, 0x61, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, + 0xdb, 0x3a, 0x3d, 0x81, 0xba, 0x0d, 0x71, 0xd0, 0xf6, 0x3d, 0x8b, 0x3e, 0xe1, 0x96, 0x7c, 0x6d, 0x1c, 0x02, 0xaf, + 0x58, 0xf2, 0x4d, 0xa3, 0xd1, 0xb0, 0x11, 0x85, 0x3b, 0xf6, 0x98, 0xc1, 0x0c, 0x2b, 0x26, 0x22, 0x27, 0xa5, 0x29, + 0x2c, 0x5b, 0x98, 0xfc, 0x6d, 0x94, 0xf3, 0x8d, 0xca, 0xb0, 0x0d, 0x6b, 0x96, 0x6c, 0xd3, 0xd2, 0xbf, 0xc5, 0x14, + 0x68, 0x5a, 0xd2, 0xf9, 0x87, 0x39, 0x7e, 0x98, 0x12, 0x5a, 0xaf, 0x1d, 0x0e, 0x1e, 0x7a, 0x01, 0x72, 0x47, 0xf4, + 0x73, 0xde, 0xa2, 0x1a, 0x83, 0xbf, 0x32, 0xcc, 0xe0, 0x89, 0xf9, 0x30, 0x44, 0xb3, 0x2c, 0x75, 0x70, 0x2b, 0x45, + 0x71, 0xff, 0x02, 0x77, 0x46, 0x5a, 0x7a, 0xfb, 0xa1, 0xda, 0x31, 0x07, 0x39, 0x63, 0xdf, 0x47, 0xc9, 0x27, 0x96, + 0x3b, 0xe7, 0x5e, 0xa7, 0xfb, 0x15, 0x75, 0xf6, 0xd0, 0x36, 0x7b, 0x55, 0x1d, 0xa3, 0x29, 0xb3, 0x40, 0x1d, 0x11, + 0xb6, 0x3a, 0x5e, 0x8e, 0x51, 0x2d, 0x24, 0x41, 0xe1, 0x65, 0x61, 0x97, 0x38, 0xdc, 0xde, 0x2d, 0x4e, 0x4f, 0xfa, + 0x76, 0x60, 0xdb, 0x60, 0xf1, 0x1f, 0x50, 0xd8, 0x4a, 0x18, 0x16, 0x60, 0x90, 0xed, 0xc6, 0x3d, 0xbe, 0xb9, 0x59, + 0x05, 0x9c, 0xf0, 0x20, 0x9d, 0xba, 0x27, 0x5e, 0xe4, 0x4d, 0x43, 0x18, 0x70, 0x04, 0xcd, 0xb0, 0x4b, 0x6f, 0xb4, + 0x1b, 0xcb, 0x69, 0x30, 0x16, 0xe2, 0x27, 0x51, 0xc1, 0x5f, 0x60, 0x3c, 0x22, 0x1c, 0xa1, 0xb1, 0xef, 0xb3, 0x73, + 0x36, 0x52, 0x76, 0x06, 0x10, 0x2a, 0x72, 0x7b, 0xee, 0x28, 0x34, 0x9a, 0xc1, 0xdc, 0x61, 0x78, 0x30, 0xb0, 0x61, + 0x2f, 0xc1, 0xae, 0x0c, 0xa3, 0xc3, 0xce, 0x70, 0x90, 0x86, 0x20, 0x6b, 0x35, 0x6d, 0x65, 0xd1, 0xa2, 0x56, 0xd4, + 0x1d, 0x0e, 0x9c, 0x53, 0x30, 0xd2, 0xc1, 0x16, 0x77, 0xf0, 0x0d, 0x23, 0x14, 0x45, 0xf8, 0x8e, 0x9d, 0x3c, 0x3b, + 0x9f, 0x3b, 0xf6, 0xee, 0x96, 0xbd, 0x89, 0xa5, 0x9e, 0x0d, 0xec, 0x05, 0x73, 0x87, 0x67, 0xae, 0xd9, 0x79, 0x7b, + 0x88, 0xa0, 0x62, 0x21, 0x4e, 0x7e, 0x36, 0xb0, 0xfb, 0x62, 0xea, 0x36, 0x0c, 0x9a, 0xca, 0xe5, 0xc7, 0x15, 0x3d, + 0x20, 0x54, 0x55, 0x57, 0x05, 0x1d, 0x94, 0x75, 0x03, 0x67, 0x6a, 0x22, 0xd1, 0xc2, 0xc9, 0x24, 0x15, 0xc0, 0xe1, + 0xc1, 0x66, 0x30, 0xa9, 0xd1, 0x6d, 0x7b, 0x38, 0x38, 0x0b, 0xee, 0xd9, 0xf7, 0xd4, 0xcb, 0x09, 0x0b, 0xc0, 0xbb, + 0xa0, 0xe9, 0x4f, 0x50, 0x8b, 0xc0, 0xcf, 0x39, 0x03, 0x24, 0xcf, 0xa8, 0x68, 0x2c, 0x8b, 0x16, 0x58, 0x74, 0x10, + 0x20, 0xa8, 0x5e, 0xa1, 0xad, 0x3f, 0xb1, 0x26, 0xe3, 0x90, 0x60, 0xbf, 0x7b, 0x17, 0x96, 0x66, 0xb3, 0x33, 0xc4, + 0xf3, 0x86, 0x9c, 0x17, 0xdf, 0xc7, 0x1c, 0x54, 0xc2, 0x56, 0xdf, 0x76, 0x07, 0xb6, 0x85, 0x4b, 0xdb, 0xcb, 0x36, + 0x43, 0x41, 0xe1, 0x78, 0xf3, 0x3d, 0x0b, 0xa6, 0xfd, 0xb0, 0x3d, 0x70, 0x72, 0xa1, 0x3a, 0x12, 0x3c, 0xb7, 0x14, + 0x12, 0xbc, 0xed, 0x4d, 0x41, 0xa0, 0x23, 0xe7, 0x6e, 0xd8, 0x9b, 0xaa, 0x10, 0x8a, 0x3e, 0x6e, 0x8e, 0xdd, 0x20, + 0x86, 0x1f, 0x4e, 0x0b, 0x99, 0x66, 0xaa, 0xfb, 0x6a, 0xcd, 0xec, 0x06, 0x63, 0x65, 0x91, 0x27, 0x61, 0xb6, 0xe9, + 0x60, 0x84, 0x16, 0x24, 0xed, 0xee, 0x00, 0x60, 0xd8, 0x74, 0x14, 0xa7, 0x6d, 0x29, 0x56, 0x53, 0xf6, 0xf9, 0x61, + 0xb5, 0x1c, 0x6c, 0x10, 0x31, 0xbf, 0xd2, 0x3e, 0x00, 0x56, 0x90, 0x78, 0xf9, 0x50, 0x9d, 0x79, 0x3d, 0xaf, 0x9d, + 0x6f, 0x2d, 0x95, 0x28, 0x62, 0x9e, 0x21, 0xa1, 0x78, 0xa9, 0xdd, 0x30, 0x61, 0x6e, 0xcf, 0x91, 0x18, 0x9a, 0xe5, + 0xc3, 0x36, 0x30, 0xbd, 0x0a, 0xb0, 0xa7, 0xe6, 0xb6, 0x48, 0xc2, 0xaa, 0xb9, 0x77, 0x08, 0xac, 0x3d, 0x0c, 0x5f, + 0x89, 0x13, 0xc7, 0x9e, 0x8a, 0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x38, 0x2e, 0x8e, 0xf0, 0x44, 0x68, 0xdf, 0x1f, 0x2d, + 0x72, 0x90, 0x07, 0xfc, 0x35, 0x58, 0x06, 0xa1, 0x6c, 0x8a, 0x8e, 0x1e, 0x1e, 0x01, 0x7b, 0x84, 0x78, 0x23, 0x6c, + 0x6e, 0x54, 0xa3, 0x45, 0x49, 0xc6, 0x0b, 0x1d, 0x0c, 0xf7, 0xb8, 0x74, 0xed, 0x51, 0x30, 0xc8, 0x13, 0x63, 0x07, + 0xcf, 0xfc, 0xfd, 0x11, 0x56, 0xe3, 0x04, 0x85, 0x5b, 0xd2, 0x6e, 0xab, 0xc4, 0xdf, 0xbe, 0x9f, 0x82, 0x04, 0xc7, + 0x3a, 0xf0, 0xb3, 0xee, 0xde, 0x4d, 0x24, 0x52, 0xbb, 0x69, 0x8f, 0x4e, 0x22, 0x30, 0x1e, 0x9c, 0xfb, 0x29, 0x54, + 0x23, 0x89, 0xa8, 0x28, 0x47, 0x0b, 0xd4, 0x3c, 0x55, 0xab, 0xe0, 0x3b, 0x34, 0x23, 0xf0, 0x1c, 0xc3, 0xd6, 0xe4, + 0xa7, 0xea, 0xc6, 0x22, 0x96, 0xef, 0xba, 0x74, 0xb4, 0x85, 0x07, 0x90, 0x82, 0xd1, 0x04, 0xc3, 0xb8, 0x14, 0x94, + 0xac, 0xf8, 0xef, 0xa3, 0x11, 0x2b, 0x9f, 0x1e, 0x66, 0x9b, 0x9b, 0x43, 0x71, 0x6e, 0x41, 0x8c, 0xc3, 0x8d, 0xe8, + 0x6a, 0x5c, 0x01, 0x50, 0x9f, 0xce, 0x89, 0xeb, 0x81, 0x69, 0xc5, 0x9a, 0x2e, 0xc5, 0x3e, 0x39, 0xcc, 0x00, 0x14, + 0xdc, 0x72, 0x0e, 0xfd, 0xc1, 0x9f, 0x86, 0xe0, 0x1e, 0xfb, 0x5f, 0xba, 0x5b, 0x4a, 0xd0, 0xf4, 0xe4, 0x99, 0xe2, + 0x92, 0xce, 0x58, 0x3b, 0x1e, 0xc5, 0x46, 0x83, 0xc2, 0x4b, 0x01, 0x03, 0xd0, 0xe6, 0x20, 0x13, 0x2a, 0x0e, 0x42, + 0x8e, 0x0a, 0x6c, 0x1f, 0x37, 0x3f, 0xc7, 0x9d, 0xfd, 0x14, 0x2c, 0xbc, 0x81, 0x7e, 0x7b, 0x0c, 0x6f, 0x7f, 0xd2, + 0x6f, 0x2f, 0x59, 0xf0, 0x4b, 0x29, 0x43, 0xf7, 0xb5, 0x29, 0x1e, 0xa8, 0x29, 0x4a, 0xb1, 0x44, 0x06, 0x0d, 0x99, + 0x9b, 0xaf, 0xc4, 0x6c, 0xb8, 0x5b, 0xa2, 0xda, 0x91, 0xa2, 0x2b, 0xf7, 0x79, 0x74, 0x82, 0xc4, 0x75, 0x4d, 0x52, + 0x18, 0xb9, 0x04, 0x26, 0xc2, 0x15, 0xdf, 0x12, 0x73, 0xf6, 0xdb, 0x60, 0x83, 0xd7, 0xf2, 0x0e, 0xd0, 0xbe, 0x63, + 0xb3, 0x39, 0xbf, 0xd8, 0x27, 0x45, 0x1f, 0xc8, 0xb4, 0x01, 0x71, 0x76, 0xde, 0xee, 0xc5, 0xbb, 0xbc, 0x17, 0x83, + 0x54, 0xcf, 0x15, 0x8b, 0xe1, 0x5e, 0xf5, 0xde, 0x62, 0x94, 0xd2, 0x64, 0x26, 0xaf, 0x86, 0x5e, 0x57, 0xa2, 0xb7, + 0xb9, 0x09, 0x08, 0xf6, 0x8c, 0xae, 0x5c, 0x74, 0x2d, 0x4b, 0x41, 0x13, 0x80, 0xe8, 0x51, 0x9d, 0xe5, 0x88, 0xe3, + 0x30, 0x9b, 0x0d, 0x05, 0x07, 0x73, 0xd7, 0x8e, 0x8a, 0x63, 0x62, 0x77, 0x99, 0xb0, 0x03, 0x98, 0x11, 0x97, 0xb7, + 0x3a, 0x22, 0x3a, 0x2c, 0xfa, 0xeb, 0xf8, 0xf6, 0x47, 0x8f, 0x6d, 0x76, 0x5c, 0xd0, 0x20, 0xb5, 0xb1, 0x1e, 0x56, + 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x53, 0xa9, 0x2c, 0x36, 0x37, 0xcb, 0xfa, 0x51, 0xad, 0xda, 0xc1, 0xb5, 0xd3, 0x94, + 0xf3, 0x66, 0x36, 0x08, 0x07, 0x22, 0x26, 0x50, 0xa0, 0xa5, 0x95, 0x15, 0x03, 0x0c, 0x29, 0xcb, 0x51, 0x3e, 0x85, + 0xcc, 0x8b, 0xcb, 0x52, 0xa7, 0xbe, 0xc8, 0x78, 0x64, 0x88, 0xa7, 0x9e, 0x64, 0xac, 0x80, 0x82, 0xf5, 0x52, 0x2f, + 0xa1, 0x25, 0x02, 0xcc, 0x9f, 0xa9, 0x1c, 0x1a, 0x61, 0x81, 0x44, 0xa1, 0x61, 0x96, 0x28, 0xe3, 0xb3, 0x08, 0x63, + 0xd0, 0xf6, 0x4f, 0x6a, 0xb1, 0xaf, 0x42, 0x19, 0x1d, 0xc5, 0x61, 0x3e, 0x0c, 0xa8, 0x7e, 0x21, 0x25, 0xd8, 0x34, + 0x7c, 0x0f, 0x6c, 0x54, 0x39, 0x9e, 0x24, 0x08, 0x9f, 0xc6, 0x39, 0x23, 0x4f, 0x61, 0x43, 0xc2, 0x2c, 0x4d, 0xdb, + 0x48, 0xb5, 0x8b, 0xcc, 0x20, 0x94, 0x0b, 0xf3, 0x4f, 0x8d, 0xb3, 0x8b, 0x2c, 0x5c, 0x69, 0x0d, 0xe6, 0xc7, 0x1b, + 0x13, 0xa0, 0xec, 0xf2, 0x32, 0x13, 0x3e, 0x6e, 0x44, 0xf6, 0x86, 0xae, 0x98, 0x0e, 0x14, 0x52, 0x81, 0x13, 0x91, + 0xc5, 0x43, 0x67, 0x28, 0x34, 0xc2, 0x01, 0x9d, 0x22, 0xe7, 0xae, 0xb1, 0xe9, 0xf3, 0x81, 0xf6, 0x8d, 0xd2, 0xd0, + 0x49, 0x40, 0x08, 0x08, 0xdc, 0x0d, 0x6b, 0x2a, 0x1d, 0xa4, 0x41, 0x42, 0xa5, 0xe8, 0xe7, 0x00, 0xfe, 0x61, 0x24, + 0x29, 0x00, 0xf6, 0x43, 0x35, 0x52, 0x44, 0x59, 0x16, 0xb8, 0x00, 0x34, 0xd7, 0x3e, 0xae, 0x84, 0x2f, 0x0c, 0x54, + 0x98, 0x9e, 0x66, 0xe5, 0xa5, 0x50, 0x22, 0xef, 0xd6, 0xa4, 0xac, 0x91, 0x4c, 0x3e, 0x45, 0x87, 0x4f, 0x79, 0xd7, + 0xaf, 0x25, 0x1e, 0xba, 0xe0, 0x29, 0x2c, 0xab, 0x7a, 0x7e, 0x15, 0x72, 0x72, 0xae, 0x41, 0x57, 0x48, 0xa1, 0xbf, + 0xe2, 0x24, 0xef, 0xbd, 0xf2, 0xab, 0x5a, 0x6a, 0x0c, 0x65, 0xef, 0xd7, 0x35, 0xc3, 0xf2, 0x72, 0x5e, 0x85, 0x29, + 0x08, 0xb8, 0x25, 0x4b, 0x82, 0xa5, 0xd4, 0x10, 0x60, 0x61, 0x7b, 0xa4, 0x95, 0x82, 0xbc, 0xd4, 0xe1, 0x9d, 0xa7, + 0x60, 0x05, 0x18, 0x87, 0x5a, 0x2a, 0x99, 0x46, 0x12, 0x5f, 0x2a, 0x51, 0x60, 0xca, 0xfd, 0x11, 0xf8, 0xa9, 0xcd, + 0x93, 0xae, 0x73, 0xd7, 0x8f, 0x67, 0x98, 0xda, 0x43, 0xa0, 0xc7, 0xde, 0x1d, 0x30, 0x25, 0xea, 0x3a, 0xac, 0x20, + 0x0e, 0xcd, 0x6a, 0x9a, 0x05, 0xcc, 0x98, 0x36, 0x68, 0xc9, 0x36, 0xd8, 0x72, 0x39, 0xd8, 0x47, 0x62, 0x7b, 0x56, + 0x2b, 0x20, 0x74, 0x0d, 0x1a, 0x18, 0x72, 0x97, 0x0a, 0x2d, 0xcc, 0x7b, 0x5d, 0x2a, 0xc2, 0xfd, 0x39, 0xe0, 0xd2, + 0x0a, 0xce, 0xbc, 0x8c, 0x06, 0xde, 0x8f, 0x8f, 0x13, 0x4c, 0x7c, 0x41, 0xac, 0xc0, 0x0e, 0x0e, 0x3a, 0xcd, 0xa6, + 0xc0, 0xa9, 0xb8, 0x48, 0x19, 0x2c, 0x2b, 0x4a, 0x6d, 0xf8, 0x21, 0x45, 0xb6, 0xee, 0xf2, 0x40, 0x77, 0x21, 0x16, + 0xc0, 0x4e, 0xbf, 0x60, 0xe4, 0x5b, 0xd6, 0xcb, 0x80, 0xc1, 0xa9, 0xd6, 0x38, 0x08, 0xfc, 0xe6, 0x66, 0x32, 0x2c, + 0x53, 0x62, 0xbb, 0x26, 0xab, 0x0b, 0xc8, 0x61, 0xa8, 0x26, 0xee, 0x20, 0x2c, 0x95, 0x3d, 0x5e, 0x94, 0x33, 0x5c, + 0x2e, 0x65, 0x21, 0x37, 0xcf, 0xab, 0x69, 0x3e, 0xb7, 0xd2, 0x6c, 0x3a, 0xde, 0x8a, 0x2f, 0x0a, 0xfe, 0x81, 0x13, + 0x4b, 0xab, 0x9e, 0x52, 0x2b, 0x3c, 0xca, 0xdc, 0x92, 0x75, 0x4a, 0x6a, 0x75, 0xdd, 0x40, 0x35, 0xc2, 0xd3, 0x34, + 0x6c, 0x04, 0x42, 0x4c, 0x70, 0xf1, 0xeb, 0x26, 0x13, 0xd3, 0xde, 0x12, 0x52, 0x47, 0xd8, 0x3d, 0x94, 0x13, 0xdc, + 0xd5, 0x3c, 0xfb, 0x3c, 0x9c, 0x5f, 0xcd, 0xdc, 0x7b, 0x06, 0x73, 0x3f, 0x0e, 0xb9, 0xc1, 0xe8, 0xb1, 0x4c, 0xf8, + 0x91, 0xb1, 0x8f, 0x5c, 0x55, 0x3d, 0x39, 0x09, 0x2b, 0x91, 0x25, 0x9e, 0x8c, 0xa3, 0x0e, 0xe3, 0x54, 0xb4, 0x26, + 0xc8, 0x2e, 0x2f, 0x0b, 0x73, 0x2f, 0x50, 0xd0, 0xd4, 0xe3, 0xf5, 0x38, 0x6d, 0xc5, 0xce, 0x46, 0x24, 0x72, 0xef, + 0x55, 0x2d, 0x12, 0x59, 0xf1, 0x39, 0x8e, 0x74, 0xc5, 0x41, 0xee, 0x93, 0x93, 0xd5, 0x4d, 0x2a, 0x74, 0x8b, 0x46, + 0xdb, 0xd8, 0xa3, 0xfa, 0x40, 0x52, 0xcf, 0xa8, 0xc0, 0xaa, 0xc6, 0xbe, 0x7b, 0xb7, 0x23, 0xd2, 0x2d, 0x95, 0x62, + 0x83, 0xa5, 0x85, 0xd1, 0x8c, 0x51, 0x30, 0x28, 0x29, 0x32, 0x50, 0xa3, 0xfc, 0x0a, 0xc1, 0xb0, 0x47, 0x0d, 0x40, + 0x71, 0xae, 0xaf, 0x7e, 0x5c, 0x4a, 0xb6, 0x10, 0x90, 0xb8, 0x4b, 0x06, 0x62, 0x4d, 0x30, 0x33, 0xf2, 0xc9, 0x7b, + 0xe0, 0xbc, 0x01, 0x43, 0x1f, 0x01, 0xfc, 0x02, 0xb1, 0xe9, 0xc1, 0xc4, 0xb6, 0x89, 0x28, 0xfa, 0x6c, 0xe0, 0x39, + 0x00, 0x3b, 0xaf, 0x42, 0xa3, 0xef, 0xaa, 0x14, 0x30, 0x64, 0x03, 0x37, 0x60, 0x55, 0x58, 0x6e, 0xef, 0x39, 0xb8, + 0x0d, 0xf0, 0xfa, 0x4c, 0x36, 0xdf, 0xc0, 0x3c, 0xc1, 0xea, 0xec, 0xc2, 0xaf, 0x2c, 0x6b, 0x71, 0xee, 0x74, 0xd0, + 0xa8, 0x57, 0x94, 0x10, 0xb5, 0xfb, 0x58, 0x7b, 0x80, 0x11, 0x16, 0xf1, 0xfe, 0x0a, 0xdf, 0xf5, 0xb8, 0xe5, 0x9e, + 0x46, 0x8b, 0x30, 0x5d, 0x25, 0x8d, 0x41, 0xc9, 0xba, 0x9f, 0x8c, 0xb8, 0x97, 0xfb, 0x22, 0x16, 0x5c, 0xe1, 0xc8, + 0xaa, 0x90, 0x62, 0x03, 0x49, 0x7a, 0xda, 0xa3, 0x03, 0xf6, 0x8d, 0x66, 0x2f, 0xa0, 0xcc, 0xfb, 0x8a, 0x54, 0x12, + 0x52, 0x9a, 0xdd, 0x10, 0x49, 0xc2, 0x5a, 0x91, 0xa7, 0xce, 0xfb, 0x8e, 0xf6, 0xb9, 0x95, 0x44, 0x30, 0x82, 0x93, + 0x30, 0x1d, 0x2b, 0x0f, 0x9a, 0x02, 0x5c, 0x45, 0x47, 0x4c, 0xdf, 0x04, 0xe4, 0x37, 0x03, 0xb9, 0xbd, 0x92, 0x5c, + 0x9b, 0x6b, 0x18, 0x9e, 0x20, 0xc1, 0xaa, 0x48, 0x04, 0x1e, 0x51, 0x03, 0x8e, 0xf9, 0x3a, 0xcf, 0x03, 0x4c, 0xf8, + 0xda, 0xde, 0x04, 0x80, 0x72, 0x72, 0x55, 0x9c, 0x95, 0x40, 0x37, 0x60, 0xb9, 0x3e, 0x4e, 0x8d, 0x8a, 0xc4, 0xc5, + 0x8d, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x2d, 0x67, 0x32, 0xc4, 0x74, 0x10, 0x04, 0x64, 0xea, 0x3b, 0xe6, 0x08, 0x99, + 0x2b, 0xac, 0xcf, 0xb9, 0x53, 0x9b, 0xba, 0xc7, 0xa8, 0x9b, 0x27, 0xa9, 0xc5, 0xeb, 0xb4, 0x29, 0x25, 0x62, 0x52, + 0x62, 0x6e, 0x88, 0x54, 0x6c, 0xa6, 0xc4, 0x9d, 0x5b, 0xdf, 0x68, 0x21, 0x6d, 0xb4, 0x0d, 0x91, 0x83, 0xcd, 0x2a, + 0x79, 0x4f, 0x60, 0x3c, 0x17, 0x84, 0x2f, 0x5f, 0x51, 0x92, 0x0e, 0x73, 0x4c, 0x04, 0xab, 0x17, 0x53, 0x91, 0xbf, + 0x73, 0x74, 0x9a, 0xbd, 0x41, 0x0f, 0x52, 0x6f, 0x20, 0x31, 0x6b, 0xe2, 0xbb, 0x90, 0x86, 0x3a, 0x42, 0xa0, 0x32, + 0xaa, 0x65, 0x3a, 0x4e, 0xac, 0xc2, 0x37, 0x82, 0xaf, 0xde, 0xea, 0xe3, 0x7c, 0xe3, 0xb9, 0xb1, 0x1a, 0x41, 0x0c, + 0xde, 0x42, 0x3e, 0xf4, 0xa4, 0x08, 0x07, 0xc2, 0xe5, 0x9b, 0x9b, 0xbd, 0x7c, 0x97, 0x57, 0x21, 0x92, 0x0a, 0xc6, + 0x18, 0x33, 0x8a, 0x71, 0x4f, 0xd4, 0xd4, 0x62, 0x0e, 0x03, 0xcb, 0xd6, 0x61, 0x8e, 0x07, 0x00, 0xd0, 0xd2, 0x94, + 0x5e, 0x35, 0x15, 0x2a, 0xcf, 0x73, 0x09, 0x9f, 0xea, 0x10, 0x55, 0x35, 0x7e, 0xbb, 0x3e, 0x03, 0x85, 0xe0, 0xbe, + 0xd3, 0xf1, 0xf0, 0x10, 0x02, 0x56, 0x51, 0xc8, 0x02, 0xbd, 0x41, 0x7b, 0x55, 0x22, 0x14, 0x33, 0x27, 0xeb, 0x31, + 0xc3, 0x49, 0x05, 0x5b, 0xa8, 0x84, 0xa5, 0xd2, 0x02, 0xbf, 0xda, 0x08, 0xcd, 0x53, 0xc6, 0xbd, 0x57, 0x15, 0xce, + 0xa0, 0x3f, 0x98, 0xb7, 0xca, 0xa8, 0x6f, 0x57, 0x4e, 0x64, 0x2a, 0x30, 0x71, 0x33, 0x4b, 0xed, 0xf7, 0xcb, 0x3a, + 0xed, 0xe7, 0x15, 0x72, 0x9f, 0x93, 0xe6, 0xeb, 0xdc, 0x42, 0xf3, 0xc9, 0x70, 0xbf, 0x52, 0x7e, 0x68, 0x61, 0xd4, + 0x94, 0x5f, 0x5e, 0x57, 0x7e, 0x85, 0xa7, 0xc2, 0x5b, 0xfd, 0x2e, 0x0a, 0x5d, 0xd4, 0xe7, 0x60, 0x08, 0xe9, 0x47, + 0x70, 0x0d, 0x0d, 0x1e, 0x14, 0xc9, 0x62, 0xb1, 0x76, 0x41, 0x5c, 0x1f, 0x73, 0xaa, 0x1d, 0xca, 0x18, 0x23, 0x9e, + 0x96, 0x1c, 0x24, 0x19, 0x1c, 0x8c, 0xdf, 0xc0, 0x80, 0x98, 0x94, 0x84, 0x74, 0x08, 0x9d, 0xb5, 0x99, 0x88, 0xca, + 0x5d, 0xbc, 0xd9, 0xb8, 0xac, 0x29, 0x14, 0x61, 0x27, 0x98, 0xa9, 0x94, 0x0a, 0x02, 0x69, 0xf2, 0xdd, 0xe9, 0xd4, + 0x82, 0xa1, 0x85, 0x6b, 0x2a, 0x20, 0xaf, 0xed, 0x7a, 0xd0, 0xe4, 0x3d, 0xc5, 0xd0, 0xd7, 0xa9, 0x11, 0x2f, 0x33, + 0xf8, 0x1a, 0x36, 0x7f, 0x4d, 0x94, 0xe4, 0x21, 0x13, 0xb1, 0x57, 0xf0, 0x89, 0x90, 0x4d, 0xc1, 0xce, 0x04, 0xfa, + 0xa1, 0x5d, 0xd9, 0x4b, 0x77, 0x8b, 0xca, 0xa5, 0x45, 0x63, 0x2b, 0x51, 0xb3, 0xe6, 0x87, 0xf1, 0x66, 0x0a, 0xfb, + 0xd9, 0xa3, 0x04, 0x02, 0xd2, 0x54, 0x4e, 0x52, 0xcd, 0x7b, 0x98, 0x0e, 0x01, 0x24, 0xd8, 0xfd, 0x04, 0x16, 0xfa, + 0x4d, 0x89, 0x09, 0x16, 0x55, 0x63, 0xb7, 0x39, 0x68, 0xcd, 0x39, 0x69, 0xbe, 0x39, 0x6a, 0xed, 0x4d, 0x65, 0x3d, + 0x63, 0x76, 0x80, 0x6d, 0xbb, 0x9b, 0xc5, 0x61, 0xba, 0xd9, 0x19, 0x1a, 0x82, 0x0b, 0x8f, 0xff, 0x93, 0x12, 0xd3, + 0x40, 0x72, 0xa9, 0x1b, 0x3f, 0xa1, 0x0e, 0xc3, 0xff, 0x96, 0xa4, 0x80, 0x07, 0xb5, 0xd5, 0x58, 0x71, 0xee, 0x15, + 0x47, 0xc9, 0x65, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0x8c, 0x89, 0x62, 0x9e, 0x13, 0x00, 0xa3, 0xd8, 0xfc, + 0x29, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f, 0xe8, 0x48, 0x45, 0x65, 0x73, 0x12, + 0xf3, 0x6f, 0x0b, 0x30, 0xcd, 0x89, 0x0f, 0xf5, 0x5c, 0xc3, 0x50, 0x80, 0xaf, 0x6c, 0x28, 0x35, 0xdb, 0xe3, 0xdf, + 0x3b, 0xdb, 0x7d, 0x49, 0x14, 0xc1, 0x02, 0x0d, 0xba, 0x5c, 0x81, 0x2f, 0x60, 0x19, 0xdc, 0x92, 0x7e, 0x0a, 0xbe, + 0x97, 0x57, 0xc1, 0x67, 0xec, 0x7f, 0x01, 0x68, 0x55, 0x60, 0x40, 0xb9, 0xd3, 0x34, 0xac, 0x84, 0xb8, 0x44, 0x85, + 0x59, 0xc5, 0xf9, 0xe3, 0x3a, 0xaf, 0x9b, 0x96, 0x25, 0x06, 0xe5, 0xe7, 0xae, 0xe1, 0xc6, 0xf7, 0x1a, 0xf9, 0xe3, + 0x7b, 0xcf, 0x41, 0xb7, 0x13, 0x69, 0xef, 0xde, 0xcd, 0xef, 0x90, 0x85, 0x86, 0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xe9, + 0x92, 0xab, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x07, 0x0c, 0xc1, 0x63, 0xa7, 0x32, 0xf9, + 0x0c, 0x1b, 0x4d, 0xb1, 0x6b, 0x2e, 0x0c, 0x3e, 0x50, 0x95, 0x85, 0xe4, 0xc5, 0x3a, 0xd9, 0x9e, 0x9d, 0xc2, 0xf3, + 0xcb, 0xb8, 0x00, 0xea, 0x00, 0xfa, 0x15, 0x95, 0xc5, 0x06, 0x72, 0x71, 0x53, 0xd6, 0x7a, 0x45, 0xe3, 0xf1, 0xb5, + 0x5d, 0x58, 0x5d, 0x81, 0x4f, 0xa3, 0x74, 0x9c, 0x88, 0x49, 0xcc, 0xa4, 0xca, 0x35, 0xb9, 0x36, 0xba, 0x97, 0xb6, + 0x68, 0x9e, 0x0b, 0x09, 0x5e, 0x11, 0xb8, 0x21, 0xf4, 0x95, 0xbe, 0x5c, 0x6f, 0xa0, 0xe0, 0x51, 0x7b, 0x73, 0x11, + 0x4c, 0x4c, 0x3c, 0x66, 0x48, 0x4d, 0xbf, 0x0e, 0xa7, 0x56, 0x16, 0x2b, 0x0e, 0xbf, 0xce, 0x19, 0x6b, 0x28, 0x00, + 0xe2, 0x93, 0x07, 0x57, 0xbb, 0x49, 0xaf, 0x94, 0x76, 0x50, 0x1a, 0x21, 0xbe, 0xad, 0xf0, 0x75, 0x97, 0x8a, 0xaf, + 0x5c, 0x75, 0xef, 0x6b, 0xc6, 0x8c, 0x0b, 0x46, 0xcf, 0xf9, 0x2c, 0x69, 0x5c, 0xbb, 0xa1, 0xbb, 0x3a, 0x3f, 0x7a, + 0x3f, 0xc8, 0xbc, 0x85, 0x19, 0xb0, 0x09, 0xa8, 0x82, 0xe7, 0xde, 0x6b, 0xe3, 0x44, 0xf9, 0x3b, 0xf3, 0x88, 0x57, + 0x0e, 0xb3, 0xee, 0x24, 0xf9, 0xbb, 0xc1, 0x77, 0xc1, 0xd5, 0x2d, 0x8d, 0x13, 0xe4, 0xae, 0x3a, 0x41, 0x26, 0xca, + 0xcd, 0xf4, 0x86, 0xdb, 0xbb, 0xad, 0x40, 0x10, 0xa7, 0x62, 0xfa, 0xa8, 0x1c, 0xd7, 0x8f, 0x16, 0xa8, 0x54, 0x44, + 0x7c, 0xaa, 0x72, 0x57, 0xae, 0x4c, 0x0d, 0xf5, 0xb8, 0x4e, 0x66, 0xa1, 0x69, 0xd6, 0xe4, 0x52, 0x36, 0x3d, 0x46, + 0xa6, 0xd9, 0xa9, 0x36, 0xbf, 0x7b, 0xe5, 0x21, 0x1d, 0x43, 0x73, 0xb1, 0x56, 0x0b, 0xee, 0x77, 0x15, 0x85, 0x77, + 0xbd, 0xd8, 0x48, 0x65, 0xa8, 0x59, 0x8f, 0xa2, 0x8f, 0xe3, 0x36, 0x73, 0x79, 0x94, 0xfd, 0x59, 0x03, 0xc0, 0x74, + 0x84, 0x45, 0x77, 0xd3, 0x33, 0xf6, 0x04, 0x7a, 0x7a, 0x22, 0x83, 0x44, 0xaf, 0x75, 0xbe, 0x6a, 0x95, 0x58, 0xba, + 0x86, 0xc0, 0xee, 0x35, 0x19, 0xab, 0x92, 0x76, 0xab, 0xf5, 0xab, 0x79, 0x3e, 0x4f, 0xf9, 0x4a, 0x9e, 0x4f, 0xcd, + 0xa2, 0xbb, 0xd3, 0x76, 0xaf, 0x4f, 0x0d, 0x15, 0x73, 0xad, 0x6f, 0xf2, 0x3b, 0xa6, 0xeb, 0x60, 0xa8, 0x45, 0x90, + 0x59, 0xed, 0xaa, 0x67, 0x65, 0x39, 0xab, 0x67, 0x72, 0xcc, 0x84, 0x6f, 0x2a, 0xdd, 0x21, 0xba, 0x61, 0xaa, 0x66, + 0xfa, 0xb1, 0xb1, 0x2d, 0x64, 0x9b, 0xe7, 0x17, 0xe3, 0x1c, 0x28, 0x2d, 0xf7, 0x97, 0x09, 0xc3, 0x8f, 0x97, 0x97, + 0x3f, 0x0a, 0x39, 0x55, 0x75, 0xf4, 0x96, 0x2f, 0x75, 0xcf, 0x60, 0x56, 0x2a, 0x27, 0xe2, 0x98, 0xad, 0x1f, 0xbc, + 0xb9, 0x7b, 0x05, 0x2c, 0xc7, 0x80, 0xdd, 0x31, 0x73, 0x1a, 0x43, 0x55, 0x1b, 0xf8, 0x87, 0xf5, 0x83, 0xad, 0xdb, + 0xc3, 0x3f, 0x0c, 0x7e, 0x08, 0xae, 0x6d, 0x6c, 0x6c, 0xe3, 0xed, 0x5a, 0x22, 0xc8, 0x2b, 0x3c, 0xd0, 0xc7, 0xab, + 0x8f, 0x82, 0x96, 0xeb, 0xc4, 0xf6, 0xc0, 0xa1, 0xb0, 0x35, 0xc8, 0x37, 0x29, 0x93, 0x46, 0x8b, 0x82, 0x67, 0x33, + 0x39, 0x43, 0x21, 0xaf, 0xf9, 0x38, 0x68, 0x3b, 0xc2, 0xdf, 0xc0, 0xa9, 0x1d, 0x2f, 0x2f, 0x3f, 0x41, 0x1f, 0xf0, + 0x74, 0xa5, 0x34, 0x15, 0x71, 0x4a, 0xb9, 0x45, 0x57, 0xeb, 0x3c, 0x18, 0x29, 0x2e, 0xa6, 0xa8, 0x74, 0xdc, 0xe5, + 0xb5, 0xb3, 0x91, 0xd3, 0x5f, 0xe2, 0xd5, 0x45, 0xba, 0x7c, 0x24, 0xb2, 0x55, 0x4b, 0xef, 0x37, 0x7d, 0xba, 0x6d, + 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x3a, 0x98, 0xf1, 0x71, 0x22, 0xbc, 0x3e, 0x31, 0xd6, 0x77, 0x8b, 0xc0, 0x74, 0x73, + 0x6c, 0xf2, 0xc3, 0xf1, 0x7a, 0xb3, 0x59, 0xe3, 0x0e, 0xde, 0x38, 0x4f, 0x9c, 0x65, 0x89, 0x11, 0x95, 0xa5, 0x86, + 0x07, 0xb4, 0x42, 0xdc, 0xbc, 0x67, 0x02, 0xe3, 0xb2, 0x0b, 0x92, 0xda, 0x6e, 0x20, 0x70, 0xb1, 0x27, 0x31, 0x4b, + 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x79, 0xed, 0xec, 0x2c, 0xb5, 0xbd, 0x6a, 0xaa, + 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x04, 0x59, 0xf7, 0x5b, 0xe8, 0x22, 0x06, 0x8c, 0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, + 0x8a, 0xf8, 0x46, 0x13, 0x69, 0x52, 0x1f, 0x52, 0xdf, 0x61, 0x58, 0xab, 0xab, 0x1c, 0x24, 0x70, 0x8f, 0xbc, 0x5b, + 0xe2, 0xd2, 0xd3, 0x67, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0x86, 0xbd, 0xe2, 0x01, 0xd8, 0x1f, 0x18, + 0xb7, 0x88, 0x45, 0xbc, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xa0, 0xb9, 0xe1, 0x06, 0xbf, 0xb1, 0x6a, 0xcd, 0xc0, + 0x0c, 0x33, 0xce, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0a, 0x20, 0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, + 0x64, 0xb9, 0xea, 0x0c, 0xf9, 0x57, 0x58, 0x67, 0xbd, 0x38, 0x01, 0x33, 0x69, 0xca, 0x4b, 0x4c, 0x4c, 0x11, 0x97, + 0x9b, 0x65, 0xcc, 0xd3, 0xf4, 0x59, 0xb4, 0x83, 0x93, 0x1b, 0x09, 0x1c, 0xb1, 0x6f, 0x2c, 0x43, 0x33, 0x61, 0x23, + 0x26, 0xd2, 0xa8, 0x94, 0x12, 0x3e, 0x90, 0x4b, 0x2d, 0xf9, 0xcb, 0x5c, 0x5e, 0x7d, 0xb9, 0x4d, 0x70, 0x40, 0x5e, + 0x03, 0xcb, 0xa1, 0x71, 0xdc, 0x32, 0x90, 0x88, 0xc5, 0x80, 0x18, 0xb5, 0x2a, 0x57, 0x93, 0x51, 0x9d, 0xcc, 0x57, + 0xc8, 0x85, 0x8a, 0x3c, 0xb8, 0x25, 0x50, 0xf2, 0xe7, 0x98, 0x3a, 0x98, 0x95, 0xda, 0x4d, 0x8b, 0x4d, 0x92, 0xf7, + 0xcc, 0x80, 0xe4, 0xfa, 0x6b, 0x78, 0x68, 0xfc, 0xe2, 0x95, 0x39, 0x25, 0x7c, 0x51, 0xc6, 0xd2, 0xd2, 0x98, 0x4b, + 0xff, 0x42, 0xde, 0xa7, 0x95, 0x80, 0xfd, 0x0a, 0x62, 0xca, 0xc0, 0x25, 0x36, 0x2e, 0x48, 0xca, 0x6b, 0x79, 0xca, + 0xee, 0x6b, 0x28, 0xdf, 0x15, 0x93, 0xae, 0x52, 0x59, 0x57, 0x58, 0x75, 0xbf, 0x2e, 0x58, 0x7e, 0xb1, 0xcf, 0x30, + 0x37, 0x19, 0x0d, 0xb2, 0x15, 0x33, 0x9b, 0xf2, 0xab, 0xbd, 0x6b, 0xbf, 0xf2, 0x50, 0xd2, 0xa1, 0x5a, 0xa5, 0x9b, + 0x57, 0x6e, 0x38, 0xc6, 0x8d, 0x1b, 0x8e, 0x00, 0x36, 0x86, 0x9d, 0x2a, 0x52, 0xeb, 0xfc, 0xf7, 0xd5, 0xf0, 0x13, + 0xed, 0xb5, 0xa1, 0xde, 0x75, 0xc3, 0xb5, 0xe9, 0xe9, 0xd7, 0xa0, 0x6a, 0x64, 0x09, 0x5d, 0x87, 0x2a, 0x26, 0x23, + 0x51, 0x62, 0xba, 0x4a, 0x79, 0xd4, 0xd7, 0x88, 0x73, 0x10, 0x37, 0x94, 0xbf, 0xf8, 0xe7, 0xf0, 0xe2, 0x28, 0x40, + 0x23, 0x6a, 0x39, 0xc9, 0x52, 0xde, 0x9a, 0x44, 0xb3, 0x38, 0xb9, 0x08, 0x16, 0x71, 0x6b, 0x96, 0xa5, 0x59, 0x31, + 0x07, 0xae, 0xf4, 0x8a, 0x0b, 0xb0, 0xe1, 0x67, 0xad, 0x45, 0xec, 0x3d, 0x67, 0xc9, 0x29, 0xe3, 0xf1, 0x28, 0xf2, + 0xec, 0xbd, 0x1c, 0xc4, 0x83, 0xf5, 0x3a, 0xca, 0xf3, 0xec, 0xcc, 0xf6, 0xde, 0x65, 0xc7, 0xc0, 0xb4, 0xde, 0x9b, + 0xf3, 0x8b, 0x13, 0x96, 0x7a, 0xef, 0x8f, 0x17, 0x29, 0x5f, 0x78, 0x45, 0x94, 0x16, 0xad, 0x82, 0xe5, 0xf1, 0x04, + 0xd4, 0x44, 0x92, 0xe5, 0x2d, 0xcc, 0x7f, 0x9e, 0xb1, 0x20, 0x89, 0x4f, 0xa6, 0xdc, 0x1a, 0x47, 0xf9, 0xa7, 0x5e, + 0xab, 0x35, 0xcf, 0xe3, 0x59, 0x94, 0x5f, 0xb4, 0xa8, 0x45, 0xf0, 0x45, 0x7b, 0x3b, 0xfa, 0x6a, 0x72, 0xbf, 0xc7, + 0x73, 0xe8, 0x1b, 0x23, 0x15, 0x03, 0x10, 0x3e, 0xd6, 0xf6, 0x4e, 0x7b, 0x56, 0xdc, 0x11, 0x27, 0x4a, 0x51, 0xca, + 0xcb, 0x23, 0xef, 0x23, 0x03, 0xb8, 0xfd, 0x63, 0x9e, 0x7a, 0xe0, 0xcb, 0xf1, 0x2c, 0x5d, 0x8e, 0x16, 0x79, 0x01, + 0x03, 0xcc, 0xb3, 0x38, 0xe5, 0x2c, 0xef, 0x1d, 0x67, 0x39, 0x90, 0xad, 0x95, 0x47, 0xe3, 0x78, 0x51, 0x04, 0xf7, + 0xe7, 0xe7, 0x3d, 0xb4, 0x15, 0x4e, 0xf2, 0x6c, 0x91, 0x8e, 0xe5, 0x5c, 0x71, 0x0a, 0x1b, 0x23, 0xe6, 0x66, 0x05, + 0x7d, 0x09, 0x05, 0xe0, 0x4b, 0x59, 0x94, 0xb7, 0x4e, 0xb0, 0x33, 0x1a, 0xfa, 0xed, 0x31, 0x3b, 0xf1, 0xf2, 0x93, + 0xe3, 0xc8, 0xe9, 0x74, 0x1f, 0x7a, 0xea, 0x9f, 0xbf, 0xe3, 0x82, 0xe1, 0xbe, 0xb6, 0xb8, 0xd3, 0x6e, 0xff, 0x9d, + 0xdb, 0x6b, 0xcc, 0x42, 0x00, 0x05, 0x9d, 0xf9, 0xb9, 0x55, 0x64, 0x09, 0xac, 0xcf, 0xba, 0x9e, 0xbd, 0x39, 0xf8, + 0x4d, 0x71, 0x7a, 0x12, 0x74, 0xe7, 0xe7, 0x25, 0x62, 0x17, 0x88, 0x84, 0x4c, 0x89, 0xa4, 0x7c, 0x5b, 0xfe, 0x5e, + 0x88, 0x1f, 0xad, 0x87, 0xb8, 0xab, 0x20, 0xae, 0xa8, 0xde, 0x1a, 0xc3, 0x3e, 0x20, 0xf2, 0x77, 0x0a, 0x01, 0xc8, + 0x14, 0x9c, 0xc0, 0x5c, 0xc1, 0x41, 0x2f, 0xbf, 0x1b, 0x8c, 0xee, 0x7a, 0x30, 0x1e, 0xdd, 0x04, 0x46, 0x9e, 0x8e, + 0x97, 0xf5, 0x75, 0xed, 0x80, 0x73, 0xda, 0x9b, 0x32, 0xe4, 0xa7, 0xa0, 0x8b, 0xcf, 0x67, 0xf1, 0x98, 0x4f, 0xc5, + 0x23, 0xb1, 0xf3, 0x99, 0xa8, 0xdb, 0x69, 0xb7, 0xc5, 0x7b, 0x01, 0x0a, 0x2d, 0xe8, 0xf8, 0xd8, 0x00, 0x98, 0xe8, + 0xc3, 0x55, 0x1f, 0xb1, 0xf9, 0xfa, 0xc6, 0x2f, 0xd5, 0x78, 0x67, 0x2a, 0x6f, 0x50, 0xa8, 0x08, 0xf5, 0xcd, 0x16, + 0xcc, 0x78, 0xcb, 0xfb, 0x1d, 0x7d, 0x50, 0x35, 0xf8, 0x9a, 0x91, 0xd6, 0x0b, 0xb8, 0x67, 0xe6, 0x02, 0xf5, 0xd2, + 0x3e, 0x86, 0xa4, 0x5a, 0x2d, 0x17, 0xf4, 0x06, 0xc3, 0x10, 0x12, 0x1d, 0x08, 0x3a, 0xf9, 0xa0, 0xa0, 0x6f, 0x6a, + 0x64, 0x6e, 0x50, 0x38, 0x99, 0x0b, 0x5b, 0x3e, 0xd3, 0x72, 0x1d, 0x94, 0x34, 0x78, 0xd9, 0x1f, 0x98, 0x6c, 0x00, + 0xd2, 0x9b, 0x42, 0x5d, 0x7f, 0x09, 0x85, 0x2b, 0xa5, 0x1c, 0xa9, 0xd9, 0x75, 0x57, 0xf4, 0x61, 0x55, 0x62, 0xca, + 0x48, 0x3e, 0x1c, 0xfe, 0x3b, 0x0c, 0x7b, 0x47, 0x3b, 0x96, 0x45, 0xb6, 0xc8, 0x47, 0x14, 0xa9, 0x5b, 0xf5, 0xf8, + 0x6d, 0x52, 0xb8, 0xb6, 0xc7, 0xb4, 0x9c, 0x47, 0x37, 0xb8, 0xf6, 0x91, 0x03, 0x4e, 0x87, 0x20, 0xe2, 0x8e, 0x81, + 0x8c, 0x72, 0x28, 0x08, 0x51, 0x75, 0x8d, 0x29, 0xdf, 0x8d, 0xee, 0x5f, 0xfa, 0x8b, 0x34, 0x06, 0x49, 0xf7, 0x31, + 0x1e, 0xd3, 0xbd, 0x93, 0x78, 0x4c, 0x07, 0x11, 0x2d, 0x4a, 0x3c, 0xc2, 0xc8, 0x36, 0x14, 0xa8, 0xef, 0xb0, 0xc0, + 0xb3, 0x4c, 0x64, 0xb1, 0x5b, 0x36, 0x1e, 0x26, 0x18, 0xaa, 0x72, 0x9c, 0xcd, 0xa2, 0x38, 0x0d, 0xf0, 0xfb, 0x20, + 0x9e, 0x1e, 0x31, 0xc0, 0x2e, 0x1e, 0xfc, 0x64, 0x32, 0x17, 0xad, 0xe3, 0xfa, 0xbf, 0x80, 0x1c, 0xa1, 0xfe, 0xa5, + 0xf4, 0xc3, 0x34, 0x5c, 0xea, 0x98, 0xb7, 0x5e, 0x0a, 0xb2, 0x87, 0x2b, 0x9b, 0x95, 0x51, 0x9c, 0x63, 0x97, 0xd3, + 0x8f, 0x41, 0xab, 0x13, 0x74, 0xb4, 0xeb, 0x5a, 0xbb, 0x8d, 0x2a, 0x72, 0x59, 0xe4, 0x8d, 0x46, 0x82, 0x41, 0x3f, + 0x0b, 0x38, 0xab, 0x77, 0x0d, 0xab, 0x27, 0xf9, 0x12, 0x03, 0x38, 0x27, 0xa9, 0x53, 0x03, 0x82, 0xce, 0x02, 0xae, + 0x98, 0xca, 0x2d, 0x23, 0x52, 0x4a, 0x8f, 0x69, 0x03, 0xd7, 0xef, 0x12, 0xe1, 0xbd, 0xa1, 0x7a, 0x0a, 0x94, 0x62, + 0xb9, 0xf1, 0xd1, 0xae, 0xd8, 0xf1, 0x16, 0xf1, 0x58, 0x68, 0xc3, 0x16, 0xb4, 0xad, 0x3f, 0x8d, 0x80, 0x4a, 0x9f, + 0x42, 0x7b, 0x63, 0xe9, 0xa8, 0xc4, 0xfa, 0x1c, 0xe6, 0xda, 0x13, 0x5a, 0x8f, 0x6e, 0xe4, 0xdb, 0xfd, 0x8d, 0x25, + 0x2f, 0x77, 0xb7, 0x44, 0xef, 0xfe, 0x51, 0x59, 0x90, 0x82, 0x32, 0x03, 0x69, 0xd5, 0x14, 0xa2, 0x0e, 0x86, 0xa5, + 0xf4, 0x5d, 0x1c, 0x37, 0xd7, 0x46, 0x97, 0x88, 0x18, 0x4b, 0xb6, 0x2b, 0x30, 0x5d, 0x29, 0xca, 0x61, 0x4f, 0xea, + 0x84, 0x94, 0x42, 0xe4, 0x60, 0xf4, 0x56, 0xa1, 0x38, 0x42, 0x08, 0x06, 0x1b, 0xcb, 0xb8, 0x0c, 0x37, 0x96, 0x59, + 0x79, 0x04, 0x96, 0x09, 0x42, 0x95, 0xab, 0xcf, 0xbb, 0xc0, 0xc4, 0x22, 0xc8, 0x62, 0xd1, 0x08, 0x38, 0x2d, 0x2b, + 0x6d, 0x6b, 0x20, 0xa0, 0x01, 0x0f, 0x10, 0x0b, 0xc0, 0x76, 0xa3, 0x5e, 0x0c, 0x70, 0x11, 0xad, 0xfb, 0x30, 0xd0, + 0xee, 0x96, 0x68, 0x04, 0x78, 0xe5, 0x08, 0x72, 0x85, 0x16, 0xa6, 0xe3, 0x98, 0xa8, 0x8d, 0xe3, 0x53, 0x4d, 0x3a, + 0xca, 0x4d, 0xee, 0xef, 0x26, 0xd1, 0x31, 0x4b, 0x60, 0xc8, 0xe2, 0xf2, 0xb2, 0x0d, 0x23, 0x89, 0x57, 0x6b, 0x37, + 0x4e, 0xe7, 0x0b, 0xf9, 0x99, 0x2d, 0x98, 0xb8, 0x83, 0x27, 0x9f, 0x78, 0x0b, 0x60, 0xa0, 0x8e, 0xf2, 0x02, 0x39, + 0x00, 0x80, 0x48, 0xa7, 0x08, 0x08, 0x5d, 0xc5, 0x16, 0x50, 0x1a, 0x8f, 0x57, 0xcb, 0x60, 0x27, 0xce, 0xb1, 0x34, + 0x85, 0xe7, 0x59, 0x9c, 0xe2, 0x63, 0x81, 0x8f, 0xd1, 0x39, 0x3e, 0x66, 0xf0, 0xa8, 0x71, 0xcf, 0x4b, 0xfb, 0x6f, + 0xb2, 0x02, 0x96, 0x26, 0x40, 0x76, 0x79, 0x09, 0xf2, 0x5e, 0x93, 0x60, 0x77, 0x0b, 0x88, 0x85, 0x9c, 0x22, 0x3e, + 0xba, 0xc2, 0x4c, 0x32, 0xb2, 0x62, 0xde, 0x12, 0xe5, 0x16, 0x69, 0xd5, 0x10, 0x9c, 0xae, 0xdc, 0x69, 0x18, 0x0f, + 0x9e, 0x4c, 0x2f, 0x79, 0x82, 0x2f, 0xae, 0x6d, 0x89, 0xaf, 0x62, 0x08, 0xa2, 0xd0, 0x23, 0x62, 0xa8, 0xcb, 0xb8, + 0xfc, 0xde, 0x4d, 0x1c, 0xda, 0x38, 0x0b, 0xd8, 0x6f, 0xa8, 0x05, 0x78, 0x14, 0x27, 0xa2, 0xf1, 0x1a, 0x7c, 0x1a, + 0x79, 0x82, 0x84, 0xce, 0xee, 0x56, 0x05, 0x1b, 0x00, 0x3f, 0x12, 0xb7, 0xac, 0x1d, 0x91, 0x03, 0x69, 0x8b, 0x72, + 0x3a, 0x3b, 0x97, 0x5b, 0x5a, 0x46, 0x76, 0x45, 0xac, 0x5c, 0xa3, 0x4a, 0x39, 0x8b, 0xf6, 0x24, 0x4a, 0xd7, 0x35, + 0x05, 0xe8, 0xe7, 0x8c, 0x8d, 0x3d, 0xdb, 0x02, 0x59, 0x2a, 0x9e, 0x3f, 0x26, 0xec, 0x94, 0xc9, 0x2f, 0xa5, 0xe8, + 0x41, 0x74, 0xe5, 0x08, 0x54, 0x32, 0x97, 0x97, 0x38, 0x25, 0x7b, 0x2a, 0x1c, 0x25, 0x25, 0xea, 0x88, 0x78, 0xb6, + 0x31, 0x68, 0x73, 0x8e, 0x76, 0x7d, 0x58, 0xaf, 0x03, 0xd6, 0xae, 0x2d, 0xe0, 0x25, 0x3b, 0xee, 0x66, 0xe4, 0x60, + 0x00, 0x36, 0x99, 0xc0, 0x76, 0x51, 0x91, 0x65, 0x2d, 0x0b, 0x04, 0x54, 0xe0, 0x94, 0x7a, 0xb6, 0x68, 0x61, 0x57, + 0x6d, 0xf5, 0x93, 0x24, 0x4e, 0x92, 0x8d, 0x3e, 0xad, 0x99, 0x0b, 0xb8, 0x63, 0x43, 0x44, 0x5a, 0x1b, 0xf2, 0xcd, + 0xfe, 0xb7, 0x7f, 0xfe, 0x9f, 0xff, 0x15, 0x06, 0xa6, 0x7e, 0x6e, 0x69, 0x5d, 0xdd, 0xea, 0x7f, 0x40, 0xab, 0x45, + 0x7a, 0x43, 0xbb, 0xbf, 0xfe, 0xe3, 0x7f, 0x83, 0x66, 0x74, 0x23, 0xc7, 0x2d, 0x8f, 0x08, 0xa2, 0x11, 0x1a, 0x41, + 0x9f, 0x05, 0x52, 0x6d, 0x90, 0x2b, 0x67, 0xfa, 0x27, 0x04, 0xbb, 0xe0, 0xd9, 0xfc, 0x5a, 0x70, 0x10, 0xea, 0x51, + 0x92, 0x15, 0x4c, 0xc3, 0x23, 0x64, 0xed, 0xe7, 0x01, 0x44, 0x73, 0xcd, 0x81, 0xcb, 0x0b, 0x4b, 0x8f, 0x23, 0x96, + 0x67, 0xdd, 0x38, 0x8d, 0xd5, 0x2b, 0x18, 0x27, 0x74, 0x28, 0xae, 0x00, 0xeb, 0x25, 0x9e, 0xe0, 0x81, 0x04, 0x82, + 0x5b, 0xff, 0xca, 0xd7, 0xfa, 0xc1, 0x34, 0x7f, 0x8a, 0xb1, 0x44, 0x28, 0x45, 0x8d, 0x00, 0x3f, 0x41, 0x68, 0x7d, + 0xd4, 0xcf, 0xd1, 0xb9, 0x7e, 0x46, 0xc1, 0x26, 0x26, 0x00, 0x5d, 0x34, 0x43, 0x33, 0xc3, 0x9c, 0x41, 0xa4, 0x01, + 0x54, 0x7e, 0xa4, 0x91, 0x4d, 0x22, 0x84, 0xd7, 0x47, 0x4c, 0xba, 0xc4, 0x2b, 0x36, 0x8b, 0x9c, 0x7d, 0x4c, 0xb2, + 0x33, 0x0c, 0x4e, 0x21, 0x91, 0xae, 0xaa, 0x2f, 0xff, 0xf5, 0x5f, 0x7c, 0xff, 0x5f, 0xff, 0xe5, 0x8a, 0x06, 0x53, + 0xd8, 0x07, 0x60, 0x4d, 0xf2, 0x50, 0xd3, 0xb9, 0x81, 0xd6, 0xfa, 0x41, 0x11, 0xcf, 0xf5, 0x35, 0x12, 0x71, 0x2c, + 0x95, 0x78, 0xcb, 0x47, 0x42, 0x5b, 0x33, 0xc5, 0xcd, 0xb3, 0x20, 0x64, 0x57, 0x4c, 0x83, 0x55, 0x37, 0xcc, 0x73, + 0xe4, 0x06, 0xd7, 0xd0, 0xe5, 0x33, 0x31, 0x5e, 0x0f, 0xc6, 0x8d, 0x10, 0x78, 0x20, 0xfd, 0x85, 0x7e, 0x78, 0x22, + 0xa4, 0x7b, 0x20, 0x96, 0x41, 0xca, 0xfa, 0x1a, 0x40, 0x9e, 0x75, 0x40, 0x13, 0x50, 0x93, 0xb8, 0xd2, 0xad, 0x64, + 0x8a, 0x1c, 0xe7, 0xfd, 0x57, 0x78, 0xab, 0xce, 0xc2, 0xde, 0xa8, 0x59, 0x0b, 0x32, 0x04, 0x38, 0x19, 0x02, 0x52, + 0x03, 0x99, 0x3a, 0x18, 0x3d, 0x8c, 0x6c, 0xbd, 0xae, 0xfd, 0x88, 0xdd, 0x6b, 0xda, 0x92, 0x4b, 0x6d, 0x19, 0x4b, + 0x4b, 0x56, 0x6a, 0x4b, 0xfc, 0x76, 0x4a, 0x43, 0x5b, 0xc6, 0x57, 0x6a, 0x4b, 0xa4, 0xdc, 0x00, 0x47, 0x0e, 0xed, + 0x4d, 0x0c, 0xa3, 0x18, 0xba, 0x99, 0xa3, 0x5d, 0x02, 0xbe, 0xf3, 0xe8, 0x93, 0x34, 0x4b, 0x08, 0x01, 0x8c, 0x23, + 0x68, 0x43, 0x4b, 0x60, 0x00, 0x2a, 0xf6, 0xa8, 0xd4, 0x9b, 0x1e, 0x1f, 0x8d, 0x09, 0xb8, 0xbb, 0x9c, 0x30, 0x14, + 0xc9, 0x47, 0x5b, 0x38, 0x84, 0xd8, 0x2b, 0x25, 0x3d, 0x03, 0x52, 0x5b, 0x38, 0xce, 0x91, 0xb7, 0x14, 0x01, 0xa9, + 0xc0, 0x7e, 0xfb, 0x66, 0xff, 0xc0, 0xf6, 0x8e, 0xb3, 0xf1, 0x45, 0x60, 0x83, 0x33, 0x01, 0x86, 0x87, 0xeb, 0xf3, + 0x29, 0x4b, 0x1d, 0x65, 0xce, 0x67, 0x09, 0xb8, 0x33, 0xd9, 0x89, 0xf8, 0x7e, 0x42, 0x33, 0x98, 0x0e, 0x34, 0xa5, + 0x0f, 0x2c, 0xf6, 0x77, 0xb9, 0xf8, 0xf6, 0x28, 0xcf, 0xf1, 0xb1, 0x8f, 0xe9, 0x04, 0xbb, 0x5b, 0xf0, 0x80, 0x2f, + 0xfb, 0xa8, 0x8a, 0xf4, 0x9b, 0x80, 0xb3, 0x10, 0xef, 0x5b, 0xd8, 0x7e, 0x4b, 0xf5, 0x45, 0x28, 0xfa, 0x92, 0xd1, + 0xb4, 0xc5, 0x5d, 0x99, 0x71, 0x34, 0xf6, 0x18, 0xad, 0x34, 0xb2, 0xb8, 0x81, 0x1a, 0x7c, 0xac, 0x4b, 0x84, 0xe6, + 0x37, 0x8a, 0x68, 0x94, 0x4a, 0x4f, 0xcb, 0x2a, 0x9c, 0x90, 0x2c, 0x3b, 0x31, 0x19, 0xfc, 0x24, 0xf0, 0x8f, 0xcc, + 0x6f, 0x85, 0x89, 0xcf, 0xfa, 0x68, 0x24, 0x0f, 0xff, 0xec, 0x7d, 0x64, 0xde, 0xc5, 0x11, 0xb5, 0x54, 0x8e, 0x29, + 0x46, 0x4c, 0xd0, 0x81, 0x6f, 0xab, 0x08, 0x04, 0x98, 0x26, 0x49, 0x34, 0x2f, 0x58, 0xa0, 0x1e, 0xa4, 0x8f, 0x8a, + 0xae, 0xee, 0x6a, 0x50, 0xc0, 0x34, 0x61, 0x4a, 0x3e, 0x5d, 0x9a, 0x4e, 0xec, 0x03, 0x70, 0x62, 0x31, 0x01, 0xbf, + 0x15, 0x81, 0xe2, 0x4d, 0x83, 0x84, 0x4d, 0x78, 0xc9, 0xf1, 0x86, 0xf7, 0x52, 0x45, 0x0d, 0xfc, 0xee, 0x0e, 0x38, + 0xb6, 0x96, 0x8f, 0xff, 0xdf, 0x34, 0xf6, 0x38, 0x48, 0xc1, 0x11, 0xa5, 0x2b, 0x1f, 0x78, 0x9d, 0x0e, 0x20, 0x32, + 0xdf, 0x97, 0xc6, 0x44, 0x23, 0x86, 0x11, 0x95, 0x92, 0xe7, 0x20, 0xb2, 0x3d, 0x9e, 0x9b, 0xed, 0x40, 0xd4, 0xae, + 0x84, 0x55, 0x56, 0x1d, 0xfb, 0x6d, 0x57, 0x9a, 0xff, 0xab, 0x8d, 0x55, 0x74, 0xa4, 0xfe, 0xb6, 0x42, 0x21, 0x23, + 0x8e, 0x53, 0x0a, 0x2d, 0xb3, 0x14, 0x3d, 0x4c, 0x9c, 0x56, 0x23, 0x3c, 0x37, 0x1a, 0x89, 0x25, 0xed, 0xf8, 0x43, + 0xda, 0xf1, 0x24, 0xc1, 0x86, 0x4b, 0x31, 0xf7, 0x28, 0x4a, 0x46, 0x0e, 0x02, 0x60, 0xb5, 0xac, 0x47, 0x40, 0x4d, + 0x57, 0x45, 0x19, 0xfc, 0x87, 0x48, 0xdc, 0x52, 0xc8, 0xbb, 0x35, 0x54, 0x3a, 0x1a, 0x96, 0x65, 0xef, 0x8c, 0x39, + 0x87, 0xbf, 0xc9, 0x0b, 0x03, 0xe2, 0x9e, 0xa8, 0xfe, 0xd6, 0x5e, 0xbb, 0x74, 0x87, 0xde, 0x5f, 0x8c, 0x0f, 0x98, + 0xd9, 0x8a, 0xa1, 0x6d, 0x0f, 0x96, 0xe1, 0x2f, 0x21, 0xf6, 0x7d, 0xe5, 0xd8, 0x68, 0x55, 0x52, 0xcd, 0x45, 0x8b, + 0xf8, 0xcb, 0xc6, 0x6e, 0x22, 0xdc, 0xfd, 0xfd, 0x55, 0x51, 0x8b, 0x6f, 0x6e, 0x8e, 0x5a, 0xb0, 0x5b, 0x46, 0x2d, + 0xbe, 0xf9, 0x83, 0xa3, 0x16, 0xdf, 0x37, 0xa3, 0x16, 0xbf, 0x7e, 0x4e, 0xd4, 0x22, 0xcf, 0xce, 0x8a, 0xb0, 0x23, + 0x4f, 0xc9, 0x41, 0xe6, 0xfc, 0x6d, 0xc2, 0x17, 0x30, 0x51, 0x23, 0x78, 0x41, 0xd1, 0x0a, 0x91, 0xd8, 0x07, 0x92, + 0x5d, 0xc6, 0x0a, 0xda, 0x3a, 0x83, 0xae, 0x75, 0x5f, 0x5d, 0x19, 0x02, 0x6f, 0xcd, 0xd5, 0x97, 0xdd, 0xba, 0x2a, + 0x9a, 0x10, 0xd0, 0x37, 0x3f, 0x75, 0xc7, 0xee, 0xa6, 0x4a, 0xdd, 0x32, 0x47, 0xe8, 0xa9, 0x88, 0xbc, 0x60, 0x9f, + 0xa5, 0xfd, 0x9f, 0x0e, 0x3b, 0xbd, 0xed, 0xce, 0x0c, 0x7a, 0x83, 0x0e, 0x85, 0xb7, 0x76, 0x6f, 0x7b, 0x1b, 0xdf, + 0xce, 0xd4, 0x5b, 0x17, 0xdf, 0x62, 0xf5, 0xb6, 0x83, 0x6f, 0x23, 0xf5, 0xf6, 0x00, 0xdf, 0xc6, 0xea, 0xed, 0x21, + 0xbe, 0x9d, 0xda, 0xe5, 0x21, 0xd7, 0xc0, 0x3d, 0x04, 0xbe, 0x22, 0x43, 0x3f, 0x50, 0x65, 0xb0, 0x69, 0xf1, 0xaa, + 0x5d, 0x74, 0x12, 0xc4, 0x9e, 0x70, 0x88, 0x82, 0xdc, 0x3b, 0x03, 0xc9, 0x1f, 0x50, 0x66, 0xd9, 0x53, 0xfc, 0xe6, + 0x02, 0xf8, 0x0f, 0x07, 0xf1, 0x8c, 0xa9, 0x8f, 0xcf, 0x2a, 0xac, 0xc1, 0x86, 0x3c, 0x6c, 0x0f, 0xcb, 0x9e, 0x5e, + 0x27, 0x11, 0x2c, 0x51, 0x27, 0xf7, 0xb4, 0x72, 0x55, 0x9d, 0x98, 0xae, 0xa5, 0x57, 0xf8, 0x0a, 0x3d, 0x62, 0xb8, + 0xd0, 0x13, 0xb0, 0x8d, 0x5a, 0xe7, 0xe0, 0x74, 0xad, 0xd5, 0x2d, 0x08, 0x91, 0xd6, 0x26, 0x84, 0x93, 0x7e, 0x3b, + 0x88, 0x4e, 0xf4, 0xf3, 0x2b, 0x30, 0x76, 0xa3, 0x13, 0x76, 0x93, 0x9e, 0x21, 0x10, 0x4d, 0x1d, 0xa3, 0x80, 0x20, + 0x6b, 0x08, 0x96, 0x06, 0x9d, 0x3f, 0xa9, 0x63, 0x90, 0x3a, 0x75, 0xad, 0x43, 0xd3, 0xd7, 0x8b, 0x80, 0xa2, 0x55, + 0xc1, 0x2e, 0xd8, 0xdc, 0x54, 0x2a, 0x28, 0x0c, 0x15, 0x58, 0x70, 0xad, 0x2a, 0xd2, 0xfe, 0xf1, 0x95, 0x0a, 0xc9, + 0x52, 0xba, 0xc8, 0x8c, 0xe4, 0xeb, 0x30, 0xfe, 0xaa, 0x78, 0xfc, 0xa2, 0x33, 0xc2, 0x3f, 0x52, 0xf8, 0x7e, 0x31, + 0x99, 0x4c, 0xae, 0xd5, 0x4d, 0x5f, 0x8c, 0x27, 0xac, 0xcb, 0x76, 0x7a, 0x18, 0xe5, 0x6d, 0x49, 0x71, 0xd8, 0x29, + 0x89, 0x76, 0xcb, 0xdb, 0x35, 0x46, 0xc9, 0x09, 0xea, 0xea, 0xf6, 0x4a, 0xac, 0x04, 0xaa, 0x2c, 0x41, 0x78, 0x9f, + 0xc4, 0x69, 0xd0, 0x2e, 0xfd, 0x53, 0x29, 0xf5, 0xbf, 0x78, 0xf4, 0xe8, 0x51, 0xe9, 0x8f, 0xd5, 0x5b, 0x7b, 0x3c, + 0x2e, 0xfd, 0xd1, 0x52, 0xa3, 0xd1, 0x6e, 0x4f, 0x26, 0xa5, 0x1f, 0xab, 0x82, 0xed, 0xee, 0x68, 0xbc, 0xdd, 0x2d, + 0xfd, 0x33, 0xa3, 0x45, 0xe9, 0x33, 0xf9, 0x96, 0xb3, 0x71, 0x2d, 0x54, 0xfc, 0xb0, 0x0d, 0x95, 0x82, 0xd1, 0x96, + 0xe8, 0xe0, 0x89, 0xc7, 0x20, 0x5a, 0xf0, 0x0c, 0x6c, 0xab, 0xb2, 0xc7, 0x40, 0x3e, 0x4f, 0xa4, 0x6c, 0x17, 0xdf, + 0x76, 0x45, 0x89, 0xfe, 0xab, 0x29, 0xd1, 0x91, 0x99, 0x49, 0x9a, 0x33, 0xd2, 0x03, 0xcd, 0x6a, 0xe4, 0x2c, 0xaa, + 0xfe, 0x35, 0x64, 0x95, 0xb0, 0x47, 0x69, 0x83, 0x2d, 0x85, 0x8c, 0xff, 0xf6, 0x2a, 0x19, 0xff, 0xdd, 0xcd, 0x32, + 0xfe, 0xf8, 0x76, 0x22, 0xfe, 0xbb, 0x3f, 0x58, 0xc4, 0x7f, 0x6b, 0x8a, 0x78, 0x21, 0xc4, 0x2e, 0xc0, 0x7a, 0x25, + 0xb3, 0xf5, 0x38, 0x3b, 0x6f, 0xe1, 0x96, 0xc8, 0x6d, 0x92, 0x9e, 0x1b, 0xb7, 0x12, 0xfe, 0x6b, 0x72, 0x7f, 0xd4, + 0x60, 0xc6, 0x87, 0x62, 0x79, 0x76, 0x72, 0x92, 0x30, 0x25, 0xe3, 0x8d, 0x0a, 0xb2, 0x88, 0xdf, 0xa4, 0xa1, 0xfd, + 0x06, 0x9c, 0x53, 0xa3, 0x64, 0x32, 0x81, 0xa2, 0xc9, 0xc4, 0x56, 0xb9, 0xb1, 0x20, 0xcf, 0xa8, 0xd5, 0xeb, 0x5a, + 0x09, 0xb5, 0xfa, 0xfa, 0x6b, 0xb3, 0xcc, 0x2c, 0x90, 0x51, 0x28, 0xd3, 0x98, 0x90, 0x35, 0xe3, 0xb8, 0xc0, 0x3d, + 0x58, 0x7d, 0xd8, 0x16, 0xed, 0x95, 0x19, 0x28, 0x95, 0x78, 0x84, 0x5f, 0x4c, 0x69, 0x7e, 0x44, 0x44, 0xe4, 0x31, + 0xaf, 0x22, 0x57, 0x9d, 0x75, 0x1a, 0xdf, 0xab, 0xab, 0xce, 0x37, 0x61, 0xf1, 0x65, 0x2e, 0xc3, 0xe3, 0x8b, 0x17, + 0x63, 0xe7, 0x02, 0xec, 0xd8, 0xb8, 0x78, 0x93, 0x36, 0x72, 0xc4, 0x04, 0xd8, 0x61, 0x68, 0x62, 0x5a, 0x0a, 0x82, + 0x55, 0xc9, 0xf2, 0x55, 0x65, 0xcf, 0xe8, 0x24, 0x53, 0x89, 0x70, 0xc8, 0x41, 0x8d, 0x2c, 0x81, 0x39, 0x98, 0xd4, + 0x85, 0xf4, 0xe1, 0x72, 0x91, 0x60, 0x71, 0x2a, 0xbf, 0x70, 0x4d, 0x91, 0xff, 0xa5, 0xd4, 0x1f, 0xf2, 0xe8, 0xbd, + 0xea, 0x89, 0xc1, 0x76, 0x31, 0xc3, 0xb8, 0x54, 0x01, 0x76, 0x20, 0xdc, 0x1c, 0x3f, 0xc7, 0x23, 0x86, 0x50, 0x71, + 0xec, 0x8a, 0x7a, 0xf8, 0xf9, 0x93, 0xea, 0xab, 0x90, 0xb5, 0x2f, 0x08, 0x36, 0x78, 0x00, 0xbf, 0xec, 0xcf, 0x51, + 0x1b, 0x64, 0x0b, 0xee, 0x38, 0xd4, 0xca, 0x71, 0x4b, 0xaf, 0xbb, 0xd3, 0x06, 0x15, 0xe3, 0x8b, 0x6f, 0xfe, 0x38, + 0xba, 0xb3, 0xc4, 0xf7, 0xaa, 0xb0, 0xf9, 0xca, 0x37, 0xb8, 0x34, 0x89, 0xf1, 0x23, 0x21, 0x02, 0x51, 0xe3, 0x9e, + 0x88, 0x5a, 0xc4, 0xe6, 0xbb, 0xaf, 0xdc, 0x37, 0x83, 0xb0, 0xee, 0x3a, 0x0e, 0x96, 0x31, 0xb2, 0x7a, 0x21, 0xb6, + 0x15, 0x56, 0xcd, 0x2a, 0x38, 0x37, 0xe8, 0xcc, 0xe2, 0xcc, 0x88, 0x39, 0xd7, 0xb6, 0x41, 0xa9, 0x82, 0xce, 0x22, + 0x72, 0x7c, 0x81, 0xf1, 0x51, 0xe1, 0xfb, 0x2a, 0xa0, 0xeb, 0x5e, 0xa7, 0x01, 0x39, 0xfa, 0xa3, 0x9a, 0xd1, 0x55, + 0x95, 0x2a, 0x28, 0xcd, 0x13, 0x02, 0x03, 0x19, 0x0a, 0xfe, 0xc2, 0x1a, 0xa7, 0x42, 0x6f, 0xc1, 0x34, 0x24, 0x80, + 0xb5, 0x47, 0x86, 0x6e, 0x89, 0xad, 0xc0, 0x16, 0xd2, 0x02, 0x94, 0x1e, 0x76, 0xe8, 0x5b, 0x35, 0xd0, 0xd3, 0xd5, + 0x98, 0xf1, 0x75, 0x4e, 0xda, 0xc5, 0x91, 0x5f, 0x9c, 0x79, 0xf0, 0xcf, 0xfa, 0x72, 0x09, 0x52, 0xfe, 0xf8, 0x53, + 0xcc, 0xc1, 0xa6, 0x9e, 0xb7, 0x30, 0x02, 0x42, 0x21, 0x4c, 0xa9, 0x0e, 0xe9, 0xd8, 0x51, 0x5c, 0x0f, 0xea, 0x2d, + 0x0a, 0xf4, 0xe5, 0xc8, 0x69, 0x09, 0xd2, 0x2c, 0x65, 0xbd, 0xfa, 0xf1, 0xb2, 0xe9, 0x37, 0x28, 0x62, 0x0d, 0x97, + 0x19, 0xfa, 0x7e, 0xfc, 0x02, 0x7c, 0x3f, 0xa1, 0x46, 0xdb, 0xca, 0x69, 0x68, 0xaf, 0x6d, 0x1f, 0x48, 0xda, 0x6e, + 0x92, 0xb5, 0x90, 0xaf, 0x3a, 0x47, 0x57, 0x39, 0x37, 0x37, 0x1d, 0xb6, 0x76, 0x77, 0x76, 0x3c, 0xf5, 0xcf, 0x38, + 0xa5, 0x6e, 0x16, 0xd3, 0x61, 0xeb, 0x6d, 0x20, 0x0b, 0xa2, 0x09, 0x7e, 0x4d, 0xef, 0x36, 0x2d, 0x8f, 0x29, 0xd3, + 0x71, 0x89, 0x6a, 0x3d, 0xe8, 0x3c, 0x02, 0x6f, 0xed, 0xd6, 0xc3, 0x5f, 0x8f, 0x7e, 0x29, 0x69, 0xa4, 0x2e, 0xac, + 0xda, 0x76, 0x0f, 0xe5, 0x45, 0x12, 0x5d, 0x80, 0xd3, 0x48, 0x36, 0xc6, 0x31, 0x06, 0x70, 0x7b, 0xf3, 0x4c, 0x66, + 0x0d, 0xe4, 0x2c, 0xa1, 0x5f, 0x59, 0x21, 0x97, 0x62, 0xfb, 0xc1, 0xfc, 0x5c, 0xad, 0x46, 0xa7, 0x91, 0x0d, 0xf0, + 0x87, 0x1e, 0xfa, 0x5f, 0x9d, 0x65, 0x50, 0x3f, 0xb8, 0xde, 0x01, 0x18, 0x84, 0x61, 0xd3, 0xca, 0x05, 0x54, 0x6d, + 0x28, 0x31, 0xd2, 0x1e, 0xaa, 0x81, 0x2c, 0x7f, 0x1b, 0x54, 0x65, 0x54, 0xb0, 0x1e, 0x7e, 0xd6, 0x30, 0x06, 0xd7, + 0x54, 0x1a, 0x4f, 0xb3, 0x78, 0x3c, 0x4e, 0x58, 0x4f, 0xd9, 0x47, 0x56, 0xe7, 0x01, 0x66, 0x0d, 0x98, 0x4b, 0x56, + 0x5f, 0x15, 0x83, 0x78, 0x9a, 0x4e, 0xd1, 0x31, 0xd8, 0x6b, 0xf8, 0x6d, 0xc2, 0xb5, 0xe4, 0x94, 0xc7, 0xe9, 0xed, + 0x8a, 0x78, 0xf4, 0x5c, 0xc7, 0x65, 0x07, 0x8c, 0x45, 0x5a, 0xf0, 0x76, 0x8f, 0x67, 0xf3, 0xa0, 0xb5, 0x5d, 0x47, + 0x04, 0xab, 0x34, 0x0a, 0xde, 0x1a, 0xb4, 0x3c, 0xb4, 0x0e, 0x84, 0x96, 0xb3, 0xfc, 0x8e, 0x2c, 0xa3, 0x01, 0xf0, + 0xfb, 0x77, 0xba, 0xa8, 0xac, 0x23, 0xf3, 0xff, 0x67, 0xb7, 0x7c, 0xb5, 0x7e, 0xb7, 0x7c, 0xa5, 0x76, 0xcb, 0xf5, + 0x1c, 0xfb, 0xc5, 0xa4, 0x83, 0x7f, 0x7a, 0x15, 0x42, 0xb0, 0x2a, 0x40, 0x0e, 0x0b, 0xed, 0xe2, 0x56, 0x17, 0xfe, + 0xa3, 0xa1, 0xdb, 0x1e, 0xfe, 0xf1, 0xc1, 0x02, 0x6c, 0x5b, 0x58, 0x88, 0xff, 0xda, 0xb5, 0xaa, 0xce, 0x7d, 0xac, + 0xc3, 0x5e, 0x3b, 0xab, 0x75, 0xdd, 0xeb, 0x37, 0x2d, 0xc8, 0x2b, 0xee, 0x04, 0x4a, 0x18, 0x83, 0xab, 0x16, 0x1d, + 0x1f, 0x43, 0xe9, 0x24, 0x1b, 0x2d, 0x8a, 0xbf, 0x97, 0xf0, 0x4b, 0x22, 0x5e, 0xbb, 0xa5, 0x1b, 0xe3, 0xa8, 0xae, + 0x22, 0x05, 0x45, 0x8d, 0xb0, 0xd4, 0xeb, 0x14, 0x14, 0xc0, 0x98, 0xcc, 0xe9, 0xfa, 0xf7, 0xd7, 0x6c, 0x82, 0xbf, + 0xc9, 0xda, 0xac, 0x45, 0xe6, 0xdf, 0x4b, 0x8c, 0x6b, 0x89, 0xf0, 0x59, 0x34, 0x30, 0xd7, 0xb0, 0xfd, 0x68, 0x3d, + 0xb8, 0x87, 0x6a, 0xa6, 0xa1, 0x52, 0x0a, 0x52, 0xef, 0x80, 0x17, 0x10, 0x2d, 0x12, 0x7e, 0xfd, 0xa8, 0x57, 0x71, + 0xc6, 0xca, 0xa8, 0xd7, 0x08, 0xf4, 0xaa, 0xed, 0x2d, 0xa5, 0xf4, 0x17, 0x5f, 0xdd, 0xc7, 0x3f, 0x22, 0xf0, 0x75, + 0x5c, 0xf9, 0x46, 0x22, 0x36, 0x80, 0xbe, 0xd1, 0x46, 0xcd, 0xf9, 0x11, 0x1a, 0x9c, 0xfc, 0x9f, 0xdb, 0xb6, 0x46, + 0x63, 0xfd, 0x56, 0xcd, 0xa5, 0x55, 0xfa, 0x59, 0xad, 0x3f, 0x6f, 0xf0, 0x5b, 0xb6, 0x1d, 0x09, 0x87, 0xa0, 0xde, + 0x56, 0xfe, 0xfa, 0x90, 0x95, 0xc6, 0x8a, 0xe2, 0xb7, 0x6d, 0x5f, 0x99, 0xc4, 0xd4, 0x63, 0x23, 0x3c, 0xd6, 0x4e, + 0xa4, 0x3c, 0x6f, 0xc6, 0x1e, 0xc2, 0x8f, 0xfc, 0x91, 0x85, 0xf7, 0xf0, 0xcb, 0x5b, 0xd6, 0xf9, 0x2c, 0x49, 0xc1, + 0xac, 0x9a, 0x72, 0x3e, 0x0f, 0xb6, 0xb6, 0xce, 0xce, 0xce, 0xfc, 0xb3, 0x6d, 0x3f, 0xcb, 0x4f, 0xb6, 0xba, 0xed, + 0x76, 0x1b, 0x3f, 0x98, 0x64, 0x5b, 0xa7, 0x31, 0x3b, 0x7b, 0x0c, 0xee, 0x87, 0xfd, 0xd0, 0x7a, 0x64, 0x3d, 0xdc, + 0xb6, 0x76, 0x1e, 0xd8, 0x16, 0x29, 0x00, 0x28, 0xd9, 0xb6, 0x2d, 0xa1, 0x00, 0x42, 0x1b, 0x8a, 0xfb, 0xbb, 0x27, + 0xca, 0x86, 0xc3, 0x84, 0x74, 0x61, 0x21, 0x81, 0xff, 0x96, 0x7d, 0x62, 0xf5, 0xad, 0x2e, 0xca, 0x5a, 0x52, 0x8d, + 0xa8, 0x57, 0xdc, 0xef, 0xa3, 0x68, 0x1e, 0x10, 0x1b, 0x99, 0x85, 0x18, 0x26, 0x13, 0xa5, 0x34, 0x05, 0xda, 0xa5, + 0xc7, 0xf0, 0x04, 0x6e, 0xc1, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x87, 0xa0, 0xe3, 0x4e, 0x5b, 0xf7, 0x47, 0xed, 0x56, + 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x43, 0xab, 0x2b, 0xfe, 0x07, 0x19, 0xb9, 0x6d, 0x75, 0xe0, 0x69, 0xdb, 0x82, 0xf7, + 0xd3, 0xfb, 0x22, 0x1d, 0x22, 0xb2, 0xb7, 0xfa, 0xbb, 0xf8, 0xfb, 0x83, 0x00, 0xa9, 0xaf, 0x6c, 0xf1, 0x1b, 0xcf, + 0xec, 0x2f, 0xcc, 0xd2, 0xce, 0xa3, 0xb5, 0xc5, 0xdd, 0x87, 0x6b, 0x8b, 0xb7, 0x1f, 0xac, 0x2d, 0xbe, 0xbf, 0x53, + 0x2f, 0xde, 0x3a, 0x11, 0x55, 0x5a, 0x2e, 0x84, 0xf6, 0x2c, 0x02, 0x46, 0x39, 0x77, 0x3a, 0x00, 0x67, 0xdb, 0x6a, + 0xe1, 0x8f, 0x87, 0x5d, 0x57, 0xf7, 0x3a, 0xc6, 0x5e, 0x1a, 0xcb, 0x87, 0x8f, 0x00, 0xcb, 0xe7, 0xdd, 0x07, 0x23, + 0x6c, 0x47, 0x88, 0xc2, 0xbf, 0xd3, 0xed, 0x47, 0x23, 0xd0, 0x08, 0x16, 0xfe, 0x83, 0x3f, 0xd3, 0x9d, 0xee, 0x48, + 0xbc, 0xb4, 0xb1, 0xfe, 0x43, 0xe7, 0x61, 0x01, 0x4d, 0xf1, 0xcf, 0x6f, 0xda, 0x84, 0x46, 0x03, 0xde, 0x1c, 0xf7, + 0x3e, 0xd0, 0xe8, 0xd1, 0xb4, 0xeb, 0x7f, 0x75, 0xfa, 0xd0, 0x7f, 0x34, 0xed, 0x3c, 0xfc, 0x20, 0xde, 0x12, 0xa0, + 0xe0, 0x57, 0xf8, 0xef, 0xc3, 0x76, 0x7b, 0xda, 0xea, 0xf8, 0x8f, 0x4e, 0xb7, 0xfd, 0xed, 0xa4, 0xf5, 0xc0, 0x7f, + 0x84, 0xff, 0xaa, 0xe1, 0xa6, 0xd9, 0x8c, 0xd9, 0x16, 0xae, 0x77, 0xc3, 0xef, 0x35, 0xe7, 0xe8, 0xde, 0xb7, 0x76, + 0xee, 0x3f, 0x7f, 0x04, 0x6b, 0x34, 0xed, 0x74, 0xe1, 0xff, 0xab, 0x1e, 0x3f, 0x20, 0xe1, 0xe5, 0xc0, 0x11, 0xc3, + 0x54, 0x52, 0x45, 0x38, 0xfa, 0x78, 0xd7, 0x3d, 0xef, 0x87, 0xab, 0x02, 0x20, 0x7f, 0xbe, 0x39, 0x00, 0xf2, 0x97, + 0x5b, 0x06, 0xb9, 0xff, 0xfc, 0x07, 0x47, 0x40, 0x7e, 0x68, 0x06, 0xb9, 0xf7, 0xd8, 0x4a, 0xa0, 0xa3, 0xe9, 0xac, + 0x3d, 0x67, 0xce, 0xe1, 0x8f, 0x6c, 0x88, 0x69, 0xd3, 0xd0, 0xfa, 0x2f, 0xb5, 0x78, 0x50, 0x86, 0x1b, 0x79, 0x8f, + 0x89, 0x9d, 0xcc, 0xf8, 0x15, 0x04, 0xe1, 0xfc, 0x46, 0x82, 0xbc, 0xb8, 0x1d, 0x3d, 0x38, 0xff, 0x63, 0xe9, 0x41, + 0x5f, 0xee, 0x57, 0xf4, 0xa8, 0x45, 0xdc, 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0xf6, 0x16, 0xc3, 0xb7, + 0xc2, 0x16, 0xb9, 0x80, 0xef, 0x3e, 0xe7, 0x74, 0x40, 0x64, 0x15, 0x87, 0xb6, 0x0c, 0xc0, 0xcc, 0xf1, 0xdb, 0xb4, + 0xea, 0xe5, 0x54, 0xdc, 0x5c, 0x09, 0xe9, 0xda, 0xd9, 0x8e, 0x0e, 0xde, 0x60, 0xa2, 0x77, 0xb8, 0xcc, 0x78, 0x84, + 0xbf, 0xfc, 0x88, 0xc7, 0x3c, 0xc1, 0x4b, 0xb1, 0xf2, 0x02, 0x19, 0xe6, 0x25, 0x7f, 0x87, 0x39, 0xd5, 0xea, 0x90, + 0x60, 0x86, 0x01, 0x83, 0x57, 0x6c, 0x1c, 0x47, 0x8e, 0xed, 0xcc, 0x61, 0xc7, 0xc2, 0x98, 0xad, 0x5a, 0x42, 0x33, + 0xe5, 0x32, 0xbb, 0xb6, 0xfa, 0x7d, 0x3b, 0x39, 0x7e, 0xbf, 0x2c, 0x3c, 0x94, 0x01, 0x46, 0x5b, 0x7a, 0x00, 0x30, + 0xbe, 0x2a, 0xc9, 0x51, 0xd8, 0x57, 0x56, 0x83, 0x2d, 0xcc, 0x86, 0x8e, 0xdf, 0x05, 0x37, 0x82, 0x8a, 0xf1, 0x7b, + 0x50, 0x3f, 0x38, 0xad, 0x6d, 0x30, 0x6b, 0x8c, 0x6e, 0x7a, 0xa0, 0xe1, 0x4a, 0x18, 0x49, 0x04, 0x07, 0x1a, 0xa5, + 0x9e, 0xfe, 0x05, 0x64, 0x55, 0xb8, 0xa8, 0x78, 0x7c, 0x71, 0x20, 0xef, 0x7c, 0xdb, 0x18, 0xb9, 0xa5, 0x88, 0x7d, + 0xf5, 0xbd, 0xa9, 0x4d, 0x50, 0x17, 0xf4, 0x5b, 0x20, 0xe9, 0xdc, 0x1b, 0x35, 0x02, 0xa6, 0x5c, 0x5b, 0xd2, 0x73, + 0x08, 0x6d, 0xa1, 0x0f, 0xc6, 0xec, 0x34, 0x1e, 0x49, 0xb1, 0xee, 0x59, 0xf2, 0xaa, 0x48, 0x8b, 0xb0, 0x08, 0x3b, + 0x9e, 0xf0, 0x9d, 0xe1, 0x05, 0xb5, 0x5a, 0x98, 0x66, 0x76, 0xff, 0x5e, 0x4f, 0x43, 0x52, 0xcf, 0x56, 0xb7, 0xf1, + 0x57, 0x52, 0x1e, 0x82, 0xaf, 0xf6, 0xf7, 0xe1, 0x3d, 0xfc, 0xa5, 0x94, 0xf7, 0x86, 0xb6, 0xeb, 0x93, 0x50, 0xbc, + 0x57, 0xfd, 0x66, 0x4a, 0x94, 0x08, 0x9b, 0xa0, 0xbf, 0xbc, 0xdb, 0x2a, 0x32, 0xa9, 0xb4, 0xba, 0x3b, 0x95, 0xd2, + 0x82, 0x67, 0x43, 0x4a, 0x81, 0x00, 0xed, 0xfa, 0x3b, 0x86, 0x28, 0x3c, 0x6d, 0xe1, 0xcf, 0x9a, 0x30, 0xbc, 0x0f, + 0x0d, 0x94, 0x34, 0x7c, 0x09, 0xcd, 0xb7, 0x85, 0xe0, 0x85, 0x7e, 0x3f, 0x92, 0xa8, 0x12, 0x62, 0xaa, 0xce, 0x31, + 0x6b, 0x0e, 0x91, 0x44, 0x8e, 0x80, 0xed, 0x19, 0xf1, 0x26, 0xc1, 0xae, 0x32, 0x9a, 0xf2, 0x14, 0xfa, 0x3a, 0xfa, + 0x33, 0xce, 0xeb, 0xea, 0xbc, 0xda, 0xce, 0x59, 0x33, 0x05, 0x32, 0x7c, 0xe3, 0xa0, 0x8a, 0xae, 0x2e, 0x88, 0xcf, + 0x99, 0x89, 0x6d, 0x5c, 0x7d, 0xf0, 0x6d, 0x4d, 0xf6, 0xad, 0xb9, 0x29, 0x58, 0xc5, 0x34, 0xb4, 0x2f, 0x30, 0x65, + 0x06, 0x7f, 0x56, 0xc5, 0xea, 0x41, 0x32, 0x94, 0x9f, 0x44, 0xf8, 0xdb, 0x58, 0xe8, 0x47, 0x59, 0x6d, 0x40, 0x4e, + 0xdf, 0xab, 0x24, 0x48, 0x5f, 0x8c, 0xcb, 0x26, 0x12, 0x60, 0x2f, 0xe0, 0x2f, 0xf7, 0xab, 0xae, 0x4a, 0xc8, 0x3b, + 0x90, 0x98, 0x53, 0x30, 0x8e, 0x73, 0xba, 0x5a, 0xab, 0xf0, 0xaf, 0x45, 0x34, 0x2b, 0x52, 0xd3, 0xae, 0x64, 0xc5, + 0xc0, 0xc6, 0x22, 0x3b, 0x90, 0xc9, 0x68, 0xe6, 0x07, 0x9b, 0xcd, 0xbb, 0x8f, 0x63, 0x91, 0x87, 0x86, 0x1f, 0xb4, + 0xb7, 0x05, 0x91, 0x6d, 0x10, 0x63, 0x57, 0xe2, 0x44, 0xc6, 0x0d, 0x5e, 0x19, 0xac, 0x7e, 0x43, 0x91, 0xb9, 0xe1, + 0x6d, 0x73, 0xb5, 0xf4, 0xb8, 0xb4, 0x0e, 0xae, 0x8c, 0xdf, 0x1d, 0xb3, 0x88, 0xfb, 0x51, 0x4a, 0xb9, 0x49, 0x8e, + 0x21, 0x16, 0xbc, 0x0e, 0xdb, 0x76, 0x4b, 0x90, 0x3c, 0xc6, 0xaf, 0x70, 0x12, 0xa4, 0xf7, 0xa1, 0xb0, 0x4a, 0xd8, + 0xda, 0x9d, 0x76, 0xfb, 0x6f, 0x0e, 0xf6, 0x2c, 0xb1, 0x9b, 0x77, 0xb7, 0xe0, 0x75, 0x97, 0xdc, 0x61, 0x91, 0x9f, + 0x11, 0x8a, 0xfc, 0x0c, 0x4b, 0x24, 0x74, 0x85, 0xf6, 0x96, 0x40, 0xd3, 0xb6, 0x58, 0x3a, 0x12, 0x31, 0xbc, 0x19, + 0xb8, 0x0b, 0x31, 0x7e, 0xd4, 0x6b, 0x0b, 0xbb, 0xb5, 0x70, 0xa5, 0x6d, 0x95, 0xe1, 0xa2, 0x0c, 0x04, 0x9e, 0xaa, + 0x88, 0x1f, 0xa8, 0x75, 0xa6, 0x92, 0x5d, 0xe4, 0x50, 0x3a, 0x27, 0x75, 0xb5, 0x75, 0xb1, 0x38, 0x9e, 0x81, 0x1c, + 0x52, 0x09, 0x2a, 0xef, 0x65, 0x87, 0x5d, 0x9a, 0x0a, 0x93, 0x62, 0x57, 0x23, 0x92, 0xd3, 0x4e, 0x7f, 0x37, 0x92, + 0xf6, 0x0e, 0xee, 0xdd, 0x02, 0x36, 0x2f, 0xa8, 0x39, 0x34, 0x2a, 0xfc, 0x38, 0xdb, 0x3a, 0x63, 0xc7, 0xad, 0x68, + 0x1e, 0x57, 0xe1, 0x3f, 0xd4, 0x7e, 0xfd, 0x5d, 0xa5, 0x08, 0x65, 0x9a, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24, + 0x1c, 0x31, 0x68, 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0, + 0xb4, 0xfb, 0x7f, 0xfd, 0x5f, 0xff, 0x5b, 0x86, 0x15, 0x81, 0xac, 0x00, 0x16, 0xa6, 0xc1, 0x54, 0x27, 0x8c, 0xec, + 0x1c, 0x1c, 0xd1, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, 0x20, 0x28, 0x98, 0xb8, 0xca, 0x24, 0xeb, 0x81, 0x0b, 0x24, + 0x58, 0xe6, 0xe1, 0xbc, 0x04, 0xaf, 0x5e, 0x84, 0x2b, 0xf6, 0xbb, 0xf2, 0x56, 0x55, 0xbe, 0x30, 0x31, 0xb4, 0x91, + 0xc5, 0x6a, 0xf0, 0x5c, 0x2d, 0x93, 0x55, 0xfd, 0x82, 0x24, 0x29, 0x3c, 0x58, 0x2d, 0x8d, 0x15, 0x5a, 0xea, 0x83, + 0x90, 0x7f, 0xfb, 0xe7, 0xff, 0xfc, 0xdf, 0xd5, 0x2b, 0x9e, 0x6f, 0xfc, 0xf5, 0x9f, 0xfe, 0xe1, 0xff, 0xfe, 0x9f, + 0xff, 0x82, 0x59, 0xc2, 0xf2, 0x0c, 0x84, 0xb6, 0x92, 0x55, 0x1d, 0x80, 0x88, 0x3d, 0x65, 0x55, 0x0e, 0x47, 0x3d, + 0xdd, 0x75, 0x9f, 0x26, 0x24, 0xde, 0x94, 0xd0, 0x11, 0x5f, 0x53, 0x7a, 0x34, 0x51, 0xed, 0x1a, 0xf2, 0xc1, 0x52, + 0x5a, 0x74, 0xac, 0x6f, 0xef, 0xb4, 0xed, 0x6a, 0x79, 0xfb, 0x46, 0xdf, 0x2d, 0x5c, 0x98, 0x5b, 0x65, 0xe0, 0xf8, + 0x7a, 0xd9, 0x96, 0x2a, 0x8c, 0x85, 0x25, 0x65, 0x55, 0x6e, 0x61, 0x7c, 0x79, 0x89, 0xaf, 0x41, 0xd7, 0x28, 0xa6, + 0x55, 0xae, 0xf5, 0xe9, 0xfd, 0xb2, 0x00, 0x44, 0x27, 0xb8, 0x34, 0x22, 0x58, 0x46, 0x67, 0xa7, 0x2d, 0xb4, 0x4e, + 0x92, 0x8b, 0x92, 0x46, 0x11, 0xde, 0xcc, 0xfd, 0x47, 0x7f, 0x57, 0xfe, 0x69, 0x86, 0x56, 0x81, 0xe5, 0xcc, 0xa2, + 0x73, 0xe9, 0xe3, 0x3c, 0x68, 0xb7, 0xe7, 0xe7, 0xee, 0xb2, 0x9a, 0xc1, 0xbb, 0x6a, 0x32, 0x0a, 0xb0, 0x99, 0x03, + 0xd2, 0xa1, 0xab, 0x8e, 0xe5, 0x81, 0x59, 0xdf, 0xc6, 0xd0, 0x4f, 0x59, 0x7e, 0xb9, 0xa4, 0x70, 0x52, 0xfc, 0x1b, + 0x1e, 0x8e, 0xca, 0xc8, 0x1b, 0x94, 0x18, 0x58, 0x2c, 0x8d, 0x5e, 0x5d, 0xd1, 0x6b, 0xda, 0x59, 0xcd, 0x4d, 0x31, + 0x0f, 0x77, 0xcd, 0x63, 0xd9, 0xfb, 0x78, 0xd0, 0x3a, 0xed, 0x78, 0xd3, 0xee, 0x52, 0x0f, 0xcf, 0x79, 0x36, 0x33, + 0x4f, 0x73, 0x59, 0xc4, 0x46, 0x6c, 0xa2, 0x22, 0x96, 0xb2, 0x5e, 0x9c, 0xd4, 0x96, 0x5f, 0xe0, 0x76, 0x03, 0xda, + 0x66, 0x11, 0x0f, 0x88, 0x69, 0x7b, 0xe6, 0x79, 0x6f, 0x84, 0x27, 0xe9, 0xd9, 0xd2, 0x98, 0xab, 0x27, 0x9a, 0x62, + 0x5c, 0xb0, 0x9e, 0xf7, 0x53, 0xfa, 0xd4, 0xdd, 0x1c, 0x4a, 0x84, 0x15, 0x5e, 0xc8, 0x63, 0xd4, 0x77, 0x35, 0x7f, + 0x5c, 0x8a, 0x62, 0x70, 0x81, 0xd7, 0xd6, 0x0b, 0xb5, 0x28, 0x6a, 0x5f, 0x80, 0xb5, 0x43, 0x60, 0xda, 0xcd, 0x56, + 0x54, 0x88, 0xad, 0xde, 0x85, 0x2f, 0xb4, 0xed, 0x1d, 0xcd, 0xe7, 0xd4, 0xd0, 0x05, 0x6e, 0x24, 0x1b, 0x1a, 0x25, + 0x05, 0xa5, 0x08, 0x88, 0x13, 0x79, 0xd9, 0x46, 0xb2, 0xad, 0x78, 0x92, 0x67, 0xf5, 0xf4, 0xfb, 0xb6, 0xff, 0x1f, + 0x22, 0x28, 0x4d, 0x5d, 0x85, 0x7b, 0x00, 0x00}; } // namespace web_server } // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 5c06a97d47..30ac959e43 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -360,9 +360,14 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { - std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); - if (!obj->get_unit_of_measurement().empty()) - state += " " + obj->get_unit_of_measurement(); + std::string state; + if (isnan(value)) { + state = "NA"; + } else { + state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); + if (!obj->get_unit_of_measurement().empty()) + state += " " + obj->get_unit_of_measurement(); + } set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); }); } @@ -719,12 +724,15 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail root["step"] = obj->traits.get_step(); root["mode"] = (int) obj->traits.get_mode(); } - std::string state = str_sprintf("%f", value); - root["state"] = state; if (isnan(value)) { root["value"] = "\"NaN\""; + root["state"] = "NA"; } else { root["value"] = value; + 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(); + root["state"] = state; } }); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index b03d890ad8..d82e452c3d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -258,6 +258,19 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { return std::string(tmp); } +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); + + std::string str{buf}; + size_t dot_pos = str.find('.'); + if (dot_pos == std::string::npos) + return 0; + + return str.length() - dot_pos - 1; +} + // Colors float gamma_correct(float value, float gamma) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index dab829ab93..6bed743010 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -415,6 +415,9 @@ ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const ch /// Create a string from a value and an accuracy in decimals. std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); +/// Derive accuracy in decimals from an increment step. +int8_t step_to_accuracy_decimals(float step); + ///@} /// @name Colors From fb8846bb455c4d8a8b84776ebb71e43eeb359c99 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 15 Aug 2022 13:06:05 +1000 Subject: [PATCH 020/838] Only trigger ble_client on_connect after discovering services (#3710) --- esphome/components/ble_client/automation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 12c22345b4..828cbca662 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -15,10 +15,10 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { void loop() override {} void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { - if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK) - this->trigger(); - if (event == ESP_GATTC_SEARCH_CMPL_EVT) + if (event == ESP_GATTC_SEARCH_CMPL_EVT) { this->node_state = espbt::ClientState::ESTABLISHED; + this->trigger(); + } } }; From 8cfcd5904c7a3b88310227ce8947f0117627868c Mon Sep 17 00:00:00 2001 From: rbaron Date: Mon, 15 Aug 2022 05:09:33 +0200 Subject: [PATCH 021/838] Fixes BLE remote address type when connecting (#3702) --- esphome/components/ble_client/ble_client.cpp | 3 ++- esphome/components/ble_client/ble_client.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index d06c3c4cad..5f58d8273f 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -54,6 +54,7 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { this->remote_bda[3] = (addr >> 16) & 0xFF; this->remote_bda[4] = (addr >> 8) & 0xFF; this->remote_bda[5] = (addr >> 0) & 0xFF; + this->remote_addr_type = device.get_address_type(); return true; } @@ -83,7 +84,7 @@ void BLEClient::set_enabled(bool enabled) { void BLEClient::connect() { ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); - auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true); + auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); this->set_states_(espbt::ClientState::IDLE); diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index b122bfd11e..5ed8f219d1 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -115,6 +115,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { int gattc_if; esp_bd_addr_t remote_bda; + esp_ble_addr_type_t remote_addr_type; uint16_t conn_id; uint64_t address; bool enabled; From d07a6704d53c79eaf6bbe40eeab920e21f226ebc Mon Sep 17 00:00:00 2001 From: rbaron Date: Tue, 16 Aug 2022 04:40:58 +0200 Subject: [PATCH 022/838] Makes ble_client.ble_write's action value templatable (#3715) --- esphome/components/ble_client/__init__.py | 10 +++++-- esphome/components/ble_client/automation.cpp | 9 +++--- esphome/components/ble_client/automation.h | 30 ++++++++++++++++---- tests/test2.yaml | 6 ++++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 6c13e7fcf5..4b7d5f5b8a 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -82,7 +82,7 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( cv.Required(CONF_ID): cv.use_id(BLEClient), cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, - cv.Required(CONF_VALUE): cv.ensure_list(cv.hex_uint8_t), + cv.Required(CONF_VALUE): cv.templatable(cv.ensure_list(cv.hex_uint8_t)), } ) @@ -93,8 +93,14 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( async def ble_write_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) + value = config[CONF_VALUE] - cg.add(var.set_value(value)) + if cg.is_template(value): + templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_value_template(templ)) + else: + cg.add(var.set_value_simple(value)) + serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(serv_uuid128)) char_uuid128 = esp32_ble_tracker.as_reversed_hex_array( diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 8d5fe96570..6918ab31b4 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -10,7 +10,7 @@ namespace esphome { namespace ble_client { static const char *const TAG = "ble_client.automation"; -void BLEWriterClientNode::write() { +void BLEWriterClientNode::write(const std::vector &value) { if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected"); return; @@ -29,9 +29,10 @@ void BLEWriterClientNode::write() { ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); return; } - ESP_LOGVV(TAG, "Will write %d bytes: %s", this->value_.size(), format_hex_pretty(this->value_).c_str()); - esp_err_t err = esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, - value_.size(), value_.data(), write_type, ESP_GATT_AUTH_REQ_NONE); + ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); + esp_err_t err = + esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(), + const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_OK) { ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 828cbca662..38e64ebd76 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -42,10 +42,8 @@ class BLEWriterClientNode : public BLEClientNode { ble_client_ = ble_client; } - void set_value(std::vector value) { value_ = std::move(value); } - - // Attempts to write the contents of value_ to char_uuid_. - void write(); + // Attempts to write the contents of value to char_uuid_. + void write(const std::vector &value); void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } @@ -60,14 +58,34 @@ class BLEWriterClientNode : public BLEClientNode { esp_gatt_char_prop_t char_props_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; - std::vector value_; }; template class BLEClientWriteAction : public Action, public BLEWriterClientNode { public: BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {} - void play(Ts... x) override { return write(); } + void play(Ts... x) override { + if (has_simple_value_) { + return write(this->value_simple_); + } else { + return write(this->value_template_(x...)); + } + } + + void set_value_template(std::function(Ts...)> func) { + this->value_template_ = std::move(func); + has_simple_value_ = false; + } + + void set_value_simple(const std::vector &value) { + this->value_simple_ = value; + has_simple_value_ = true; + } + + private: + bool has_simple_value_ = true; + std::vector value_simple_; + std::function(Ts...)> value_template_{}; }; } // namespace ble_client diff --git a/tests/test2.yaml b/tests/test2.yaml index 5dd9e76a2f..110e8e6625 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -615,3 +615,9 @@ switch: service_uuid: F61E3BE9-2826-A81B-970A-4D4DECFABBAE characteristic_uuid: 6490FAFE-0734-732C-8705-91B653A081FC value: [0x01, 0xab, 0xff] + - ble_client.ble_write: + id: airthings01 + service_uuid: F61E3BE9-2826-A81B-970A-4D4DECFABBAE + characteristic_uuid: 6490FAFE-0734-732C-8705-91B653A081FC + value: !lambda |- + return {0x13, 0x37}; From c943d84036fd77a89d8ba1f3c3a0156aa3ffd59a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:47:48 +1200 Subject: [PATCH 023/838] Bump version to 2022.8.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 59fa466d11..00d155e66f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.0b2" +__version__ = "2022.8.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 3a82f500d4d1a8122aeb8ba4dd1a228a36ed2101 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:58:30 +1200 Subject: [PATCH 024/838] Bump version to 2022.8.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 00d155e66f..981ee773d4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.0b3" +__version__ = "2022.8.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 2650441013a587911d603e3e715e63ae7b808484 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:01:36 +1200 Subject: [PATCH 025/838] Bump pylint from 2.14.5 to 2.15.0 (#3746) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/font/__init__.py | 4 ++-- esphome/components/shelly_dimmer/light.py | 2 +- esphome/dashboard/dashboard.py | 4 +++- requirements_test.txt | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index ced433dc5a..aa165ebaa5 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -146,7 +146,7 @@ def download_gfonts(value): if path.is_file(): return value try: - req = requests.get(url) + req = requests.get(url, timeout=30) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid( @@ -162,7 +162,7 @@ def download_gfonts(value): ttf_url = match.group(1) try: - req = requests.get(ttf_url) + 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}") diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index 003498c090..3978d37c0b 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -73,7 +73,7 @@ def get_firmware(value): def dl(url): try: - req = requests.get(url) + req = requests.get(url, timeout=30) req.raise_for_status() except requests.exceptions.RequestException as e: raise cv.Invalid(f"Could not download firmware file ({url}): {e}") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ca2767639d..e7a17cba4c 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -823,7 +823,9 @@ class LoginHandler(BaseHandler): "password": self.get_argument("password", ""), } try: - req = requests.post("http://supervisor/auth", headers=headers, data=data) + req = requests.post( + "http://supervisor/auth", headers=headers, data=data, timeout=30 + ) if req.status_code == 200: self.set_secure_cookie("authenticated", cookie_authenticated_yes) self.redirect("/") diff --git a/requirements_test.txt b/requirements_test.txt index ed48818276..8ae7563642 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.14.5 +pylint==2.15.0 flake8==5.0.4 black==22.6.0 # also change in .pre-commit-config.yaml when updating pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating From 3acc8e7479843998bdc9def5ea0a0c7397906b57 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 16 Aug 2022 20:41:46 -0700 Subject: [PATCH 026/838] fix grow password setting (#3722) Co-authored-by: Samuel Sieb --- esphome/components/fingerprint_grow/fingerprint_grow.cpp | 8 ++++---- esphome/components/fingerprint_grow/fingerprint_grow.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 3b8c52fea2..1d6cb776b7 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -51,7 +51,7 @@ void FingerprintGrowComponent::update() { void FingerprintGrowComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); if (this->check_password_()) { - if (this->new_password_ != nullptr) { + if (this->new_password_ != -1) { if (this->set_password_()) return; } else { @@ -202,9 +202,9 @@ bool FingerprintGrowComponent::check_password_() { } bool FingerprintGrowComponent::set_password_() { - ESP_LOGI(TAG, "Setting new password: %d", *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)}; + ESP_LOGI(TAG, "Setting new password: %d", 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) { ESP_LOGI(TAG, "New password successfully set"); ESP_LOGI(TAG, "Define the new password in your configuration and reflash now"); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index 7ec253ff3a..e50418a768 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -96,7 +96,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic } void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_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_new_password(uint32_t new_password) { this->new_password_ = new_password; } void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) { this->fingerprint_count_sensor_ = fingerprint_count_sensor; } @@ -153,7 +153,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; uint16_t capacity_ = 64; uint32_t password_ = 0x0; - uint32_t *new_password_{nullptr}; + uint32_t new_password_ = -1; GPIOPin *sensing_pin_{nullptr}; uint8_t enrollment_image_ = 0; uint16_t enrollment_slot_ = 0; From 22eb4f9cb92ee2a22397f187c15d279c46e2552a Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 18 Aug 2022 14:55:10 -0500 Subject: [PATCH 027/838] Fix SPI HW selection for ESP32 variants (#3728) --- esphome/components/spi/spi.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 95280f23b9..864f6ae39d 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -76,7 +76,11 @@ void SPIComponent::setup() { 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) + this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory) +#else this->hw_spi_ = new SPIClass(VSPI); // NOLINT(cppcoreguidelines-owning-memory) +#endif // USE_ESP32_VARIANT } spi_bus_num++; this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); From aa2eb29274a7c3c7be4bf97dfe13e4a070cb81a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:26:52 +1200 Subject: [PATCH 028/838] Bump version to 2022.8.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 981ee773d4..3091f55f12 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.0" +__version__ = "2022.8.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 1b4156646e03dacd3dfba02e092023c7d7dd188a Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Fri, 2 Sep 2022 03:22:34 +0200 Subject: [PATCH 029/838] Esp32 pulsecounter optional pcnt (#3691) Co-authored-by: RoboMagus <-> --- esphome/components/hlw8012/hlw8012.h | 13 +++++- .../pulse_counter/pulse_counter_sensor.cpp | 23 ++++++---- .../pulse_counter/pulse_counter_sensor.h | 45 ++++++++++++------- esphome/components/pulse_counter/sensor.py | 34 +++++++++----- 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index 5060957cf1..adb49ffb66 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -16,8 +16,17 @@ enum HLW8012SensorModels { HLW8012_SENSOR_MODEL_BL0937 }; +#ifdef HAS_PCNT +#define USE_PCNT true +#else +#define USE_PCNT false +#endif + class HLW8012Component : public PollingComponent { public: + HLW8012Component() + : cf_store_(*pulse_counter::get_storage(USE_PCNT)), cf1_store_(*pulse_counter::get_storage(USE_PCNT)) {} + void setup() override; void dump_config() override; float get_setup_priority() const override; @@ -49,9 +58,9 @@ class HLW8012Component : public PollingComponent { uint64_t cf_total_pulses_{0}; GPIOPin *sel_pin_; InternalGPIOPin *cf_pin_; - pulse_counter::PulseCounterStorage cf_store_; + pulse_counter::PulseCounterStorageBase &cf_store_; InternalGPIOPin *cf1_pin_; - pulse_counter::PulseCounterStorage cf1_store_; + pulse_counter::PulseCounterStorageBase &cf1_store_; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 002f6dcac9..1f50360fed 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -8,8 +8,16 @@ static const char *const TAG = "pulse_counter"; const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; -#ifndef HAS_PCNT -void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { +#ifdef HAS_PCNT +PulseCounterStorageBase *get_storage(bool hw_pcnt) { + return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) + : (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); +} +#else +PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } +#endif + +void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { const uint32_t now = micros(); const bool discard = now - arg->last_pulse < arg->filter_us; arg->last_pulse = now; @@ -28,23 +36,22 @@ void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { break; } } -bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { +bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { this->pin = pin; this->pin->setup(); this->isr_pin = this->pin->to_isr(); - this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); + this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); return true; } -pulse_counter_t PulseCounterStorage::read_raw_value() { +pulse_counter_t BasicPulseCounterStorage::read_raw_value() { pulse_counter_t counter = this->counter; pulse_counter_t ret = counter - this->last_value; this->last_value = counter; return ret; } -#endif #ifdef HAS_PCNT -bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { +bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); @@ -127,7 +134,7 @@ bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { } return true; } -pulse_counter_t PulseCounterStorage::read_raw_value() { +pulse_counter_t HwPulseCounterStorage::read_raw_value() { pulse_counter_t counter; pcnt_get_counter_value(this->pcnt_unit, &counter); pulse_counter_t ret = counter - this->last_value; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index f81d20a646..d9be79e403 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -24,31 +24,44 @@ using pulse_counter_t = int16_t; using pulse_counter_t = int32_t; #endif -struct PulseCounterStorage { - bool pulse_counter_setup(InternalGPIOPin *pin); - pulse_counter_t read_raw_value(); - - static void gpio_intr(PulseCounterStorage *arg); - -#ifndef HAS_PCNT - volatile pulse_counter_t counter{0}; - volatile uint32_t last_pulse{0}; -#endif +struct PulseCounterStorageBase { + virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; + virtual pulse_counter_t read_raw_value() = 0; InternalGPIOPin *pin; -#ifdef HAS_PCNT - pcnt_unit_t pcnt_unit; -#else - ISRInternalGPIOPin isr_pin; -#endif PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE}; uint32_t filter_us{0}; pulse_counter_t last_value{0}; }; +struct BasicPulseCounterStorage : public PulseCounterStorageBase { + static void gpio_intr(BasicPulseCounterStorage *arg); + + bool pulse_counter_setup(InternalGPIOPin *pin) override; + pulse_counter_t read_raw_value() override; + + volatile pulse_counter_t counter{0}; + volatile uint32_t last_pulse{0}; + + ISRInternalGPIOPin isr_pin; +}; + +#ifdef HAS_PCNT +struct HwPulseCounterStorage : public PulseCounterStorageBase { + bool pulse_counter_setup(InternalGPIOPin *pin) override; + pulse_counter_t read_raw_value() override; + + pcnt_unit_t pcnt_unit; +}; +#endif + +PulseCounterStorageBase *get_storage(bool hw_pcnt = false); + class PulseCounterSensor : public sensor::Sensor, public PollingComponent { public: + explicit PulseCounterSensor(bool hw_pcnt = false) : storage_(*get_storage(hw_pcnt)) {} + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; } void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; } @@ -65,7 +78,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { protected: InternalGPIOPin *pin_; - PulseCounterStorage storage_; + PulseCounterStorageBase &storage_; uint32_t last_time_{0}; uint32_t current_total_{0}; sensor::Sensor *total_sensor_; diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 88f53bdf77..27364a34b3 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -20,6 +20,8 @@ from esphome.const import ( ) from esphome.core import CORE +CONF_USE_PCNT = "use_pcnt" + pulse_counter_ns = cg.esphome_ns.namespace("pulse_counter") PulseCounterCountMode = pulse_counter_ns.enum("PulseCounterCountMode") COUNT_MODES = { @@ -40,11 +42,19 @@ SetTotalPulsesAction = pulse_counter_ns.class_( def validate_internal_filter(value): - value = cv.positive_time_period_microseconds(value) - if CORE.is_esp32: - if value.total_microseconds > 13: - raise cv.Invalid("Maximum internal filter value for ESP32 is 13us") - return value + use_pcnt = value.get(CONF_USE_PCNT) + if CORE.is_esp8266 and use_pcnt: + raise cv.Invalid( + "Using hardware PCNT is only available on ESP32", + [CONF_USE_PCNT], + ) + + if CORE.is_esp32 and use_pcnt: + if value.get(CONF_INTERNAL_FILTER).total_microseconds > 13: + raise cv.Invalid( + "Maximum internal filter value when using ESP32 hardware PCNT is 13us", + [CONF_INTERNAL_FILTER], + ) return value @@ -69,7 +79,7 @@ def validate_count_mode(value): return value -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( sensor.sensor_schema( PulseCounterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, @@ -95,21 +105,25 @@ CONFIG_SCHEMA = ( ), validate_count_mode, ), - cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, + cv.SplitDefault(CONF_USE_PCNT, esp32=True): cv.boolean, + cv.Optional( + CONF_INTERNAL_FILTER, default="13us" + ): cv.positive_time_period_microseconds, cv.Optional(CONF_TOTAL): sensor.sensor_schema( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, ), - } + }, ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("60s")), + validate_internal_filter, ) async def to_code(config): - var = await sensor.new_sensor(config) + var = await sensor.new_sensor(config, config.get(CONF_USE_PCNT)) await cg.register_component(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) From e3dad7c63215bbeddf154c52379b99f6cec0749f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 13:42:04 +1200 Subject: [PATCH 030/838] Bump black from 22.6.0 to 22.8.0 (#3760) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8ae7563642..bbd707fbb3 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.15.0 flake8==5.0.4 -black==22.6.0 # also change in .pre-commit-config.yaml when updating +black==22.8.0 # also change in .pre-commit-config.yaml when updating pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating pre-commit From 4788a6182e295abc7ee50b30234bf6f893d81479 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Fri, 2 Sep 2022 05:46:51 +0200 Subject: [PATCH 031/838] I found some issue in the ili9341 driver (#3756) --- .../components/ili9341/ili9341_display.cpp | 92 ++++++++++++------- esphome/components/ili9341/ili9341_display.h | 12 ++- 2 files changed, 71 insertions(+), 33 deletions(-) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index c4fa5f1b10..117de3de89 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -10,7 +10,6 @@ namespace ili9341 { static const char *const TAG = "ili9341"; void ILI9341Display::setup_pins_() { - this->init_internal_(this->get_buffer_length_()); this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); if (this->reset_pin_ != nullptr) { @@ -28,15 +27,14 @@ void ILI9341Display::setup_pins_() { void ILI9341Display::dump_config() { LOG_DISPLAY("", "ili9341", this); - ESP_LOGCONFIG(TAG, " Width: %d, Height: %d, Rotation: %d", this->width_, this->height_, this->rotation_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_PIN(" Backlight Pin: ", this->led_pin_); LOG_UPDATE_INTERVAL(this); } -float ILI9341Display::get_setup_priority() const { return setup_priority::PROCESSOR; } +float ILI9341Display::get_setup_priority() const { return setup_priority::HARDWARE; } + void ILI9341Display::command(uint8_t value) { this->start_command_(); this->write_byte(value); @@ -88,10 +86,19 @@ void ILI9341Display::display_() { // we will only update the changed window to the display uint16_t w = this->x_high_ - this->x_low_ + 1; uint16_t h = this->y_high_ - this->y_low_ + 1; + uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); + + // check if something was displayed + if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { + return; + } set_addr_window_(this->x_low_, this->y_low_, w, h); + + ESP_LOGVV("ILI9341", "Start ILI9341Display::display_(xl:%d, xh:%d, yl:%d, yh:%d, w:%d, h:%d, start_pos:%d)", + this->x_low_, this->x_high_, this->y_low_, this->y_high_, w, h, start_pos); + this->start_data_(); - uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); for (uint16_t row = 0; row < h; row++) { uint32_t pos = start_pos + (row * width_); uint32_t rem = w; @@ -101,7 +108,9 @@ void ILI9341Display::display_() { this->write_array(transfer_buffer_, 2 * sz); pos += sz; rem -= sz; + App.feed_wdt(); } + App.feed_wdt(); } this->end_data_(); @@ -121,20 +130,10 @@ void ILI9341Display::fill(Color color) { this->y_high_ = this->get_height_internal() - 1; } -void ILI9341Display::fill_internal_(Color color) { - if (color.raw_32 == Color::BLACK.raw_32) { - memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); - } else { - uint8_t *dst = transfer_buffer_; - auto color565 = display::ColorUtil::color_to_565(color); +void ILI9341Display::fill_internal_(uint8_t color) { + memset(transfer_buffer_, color, sizeof(transfer_buffer_)); - while (dst < transfer_buffer_ + sizeof(transfer_buffer_)) { - *dst++ = (uint8_t)(color565 >> 8); - *dst++ = (uint8_t) color565; - } - } - - uint32_t rem = this->get_width_internal() * this->get_height_internal(); + uint32_t rem = (this->get_buffer_length_() * 2); this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); this->start_data_(); @@ -147,26 +146,58 @@ void ILI9341Display::fill_internal_(Color color) { this->end_data_(); - memset(buffer_, 0, (this->get_width_internal()) * (this->get_height_internal())); + memset(buffer_, color, this->get_buffer_length_()); +} + +void ILI9341Display::rotate_my_(uint8_t m) { + uint8_t rotation = m & 3; // can't be higher than 3 + switch (rotation) { + case 0: + m = (MADCTL_MX | MADCTL_BGR); + // _width = ILI9341_TFTWIDTH; + // _height = ILI9341_TFTHEIGHT; + break; + case 1: + m = (MADCTL_MV | MADCTL_BGR); + // _width = ILI9341_TFTHEIGHT; + // _height = ILI9341_TFTWIDTH; + break; + case 2: + m = (MADCTL_MY | MADCTL_BGR); + // _width = ILI9341_TFTWIDTH; + // _height = ILI9341_TFTHEIGHT; + break; + case 3: + m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); + // _width = ILI9341_TFTHEIGHT; + // _height = ILI9341_TFTWIDTH; + break; + } + + this->command(ILI9341_MADCTL); + this->data(m); } void HOT ILI9341Display::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; - // 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_; - uint32_t pos = (y * width_) + x; + uint8_t new_color; + if (this->buffer_color_mode_ == BITS_8) { - uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); - buffer_[pos] = color332; + new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); } else { // if (this->buffer_color_mode_ == BITS_8_INDEXED) { - uint8_t index = display::ColorUtil::color_to_index8_palette888(color, this->palette_); - buffer_[pos] = index; + new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); + } + + if (buffer_[pos] != new_color) { + buffer_[pos] = new_color; + // 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_; } } @@ -252,7 +283,6 @@ void ILI9341M5Stack::initialize() { this->width_ = 320; this->height_ = 240; this->invert_display_(true); - this->fill_internal_(Color::BLACK); } // 24_TFT display @@ -260,7 +290,6 @@ void ILI9341TFT24::initialize() { this->init_lcd_(INITCMD_TFT); this->width_ = 240; this->height_ = 320; - this->fill_internal_(Color::BLACK); } // 24_TFT rotated display @@ -268,7 +297,6 @@ void ILI9341TFT24R::initialize() { this->init_lcd_(INITCMD_TFT); this->width_ = 320; this->height_ = 240; - this->fill_internal_(Color::BLACK); } } // namespace ili9341 diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index 3fbc144ac2..547c608ae8 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -5,6 +5,7 @@ #include "esphome/components/display/display_buffer.h" #include "ili9341_defines.h" #include "ili9341_init.h" +#include "esphome/core/log.h" namespace esphome { namespace ili9341 { @@ -47,6 +48,14 @@ class ILI9341Display : public PollingComponent, void setup() override { this->setup_pins_(); this->initialize(); + + this->x_low_ = this->width_; + this->y_low_ = this->height_; + this->x_high_ = 0; + this->y_high_ = 0; + + this->init_internal_(this->get_buffer_length_()); + this->fill_internal_(0x00); } display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } @@ -59,8 +68,9 @@ class ILI9341Display : public PollingComponent, void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void invert_display_(bool invert); void reset_(); - void fill_internal_(Color color); + void fill_internal_(uint8_t color); void display_(); + void rotate_my_(uint8_t m); ILI9341Model model_; int16_t width_{320}; ///< Display width as modified by current rotation From 7a4cf13e0c998bb08f85c57461d270c399dfb694 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Sun, 4 Sep 2022 10:21:17 +0300 Subject: [PATCH 032/838] Ignore NaN states in the integration component (#3767) --- esphome/components/integration/integration_sensor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index 65fa42dd0d..2ac7caca21 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -23,6 +23,8 @@ void IntegrationSensor::setup() { } void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); } void IntegrationSensor::process_sensor_value_(float value) { + if (std::isnan(value)) + return; const uint32_t now = millis(); const double old_value = this->last_value_; const double new_value = value; From 7097b7677ea8196f2e3052debe193b7c95ce0a09 Mon Sep 17 00:00:00 2001 From: Andrey Yantsen Date: Sun, 4 Sep 2022 09:23:54 +0100 Subject: [PATCH 033/838] Add the same docker tags as used in HA (#3752) --- docker/build.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docker/build.py b/docker/build.py index d5926ae3d4..ae977f87c1 100755 --- a/docker/build.py +++ b/docker/build.py @@ -88,10 +88,12 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) + match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag) + major_minor_version = None if match is None: channel = CHANNEL_DEV - elif match.group(1) is None: + elif match.group(2) is None: + major_minor_version = match.group(1) channel = CHANNEL_RELEASE else: channel = CHANNEL_BETA @@ -106,6 +108,11 @@ def main(): tags_to_push.append("beta") tags_to_push.append("latest") + # Compatibility with HA tags + if major_minor_version: + tags_to_push.append("stable") + tags_to_push.append(major_minor_version) + if args.command == "build": # 1. pull cache image params = DockerParams.for_type_arch(args.build_type, args.arch) From 5d712c73ea77bbf61e5abdd690da610b742dfdfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Sep 2022 20:24:26 +1200 Subject: [PATCH 034/838] Bump pytest from 7.1.1 to 7.1.3 (#3766) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index bbd707fbb3..1d57fef954 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.1.1 +pytest==7.1.3 pytest-cov==3.0.0 pytest-mock==3.8.2 pytest-asyncio==0.19.0 From 219c5953f152e375469ba81e4728dcbfcf9c1526 Mon Sep 17 00:00:00 2001 From: Avirsaam <107193364+Avirsaam@users.noreply.github.com> Date: Mon, 5 Sep 2022 03:50:27 +0300 Subject: [PATCH 035/838] Update modbus_controller.cpp (#3768) --- esphome/components/modbus_controller/modbus_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index bd2fec3ece..e60b016a17 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -236,7 +236,7 @@ size_t ModbusController::create_register_ranges_() { } } - if (curr->start_address == r.start_address) { + if (curr->start_address == r.start_address && curr->register_type == r.register_type) { // use the lowest non zero value for the whole range // Because zero is the default value for skip_updates it is excluded from getting the min value. if (curr->skip_updates != 0) { From 614eb81ad7b51b86b8d12714517bb59e7ca085f1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 11:54:32 +1200 Subject: [PATCH 036/838] Remove unneeded line (spi component adds it) (#3778) --- esphome/components/mcp3008/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 431963acfd..24a48664c1 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import spi from esphome.const import CONF_ID -from esphome.core import CORE DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] @@ -24,6 +23,3 @@ 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 CORE.is_esp32: - cg.add_library("SPI", None) From c317422ed76089fe0900b2e80bf676ee63ddf70f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 12:57:21 +1200 Subject: [PATCH 037/838] Move crc16 to helpers (#3780) --- esphome/components/am2320/am2320.cpp | 24 +++--------------------- esphome/components/modbus/modbus.cpp | 16 ---------------- esphome/components/modbus/modbus.h | 2 -- esphome/components/senseair/senseair.cpp | 20 ++------------------ esphome/components/senseair/senseair.h | 1 - esphome/core/helpers.cpp | 15 +++++++++++++++ esphome/core/helpers.h | 3 +++ 7 files changed, 23 insertions(+), 58 deletions(-) diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index 8ab48a348e..ec501f2d36 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -4,33 +4,15 @@ // - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp #include "am2320.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace am2320 { static const char *const TAG = "am2320"; -// ---=== Calc CRC16 ===--- -uint16_t crc_16(uint8_t *ptr, uint8_t length) { - uint16_t crc = 0xFFFF; - uint8_t i; - //------------------------------ - while (length--) { - crc ^= *ptr++; - for (i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - void AM2320Component::update() { uint8_t data[8]; data[0] = 0; @@ -98,7 +80,7 @@ bool AM2320Component::read_data_(uint8_t *data) { checksum = data[7] << 8; checksum += data[6]; - if (crc_16(data, 6) != checksum) { + if (crc16(data, 6) != checksum) { ESP_LOGW(TAG, "AM2320 Checksum invalid!"); return false; } diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 845fa92e95..4d75675d0f 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -35,22 +35,6 @@ void Modbus::loop() { } } -uint16_t crc16(const uint8_t *data, uint8_t len) { - uint16_t crc = 0xFFFF; - while (len--) { - crc ^= *data++; - for (uint8_t i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - bool Modbus::parse_modbus_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index 400e29e08b..629ab6dcce 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -40,8 +40,6 @@ class Modbus : public uart::UARTDevice, public Component { std::vector devices_; }; -uint16_t crc16(const uint8_t *data, uint8_t len); - class ModbusDevice { public: void set_parent(Modbus *parent) { parent_ = parent; } diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 50b9e01f17..7a98584201 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -1,4 +1,5 @@ #include "senseair.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -42,7 +43,7 @@ void SenseAirComponent::update() { return; } - uint16_t calc_checksum = this->senseair_checksum_(response, 11); + uint16_t calc_checksum = crc16(response, 11); uint16_t resp_checksum = (uint16_t(response[12]) << 8) | response[11]; if (resp_checksum != calc_checksum) { ESP_LOGW(TAG, "SenseAir checksum doesn't match: 0x%02X!=0x%02X", resp_checksum, calc_checksum); @@ -60,23 +61,6 @@ void SenseAirComponent::update() { this->co2_sensor_->publish_state(ppm); } -uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { - uint16_t crc = 0xFFFF; - uint8_t i; - while (length--) { - crc ^= *ptr++; - for (i = 0; i < 8; i++) { - if ((crc & 0x01) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; -} - void SenseAirComponent::background_calibration() { // Responses are just echoes but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Starting background calibration"); diff --git a/esphome/components/senseair/senseair.h b/esphome/components/senseair/senseair.h index c03a0848e9..bcec638f79 100644 --- a/esphome/components/senseair/senseair.h +++ b/esphome/components/senseair/senseair.h @@ -23,7 +23,6 @@ class SenseAirComponent : public PollingComponent, public uart::UARTDevice { void abc_disable(); protected: - uint16_t senseair_checksum_(uint8_t *ptr, uint8_t length); bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length); sensor::Sensor *co2_sensor_{nullptr}; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index d82e452c3d..3aca944a36 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -62,6 +62,21 @@ uint8_t crc8(uint8_t *data, uint8_t len) { } return crc; } +uint16_t crc16(const uint8_t *data, uint8_t len) { + uint16_t crc = 0xFFFF; + while (len--) { + crc ^= *data++; + for (uint8_t i = 0; i < 8; i++) { + if ((crc & 0x01) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6bed743010..1459f0ef55 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -149,6 +149,9 @@ template T remap(U value, U min, U max, T min_out, T max /// Calculate a CRC-8 checksum of \p data with size \p len. uint8_t crc8(uint8_t *data, uint8_t len); +/// Calculate a CRC-16 checksum of \p data with size \p len. +uint16_t crc16(const uint8_t *data, uint8_t len); + /// Calculate a FNV-1 hash of \p str. uint32_t fnv1_hash(const std::string &str); From c8eb30ef27934329f54a1fce50444400239acda8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 25 Aug 2022 07:13:44 +1200 Subject: [PATCH 038/838] Initial bluetooth_proxy support (#3736) --- CODEOWNERS | 1 + esphome/components/api/api.proto | 28 ++++ esphome/components/api/api_connection.cpp | 3 + esphome/components/api/api_connection.h | 15 ++ esphome/components/api/api_pb2.cpp | 146 ++++++++++++++++++ esphome/components/api/api_pb2.h | 40 +++++ esphome/components/api/api_pb2_service.cpp | 29 ++++ esphome/components/api/api_pb2_service.h | 7 + esphome/components/api/api_server.cpp | 7 + esphome/components/api/api_server.h | 3 + esphome/components/api/proto.h | 2 +- .../components/bluetooth_proxy/__init__.py | 27 ++++ .../bluetooth_proxy/bluetooth_proxy.cpp | 58 +++++++ .../bluetooth_proxy/bluetooth_proxy.h | 26 ++++ esphome/core/defines.h | 1 + tests/test1.yaml | 2 + 16 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 esphome/components/bluetooth_proxy/__init__.py create mode 100644 esphome/components/bluetooth_proxy/bluetooth_proxy.cpp create mode 100644 esphome/components/bluetooth_proxy/bluetooth_proxy.h diff --git a/CODEOWNERS b/CODEOWNERS index 62aae6d3cb..7f7562dec4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -34,6 +34,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix +esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bme680_bsec/* @trvrnrth esphome/components/bmp3xx/* @martgras esphome/components/button/* @esphome/core diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 88a74540d0..c6cde8b038 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -27,6 +27,7 @@ service APIConnection { rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} + rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc get_time (GetTimeRequest) returns (GetTimeResponse) { option (needs_authentication) = false; } @@ -190,6 +191,8 @@ message DeviceInfoResponse { string project_version = 9; uint32 webserver_port = 10; + + bool has_bluetooth_proxy = 11; } message ListEntitiesRequest { @@ -1099,3 +1102,28 @@ message MediaPlayerCommandRequest { bool has_media_url = 6; string media_url = 7; } + +// ==================== BLUETOOTH ==================== +message SubscribeBluetoothLEAdvertisementsRequest { + option (id) = 66; + option (source) = SOURCE_CLIENT; +} + +message BluetoothServiceData { + string uuid = 1; + repeated uint32 data = 2 [packed=false]; +} +message BluetoothLEAdvertisementResponse { + option (id) = 67; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + option (no_delay) = true; + + uint64 address = 1; + string name = 2; + sint32 rssi = 3; + + repeated string service_uuids = 4; + repeated BluetoothServiceData service_data = 5; + repeated BluetoothServiceData manufacturer_data = 6; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9028034c90..a3b000c778 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -886,6 +886,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #endif #ifdef USE_WEBSERVER resp.webserver_port = USE_WEBSERVER_PORT; +#endif +#ifdef USE_BLUETOOTH_PROXY + resp.has_bluetooth_proxy = true; #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 0787d2f7eb..dcf8bacad2 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -7,6 +7,10 @@ #include "api_server.h" #include "api_frame_helper.h" +#ifdef USE_BLUETOOTH_PROXY +#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" +#endif + namespace esphome { namespace api { @@ -94,6 +98,13 @@ class APIConnection : public APIServerConnection { return; this->send_homeassistant_service_response(call); } +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { + if (!this->bluetooth_le_advertisement_subscription_) + return false; + return this->send_bluetooth_le_advertisement_response(call); + } +#endif #ifdef USE_HOMEASSISTANT_TIME void send_time_request() { GetTimeRequest req; @@ -134,6 +145,9 @@ class APIConnection : public APIServerConnection { return {}; } void execute_service(const ExecuteServiceRequest &msg) override; + void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { + this->bluetooth_le_advertisement_subscription_ = true; + } bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } bool is_connection_setup() override { return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); @@ -176,6 +190,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; + bool bluetooth_le_advertisement_subscription_{true}; bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index b91c9bd600..13969fce76 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -495,6 +495,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->webserver_port = value.as_uint32(); return true; } + case 11: { + this->has_bluetooth_proxy = value.as_bool(); + return true; + } default: return false; } @@ -544,6 +548,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); buffer.encode_uint32(10, this->webserver_port); + buffer.encode_bool(11, this->has_bluetooth_proxy); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -589,6 +594,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const { sprintf(buffer, "%u", this->webserver_port); out.append(buffer); out.append("\n"); + + out.append(" has_bluetooth_proxy: "); + out.append(YESNO(this->has_bluetooth_proxy)); + out.append("\n"); out.append("}"); } #endif @@ -4854,6 +4863,143 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { + out.append("SubscribeBluetoothLEAdvertisementsRequest {}"); +} +#endif +bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->data.push_back(value.as_uint32()); + return true; + } + default: + return false; + } +} +bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->uuid = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->uuid); + for (auto &it : this->data) { + buffer.encode_uint32(2, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothServiceData::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothServiceData {\n"); + out.append(" uuid: "); + out.append("'").append(this->uuid).append("'"); + out.append("\n"); + + for (const auto &it : this->data) { + out.append(" data: "); + sprintf(buffer, "%u", it); + out.append(buffer); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 3: { + this->rssi = value.as_sint32(); + return true; + } + default: + return false; + } +} +bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->name = value.as_string(); + return true; + } + case 4: { + this->service_uuids.push_back(value.as_string()); + return true; + } + case 5: { + this->service_data.push_back(value.as_message()); + return true; + } + case 6: { + this->manufacturer_data.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_string(2, this->name); + buffer.encode_sint32(3, this->rssi); + for (auto &it : this->service_uuids) { + buffer.encode_string(4, it, true); + } + for (auto &it : this->service_data) { + buffer.encode_message(5, it, true); + } + for (auto &it : this->manufacturer_data) { + buffer.encode_message(6, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothLEAdvertisementResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" rssi: "); + sprintf(buffer, "%d", this->rssi); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->service_uuids) { + out.append(" service_uuids: "); + out.append("'").append(it).append("'"); + out.append("\n"); + } + + for (const auto &it : this->service_data) { + out.append(" service_data: "); + it.dump_to(out); + out.append("\n"); + } + + for (const auto &it : this->manufacturer_data) { + out.append(" manufacturer_data: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index f9981fdbb7..2093f93ee7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -263,6 +263,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; uint32_t webserver_port{0}; + bool has_bluetooth_proxy{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1214,6 +1215,45 @@ class MediaPlayerCommandRequest : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class BluetoothServiceData : public ProtoMessage { + public: + std::string uuid{}; + std::vector data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothLEAdvertisementResponse : public ProtoMessage { + public: + uint64_t address{0}; + std::string name{}; + int32_t rssi{0}; + std::vector service_uuids{}; + std::vector service_data{}; + std::vector manufacturer_data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index bd146cb54d..6932675c41 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -328,6 +328,14 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer #endif #ifdef USE_MEDIA_PLAYER #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 67); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -595,6 +603,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #endif break; } + case 66: { + SubscribeBluetoothLEAdvertisementsRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); +#endif + this->on_subscribe_bluetooth_le_advertisements_request(msg); + break; + } default: return false; } @@ -691,6 +708,18 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc } this->subscribe_home_assistant_states(msg); } +void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->subscribe_bluetooth_le_advertisements(msg); +} void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { if (!this->is_connection_setup()) { this->on_no_setup_connection(); diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 28ad3fbd15..49426d1bbc 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -153,6 +153,11 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_MEDIA_PLAYER virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; +#endif + virtual void on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &value){}; +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -170,6 +175,7 @@ class APIServerConnection : public APIServerConnectionBase { virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; + virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0; #ifdef USE_COVER @@ -216,6 +222,7 @@ class APIServerConnection : public APIServerConnectionBase { void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override; #ifdef USE_COVER diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 8375a82313..5851594955 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -291,6 +291,13 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon client->send_homeassistant_service_call(call); } } +#ifdef USE_BLUETOOTH_PROXY +void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_le_advertisement(call); + } +} +#endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, std::function f) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 6997e23cac..dc892b2088 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -73,6 +73,9 @@ class APIServer : public Component, public Controller { void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); +#ifdef USE_BLUETOOTH_PROXY + void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); +#endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME void request_time(); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 32f525990d..11cd4330ce 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -70,7 +70,7 @@ class ProtoVarInt { } } void encode(std::vector &out) { - uint32_t val = this->value_; + uint64_t val = this->value_; if (val <= 0x7F) { out.push_back(val); return; diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py new file mode 100644 index 0000000000..a6a5a4391b --- /dev/null +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -0,0 +1,27 @@ +from esphome.components import esp32_ble_tracker +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID + +DEPENDENCIES = ["esp32", "esp32_ble_tracker"] +CODEOWNERS = ["@jesserockz"] + + +bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") + +BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(BluetoothProxy), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_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_define("USE_BLUETOOTH_PROXY") diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp new file mode 100644 index 0000000000..41871295ce --- /dev/null +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -0,0 +1,58 @@ +#include "bluetooth_proxy.h" + +#ifdef USE_API +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_server.h" +#endif // USE_API +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace bluetooth_proxy { + +static const char *const TAG = "bluetooth_proxy"; + +bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), + device.get_rssi()); + this->send_api_packet_(device); + return true; +} + +void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { +#ifndef USE_API + return; +#else + api::BluetoothLEAdvertisementResponse resp; + resp.address = device.address_uint64(); + if (!device.get_name().empty()) + resp.name = device.get_name(); + resp.rssi = device.get_rssi(); + for (auto uuid : device.get_service_uuids()) { + resp.service_uuids.push_back(uuid.to_string()); + } + for (auto &data : device.get_service_datas()) { + api::BluetoothServiceData service_data; + service_data.uuid = data.uuid.to_string(); + for (auto d : data.data) + service_data.data.push_back(d); + resp.service_data.push_back(service_data); + } + for (auto &data : device.get_manufacturer_datas()) { + api::BluetoothServiceData manufacturer_data; + manufacturer_data.uuid = data.uuid.to_string(); + for (auto d : data.data) + manufacturer_data.data.push_back(d); + resp.manufacturer_data.push_back(manufacturer_data); + } + api::global_api_server->send_bluetooth_le_advertisement(resp); +#endif +} + +void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } + +} // namespace bluetooth_proxy +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h new file mode 100644 index 0000000000..9a936747c0 --- /dev/null +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef USE_ESP32 + +#include + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace bluetooth_proxy { + +class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + + protected: + void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); +}; + +} // namespace bluetooth_proxy +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5b1d567aa4..51da2ceb66 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -70,6 +70,7 @@ #define USE_ESP32_IGNORE_EFUSE_MAC_CRC #define USE_IMPROV #define USE_SOCKET_IMPL_BSD_SOCKETS +#define USE_BLUETOOTH_PROXY #ifdef USE_ARDUINO #define USE_ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 6) diff --git a/tests/test1.yaml b/tests/test1.yaml index 5897639ead..15ede4d05e 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -288,6 +288,8 @@ adalight: esp32_ble_tracker: +bluetooth_proxy: + ble_client: - mac_address: AA:BB:CC:DD:EE:FF id: ble_foo From d2ce62aa13714c8f76bd74d9b03c93985205a343 Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Sun, 4 Sep 2022 10:21:17 +0300 Subject: [PATCH 039/838] Ignore NaN states in the integration component (#3767) --- esphome/components/integration/integration_sensor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index 65fa42dd0d..2ac7caca21 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -23,6 +23,8 @@ void IntegrationSensor::setup() { } void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); } void IntegrationSensor::process_sensor_value_(float value) { + if (std::isnan(value)) + return; const uint32_t now = millis(); const double old_value = this->last_value_; const double new_value = value; From 39d493c2786d301797ec19a46fa45914710d226c Mon Sep 17 00:00:00 2001 From: Avirsaam <107193364+Avirsaam@users.noreply.github.com> Date: Mon, 5 Sep 2022 03:50:27 +0300 Subject: [PATCH 040/838] Update modbus_controller.cpp (#3768) --- esphome/components/modbus_controller/modbus_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index bd2fec3ece..e60b016a17 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -236,7 +236,7 @@ size_t ModbusController::create_register_ranges_() { } } - if (curr->start_address == r.start_address) { + if (curr->start_address == r.start_address && curr->register_type == r.register_type) { // use the lowest non zero value for the whole range // Because zero is the default value for skip_updates it is excluded from getting the min value. if (curr->skip_updates != 0) { From 5bf0c92318b463968bdf070835070fb2112e43a9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:12:00 +1200 Subject: [PATCH 041/838] Bump version to 2022.8.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3091f55f12..7d916e30af 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.1" +__version__ = "2022.8.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 89fd3672970ecbcaf2bcc63c5bd43936f749ed18 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:48:01 +1200 Subject: [PATCH 042/838] YAML linting (#3779) --- .editorconfig | 3 +- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/config.yml | 8 +- .github/dependabot.yml | 7 +- .github/workflows/ci-docker.yml | 56 +- .github/workflows/ci.yml | 21 +- .github/workflows/lock.yml | 4 +- .github/workflows/release.yml | 104 +-- .github/workflows/stale.yml | 7 +- .gitpod.yml | 10 +- .pre-commit-config.yaml | 11 +- .yamllint | 3 + .../binary_sensor/test_binary_sensor.yaml | 5 +- tests/component_tests/button/test_button.yaml | 1 + .../deep_sleep/test_deep_sleep1.yaml | 1 + .../deep_sleep/test_deep_sleep2.yaml | 1 + tests/component_tests/sensor/test_sensor.yaml | 5 +- tests/test1.yaml | 775 +++++++++--------- tests/test2.yaml | 321 ++++---- tests/test3.yaml | 427 +++++----- tests/test4.yaml | 95 +-- tests/test5.yaml | 91 +- .../test_packages/test_packages_package1.yaml | 1 + .../test_packages_package_wifi.yaml | 5 +- tests/test_packages/test_uptime_sensor.yaml | 1 + .../fixtures/yaml_util/includes/scalar.yaml | 1 + .../fixtures/yaml_util/includetest.yaml | 7 +- 27 files changed, 1039 insertions(+), 933 deletions(-) create mode 100644 .yamllint diff --git a/.editorconfig b/.editorconfig index 8ccf1eeebc..9e203f60e4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,10 +25,9 @@ indent_size = 2 [*.{yaml,yml}] indent_style = space indent_size = 2 -quote_type = single +quote_type = double # JSON [*.json] indent_style = space indent_size = 2 - diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 864586fe6b..a8ca63d158 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ +--- # These are supported funding model platforms custom: https://www.nabucasa.com diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 7f99701e39..804dad47c7 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +--- blank_issues_enabled: false contact_links: - name: Issue Tracker @@ -5,7 +6,10 @@ contact_links: about: Please create bug reports in the dedicated issue tracker. - name: Feature Request Tracker url: https://github.com/esphome/feature-requests - about: Please create feature requests in the dedicated feature request tracker. + about: | + Please create feature requests in the dedicated feature request tracker. - name: Frequently Asked Question url: https://esphome.io/guides/faq.html - about: Please view the FAQ for common questions and what to include in a bug report. + about: | + Please view the FAQ for common questions and what + to include in a bug report. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c67378093e..666532f360 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,14 @@ +--- version: 2 updates: - - package-ecosystem: "pip" + - package-ecosystem: pip directory: "/" schedule: - interval: "daily" + interval: daily ignore: # Hypotehsis is only used for testing and is updated quite often - dependency-name: hypothesis - - package-ecosystem: "github-actions" + - package-ecosystem: github-actions directory: "/" schedule: interval: daily diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index d424bd3b60..7eb4cd1f66 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -1,21 +1,23 @@ +--- name: CI for docker images # Only run when docker paths change +# yamllint disable-line rule:truthy on: push: branches: [dev, beta, release] paths: - - 'docker/**' - - '.github/workflows/**' - - 'requirements*.txt' - - 'platformio.ini' + - "docker/**" + - ".github/workflows/**" + - "requirements*.txt" + - "platformio.ini" pull_request: paths: - - 'docker/**' - - '.github/workflows/**' - - 'requirements*.txt' - - 'platformio.ini' + - "docker/**" + - ".github/workflows/**" + - "requirements*.txt" + - "platformio.ini" permissions: contents: read @@ -30,24 +32,24 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Set TAG - run: | - echo "TAG=check" >> $GITHUB_ENV + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - - name: Run build - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1705610947..7496ccf388 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,7 @@ +--- name: CI +# yamllint disable-line rule:truthy on: push: branches: [dev, beta, release] @@ -10,6 +12,7 @@ permissions: contents: read concurrency: + # yamllint disable-line rule:line-length group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -73,6 +76,8 @@ jobs: name: Run script/clang-tidy for ESP32 IDF options: --environment esp32-idf-tidy --grep USE_ESP_IDF pio_cache_key: tidyesp32-idf + - id: yamllint + name: Run yamllint steps: - uses: actions/checkout@v3 @@ -80,17 +85,19 @@ jobs: uses: actions/setup-python@v4 id: python with: - python-version: '3.8' + python-version: "3.8" - name: Cache virtualenv uses: actions/cache@v3 with: path: .venv + # yamllint disable-line rule:line-length key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | venv-${{ steps.python.outputs.python-version }}- - name: Set up virtualenv + # yamllint disable rule:line-length run: | python -m venv .venv source .venv/bin/activate @@ -99,12 +106,14 @@ jobs: pip install -e . echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV + # yamllint enable rule:line-length # Use per check platformio cache because checks use different parts - name: Cache platformio uses: actions/cache@v3 with: path: ~/.platformio + # yamllint disable-line rule:line-length key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} if: matrix.id == 'test' || matrix.id == 'clang-tidy' @@ -145,8 +154,9 @@ jobs: pytest -vv --tb=native tests if: matrix.id == 'pytest' - # Also run git-diff-index so that the step is marked as failed on formatting errors, - # since clang-format doesn't do anything but change files if -i is passed. + # Also run git-diff-index so that the step is marked as failed on + # formatting errors, since clang-format doesn't do anything but + # change files if -i is passed. - name: Run clang-format run: | script/clang-format -i @@ -161,6 +171,11 @@ jobs: # Also cache libdeps, store them in a ~/.platformio subfolder PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps + - name: Run yamllint + if: matrix.id == 'yamllint' + uses: frenck/action-yamllint@v1.2.0 + - name: Suggested changes run: script/ci-suggest-changes + # yamllint disable-line rule:line-length if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index ceb45b2a91..1cf82895f3 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,8 +1,10 @@ +--- name: Lock +# yamllint disable-line rule:truthy on: schedule: - - cron: '30 0 * * *' + - cron: "30 0 * * *" workflow_dispatch: permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 216c094122..65d170f5d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,7 @@ +--- name: Publish Release +# yamllint disable-line rule:truthy on: workflow_dispatch: release: @@ -20,6 +22,7 @@ jobs: - uses: actions/checkout@v3 - name: Get tag id: tag + # yamllint disable rule:line-length run: | if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then TAG="${GITHUB_REF#refs/tags/}" @@ -29,6 +32,7 @@ jobs: TAG="${TAG}${today}" fi echo "::set-output name=tag::${TAG}" + # yamllint enable rule:line-length deploy-pypi: name: Build and publish to PyPi @@ -39,7 +43,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: "3.x" - name: Set up python environment run: | script/setup @@ -65,37 +69,37 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Log in to docker hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v2 - with: + - name: Log in to docker hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v2 + with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build \ - --push + - name: Build and push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build \ + --push deploy-docker-manifest: if: github.repository == 'esphome/esphome' @@ -108,34 +112,34 @@ jobs: matrix: build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Log in to docker hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v2 - with: + - name: Log in to docker hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v2 + with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Run manifest - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --build-type "${{ matrix.build_type }}" \ - manifest + - name: Run manifest + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-ha-addon-repo: if: github.repository == 'esphome/esphome' && github.event_name == 'release' @@ -144,6 +148,7 @@ jobs: steps: - env: TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} + # yamllint disable rule:line-length run: | TAG="${GITHUB_REF#refs/tags/}" curl \ @@ -152,3 +157,4 @@ jobs: -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" + # yamllint enable rule:line-length diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b998043039..33f7ad041c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,8 +1,10 @@ +--- name: Stale +# yamllint disable-line rule:truthy on: schedule: - - cron: '30 0 * * *' + - cron: "30 0 * * *" workflow_dispatch: permissions: @@ -31,7 +33,8 @@ jobs: and will be closed if no further activity occurs within 7 days. Thank you for your contributions. - # Use stale to automatically close issues with a reference to the issue tracker + # Use stale to automatically close issues with a + # reference to the issue tracker close-issues: runs-on: ubuntu-latest steps: diff --git a/.gitpod.yml b/.gitpod.yml index e3f786a403..be2f11227c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,8 @@ +--- ports: -- port: 6052 - onOpen: open-preview + - port: 6052 + onOpen: open-preview tasks: -- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup - command: python -m esphome dashboard config + # yamllint disable-line rule:line-length + - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup + command: python -m esphome dashboard config diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95365ff5bb..083aea117d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,15 @@ +--- # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/ambv/black rev: 22.6.0 hooks: - - id: black - args: - - --safe - - --quiet - files: ^((esphome|script|tests)/.+)?[^/]+\.py$ + - id: black + args: + - --safe + - --quiet + files: ^((esphome|script|tests)/.+)?[^/]+\.py$ - repo: https://gitlab.com/pycqa/flake8 rev: 4.0.1 hooks: diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000000..4fea263214 --- /dev/null +++ b/.yamllint @@ -0,0 +1,3 @@ +--- +ignore: | + venv/ diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.yaml b/tests/component_tests/binary_sensor/test_binary_sensor.yaml index 912ae115eb..f98ce693f7 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.yaml +++ b/tests/component_tests/binary_sensor/test_binary_sensor.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 @@ -6,13 +7,13 @@ esphome: binary_sensor: - platform: gpio id: bs_1 - name: "test bs1" + name: test bs1 internal: true pin: number: D0 - platform: gpio id: bs_2 - name: "test bs2" + name: test bs2 internal: false pin: number: D1 diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml index 32d2e8d93b..48e13f0353 100644 --- a/tests/component_tests/button/test_button.yaml +++ b/tests/component_tests/button/test_button.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 diff --git a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml index 18a425df58..96514a677f 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep1.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep1.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP32 diff --git a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml index 49a7f510f2..0e8e598402 100644 --- a/tests/component_tests/deep_sleep/test_deep_sleep2.yaml +++ b/tests/component_tests/deep_sleep/test_deep_sleep2.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP32 diff --git a/tests/component_tests/sensor/test_sensor.yaml b/tests/component_tests/sensor/test_sensor.yaml index a38dd14041..8c0fd85b17 100644 --- a/tests/component_tests/sensor/test_sensor.yaml +++ b/tests/component_tests/sensor/test_sensor.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test platform: ESP8266 @@ -7,6 +8,6 @@ sensor: - platform: adc pin: A0 id: s_1 - name: "test s1" + name: test s1 update_interval: 60s - device_class: "voltage" + device_class: voltage diff --git a/tests/test1.yaml b/tests/test1.yaml index 1a9d69eac8..274b25548b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1,3 +1,4 @@ +--- substitutions: devicename: test1 sensorname: my @@ -43,13 +44,13 @@ esphome: json: key: !lambda |- return id(${textname}_text).state; - greeting: "Hello World" + greeting: Hello World - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: "Some data" + body: Some data verify_ssl: false on_response: then: @@ -95,8 +96,8 @@ mqtt: password: "debug" client_id: someclient use_abbreviations: false - discovery: True - discovery_retain: False + discovery: true + discovery_retain: false discovery_prefix: discovery discovery_unique_id_generator: legacy topic_prefix: helloworld @@ -109,7 +110,7 @@ mqtt: topic: topic/to/send/to payload: hi qos: 2 - retain: True + retain: true keepalive: 60s reboot_timeout: 60s on_message: @@ -154,6 +155,7 @@ mqtt: return effect; - light.control: id: ${roomname}_lights + # yamllint disable-line rule:line-length brightness: !lambda "return id(${roomname}_lights).current_values.get_brightness() + 0.5;" - light.dim_relative: id: ${roomname}_lights @@ -179,7 +181,7 @@ mqtt: i2c: sda: 21 scl: 22 - scan: True + scan: true frequency: 100kHz setup_priority: -100 id: i2c_bus @@ -192,10 +194,10 @@ spi: uart: - tx_pin: number: GPIO22 - inverted: yes + inverted: true rx_pin: number: GPIO23 - inverted: yes + inverted: true baud_rate: 115200 id: uart0 parity: NONE @@ -222,7 +224,7 @@ uart: rx_buffer_size: 1024 ota: - safe_mode: True + safe_mode: true password: "superlongpasswordthatnoonewillknow" port: 3286 reboot_timeout: 2min @@ -233,14 +235,14 @@ ota: ESP_LOGD("ota", "State %d", state); on_begin: then: - logger.log: "OTA begin" + logger.log: OTA begin on_progress: then: lambda: >- ESP_LOGD("ota", "Got progress %f", x); on_end: then: - logger.log: "OTA end" + logger.log: OTA end on_error: then: lambda: >- @@ -258,7 +260,7 @@ web_server: version: 2 power_supply: - id: "atx_power_supply" + id: atx_power_supply enable_time: 20ms keep_on_time: 10s pin: @@ -308,22 +310,22 @@ bedjet: id: my_bedjet_client time_id: sntp_time mcp23s08: - - id: "mcp23s08_hub" + - id: mcp23s08_hub cs_pin: GPIO12 deviceaddress: 0 mcp23s17: - - id: "mcp23s17_hub" + - id: mcp23s17_hub cs_pin: GPIO12 deviceaddress: 1 sensor: - platform: ble_client ble_client_id: ble_foo - name: "Green iTag btn" - service_uuid: "ffe0" - characteristic_uuid: "ffe1" - descriptor_uuid: "ffe2" + name: Green iTag btn + service_uuid: ffe0 + characteristic_uuid: ffe1 + descriptor_uuid: ffe2 notify: true update_interval: never lambda: |- @@ -335,7 +337,7 @@ sensor: ESP_LOGD("green_btn", "Button was pressed, val%f", x); - platform: adc pin: A0 - name: "Living Room Brightness" + name: Living Room Brightness update_interval: "1:01" attenuation: 2.5db unit_of_measurement: "°C" @@ -384,11 +386,13 @@ sensor: - lambda: return x * (9.0/5.0) + 32.0; on_value: then: + # yamllint disable rule:line-length - lambda: |- ESP_LOGD("main", "Got value %f", x); id(${sensorname}_sensor).publish_state(42.0); ESP_LOGI("main", "Value of my sensor: %f", id(${sensorname}_sensor).state); ESP_LOGI("main", "Raw Value of my sensor: %f", id(${sensorname}_sensor).state); + # yamllint enable rule:line-length on_value_range: above: 5 below: 10 @@ -405,18 +409,18 @@ sensor: ESP_LOGD("main", "Got raw value %f", x); - logger.log: level: DEBUG - format: "Got raw value %f" + format: Got raw value %f args: ["x"] - - logger.log: "Got raw value NAN" + - logger.log: Got raw value NAN - mqtt.publish: topic: some/topic payload: Hello qos: 2 - retain: True + retain: true - platform: esp32_hall name: ESP32 Hall Sensor - platform: ads1115 - multiplexer: "A0_A1" + multiplexer: A0_A1 gain: 1.024 id: ${sensorname}_sensor filters: @@ -427,57 +431,57 @@ sensor: cs_pin: 5 phase_a: voltage: - name: "EMON Line Voltage A" + name: EMON Line Voltage A current: - name: "EMON CT1 Current" + name: EMON CT1 Current power: - name: "EMON Active Power CT1" + name: EMON Active Power CT1 reactive_power: - name: "EMON Reactive Power CT1" + name: EMON Reactive Power CT1 power_factor: - name: "EMON Power Factor CT1" + name: EMON Power Factor CT1 gain_voltage: 7305 gain_ct: 27961 phase_b: current: - name: "EMON CT2 Current" + name: EMON CT2 Current power: - name: "EMON Active Power CT2" + name: EMON Active Power CT2 reactive_power: - name: "EMON Reactive Power CT2" + name: EMON Reactive Power CT2 power_factor: - name: "EMON Power Factor CT2" + name: EMON Power Factor CT2 gain_voltage: 7305 gain_ct: 27961 phase_c: current: - name: "EMON CT3 Current" + name: EMON CT3 Current power: - name: "EMON Active Power CT3" + name: EMON Active Power CT3 reactive_power: - name: "EMON Reactive Power CT3" + name: EMON Reactive Power CT3 power_factor: - name: "EMON Power Factor CT3" + name: EMON Power Factor CT3 gain_voltage: 7305 gain_ct: 27961 frequency: - name: "EMON Line Frequency" + name: EMON Line Frequency chip_temperature: - name: "EMON Chip Temp A" + name: EMON Chip Temp A line_frequency: 60Hz current_phases: 3 gain_pga: 2X - platform: bh1750 - name: "Living Room Brightness 3" + name: Living Room Brightness 3 internal: true address: 0x23 update_interval: 30s - retain: False + retain: false availability: state_topic: livingroom/custom_state_topic i2c_id: i2c_bus - platform: max44009 - name: "Outside Brightness 1" + name: Outside Brightness 1 internal: true address: 0x4A update_interval: 30s @@ -485,13 +489,13 @@ sensor: i2c_id: i2c_bus - platform: bme280 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure oversampling: none humidity: - name: "Outside Humidity" + name: Outside Humidity oversampling: 8x address: 0x77 iir_filter: 16x @@ -499,14 +503,14 @@ sensor: i2c_id: i2c_bus - platform: bme680 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure humidity: - name: "Outside Humidity" + name: Outside Humidity gas_resistance: - name: "Outside Gas Sensor" + name: Outside Gas Sensor address: 0x77 heater: temperature: 320 @@ -515,9 +519,9 @@ sensor: i2c_id: i2c_bus - platform: bmp085 temperature: - name: "Outside Temperature" + name: Outside Temperature pressure: - name: "Outside Pressure" + name: Outside Pressure filters: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); @@ -525,54 +529,54 @@ sensor: i2c_id: i2c_bus - platform: bmp280 temperature: - name: "Outside Temperature" + name: Outside Temperature oversampling: 16x pressure: - name: "Outside Pressure" + name: Outside Pressure address: 0x77 update_interval: 15s iir_filter: 16x i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 - name: "Living Room Temperature" + name: Living Room Temperature resolution: 9 - platform: dallas index: 1 - name: "Living Room Temperature 2" + name: Living Room Temperature 2 - platform: dht pin: GPIO26 temperature: - name: "Living Room Temperature 3" + name: Living Room Temperature 3 humidity: - name: "Living Room Humidity 3" + name: Living Room Humidity 3 model: AM2302 update_interval: 15s - platform: dht12 temperature: - name: "Living Room Temperature 4" + name: Living Room Temperature 4 humidity: - name: "Living Room Humidity 4" + name: Living Room Humidity 4 update_interval: 15s i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor - platform: esp32_hall - name: "ESP32 Hall Sensor" + name: ESP32 Hall Sensor update_interval: 15s - platform: ens210 temperature: - name: "Living Room Temperature 5" + name: Living Room Temperature 5 humidity: - name: 'Living Room Humidity 5' + name: Living Room Humidity 5 update_interval: 15s i2c_id: i2c_bus - platform: hdc1080 temperature: - name: 'Living Room Temperature 6' + name: Living Room Temperature 6 humidity: - name: 'Living Room Humidity 5' + name: Living Room Humidity 5 update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 @@ -580,14 +584,14 @@ sensor: cf_pin: 14 cf1_pin: 13 current: - name: "HLW8012 Current" + name: HLW8012 Current voltage: - name: "HLW8012 Voltage" + name: HLW8012 Voltage power: - name: "HLW8012 Power" + name: HLW8012 Power id: hlw8012_power energy: - name: "HLW8012 Energy" + name: HLW8012 Energy id: hlw8012_energy update_interval: 15s current_resistor: 0.001 ohm @@ -597,53 +601,53 @@ sensor: model: hlw8012 - platform: total_daily_energy power_id: hlw8012_power - name: "HLW8012 Total Daily Energy" + name: HLW8012 Total Daily Energy - platform: integration sensor: hlw8012_power - name: "Integration Sensor" + name: Integration Sensor time_unit: s - platform: integration sensor: hlw8012_power - name: "Integration Sensor lazy" + name: Integration Sensor lazy time_unit: s - platform: hmc5883l address: 0x68 field_strength_x: - name: "HMC5883L Field Strength X" + name: HMC5883L Field Strength X field_strength_y: - name: "HMC5883L Field Strength Y" + name: HMC5883L Field Strength Y field_strength_z: - name: "HMC5883L Field Strength Z" + name: HMC5883L Field Strength Z heading: - name: "HMC5883L Heading" + name: HMC5883L Heading range: 130uT oversampling: 8x update_interval: 15s i2c_id: i2c_bus - platform: honeywellabp pressure: - name: "Honeywell pressure" + name: Honeywell pressure min_pressure: 0 max_pressure: 15 temperature: - name: "Honeywell temperature" + name: Honeywell temperature cs_pin: GPIO5 - platform: qmc5883l address: 0x0D field_strength_x: - name: "QMC5883L Field Strength X" + name: QMC5883L Field Strength X field_strength_y: - name: "QMC5883L Field Strength Y" + name: QMC5883L Field Strength Y field_strength_z: - name: "QMC5883L Field Strength Z" + name: QMC5883L Field Strength Z heading: - name: "QMC5883L Heading" + name: QMC5883L Heading range: 800uT oversampling: 256x update_interval: 15s i2c_id: i2c_bus - platform: hx711 - name: "HX711 Value" + name: HX711 Value dout_pin: GPIO23 clk_pin: GPIO25 gain: 128 @@ -652,13 +656,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: "INA219 Current" + name: INA219 Current power: - name: "INA219 Power" + name: INA219 Power bus_voltage: - name: "INA219 Bus Voltage" + name: INA219 Bus Voltage shunt_voltage: - name: "INA219 Shunt Voltage" + name: INA219 Shunt Voltage max_voltage: 32.0V max_current: 3.2A update_interval: 15s @@ -667,13 +671,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: "INA226 Current" + name: INA226 Current power: - name: "INA226 Power" + name: INA226 Power bus_voltage: - name: "INA226 Bus Voltage" + name: INA226 Bus Voltage shunt_voltage: - name: "INA226 Shunt Voltage" + name: INA226 Shunt Voltage max_current: 3.2A update_interval: 15s i2c_id: i2c_bus @@ -682,17 +686,17 @@ sensor: channel_1: shunt_resistance: 0.1 ohm current: - name: "INA3221 Channel 1 Current" + name: INA3221 Channel 1 Current power: - name: "INA3221 Channel 1 Power" + name: INA3221 Channel 1 Power bus_voltage: - name: "INA3221 Channel 1 Bus Voltage" + name: INA3221 Channel 1 Bus Voltage shunt_voltage: - name: "INA3221 Channel 1 Shunt Voltage" + name: INA3221 Channel 1 Shunt Voltage update_interval: 15s i2c_id: i2c_bus - platform: kalman_combinator - name: "Kalman-filtered temperature" + name: Kalman-filtered temperature process_std_dev: 0.00139 sources: - source: scd30_temperature @@ -702,114 +706,114 @@ sensor: error: 1.5 - platform: htu21d temperature: - name: "Living Room Temperature 6" + name: Living Room Temperature 6 humidity: - name: "Living Room Humidity 6" + name: Living Room Humidity 6 update_interval: 15s i2c_id: i2c_bus - platform: max6675 - name: "Living Room Temperature" + name: Living Room Temperature cs_pin: GPIO23 update_interval: 15s - platform: max31855 - name: "Den Temperature" + name: Den Temperature cs_pin: GPIO23 update_interval: 15s reference_temperature: - name: "MAX31855 Internal Temperature" + name: MAX31855 Internal Temperature - platform: max31856 - name: "BBQ Temperature" + name: BBQ Temperature cs_pin: GPIO17 update_interval: 15s mains_filter: 50Hz - platform: max31865 - name: "Water Tank Temperature" + name: Water Tank Temperature cs_pin: GPIO23 update_interval: 15s - reference_resistance: "430 Ω" - rtd_nominal_resistance: "100 Ω" + reference_resistance: 430 Ω + rtd_nominal_resistance: 100 Ω - platform: mhz19 uart_id: uart0 co2: - name: "MH-Z19 CO2 Value" + name: MH-Z19 CO2 Value temperature: - name: "MH-Z19 Temperature" + name: MH-Z19 Temperature update_interval: 15s automatic_baseline_calibration: false - platform: mpu6050 address: 0x68 accel_x: - name: "MPU6050 Accel X" + name: MPU6050 Accel X accel_y: - name: "MPU6050 Accel Y" + name: MPU6050 Accel Y accel_z: - name: "MPU6050 Accel z" + name: MPU6050 Accel z gyro_x: - name: "MPU6050 Gyro X" + name: MPU6050 Gyro X gyro_y: - name: "MPU6050 Gyro Y" + name: MPU6050 Gyro Y gyro_z: - name: "MPU6050 Gyro z" + name: MPU6050 Gyro z temperature: - name: "MPU6050 Temperature" + name: MPU6050 Temperature i2c_id: i2c_bus - platform: mpu6886 address: 0x68 accel_x: - name: "MPU6886 Accel X" + name: MPU6886 Accel X accel_y: - name: "MPU6886 Accel Y" + name: MPU6886 Accel Y accel_z: - name: "MPU6886 Accel z" + name: MPU6886 Accel z gyro_x: - name: "MPU6886 Gyro X" + name: MPU6886 Gyro X gyro_y: - name: "MPU6886 Gyro Y" + name: MPU6886 Gyro Y gyro_z: - name: "MPU6886 Gyro z" + name: MPU6886 Gyro z temperature: - name: "MPU6886 Temperature" + name: MPU6886 Temperature i2c_id: i2c_bus - platform: dps310 temperature: - name: "DPS310 Temperature" + name: DPS310 Temperature pressure: - name: "DPS310 Pressure" + name: DPS310 Pressure address: 0x77 update_interval: 15s i2c_id: i2c_bus - platform: ms5611 temperature: - name: "Outside Temperature" + name: Outside Temperature pressure: - name: "Outside Pressure" + name: Outside Pressure address: 0x77 update_interval: 15s i2c_id: i2c_bus - platform: pmsa003i pm_1_0: - name: "PMSA003i PM1.0" + name: PMSA003i PM1.0 pm_2_5: - name: "PMSA003i PM2.5" + name: PMSA003i PM2.5 pm_10_0: - name: "PMSA003i PM10.0" + name: PMSA003i PM10.0 pmc_0_3: - name: "PMSA003i PMC <0.3µm" + name: PMSA003i PMC <0.3µm pmc_0_5: - name: "PMSA003i PMC <0.5µm" + name: PMSA003i PMC <0.5µm pmc_1_0: - name: "PMSA003i PMC <1µm" + name: PMSA003i PMC <1µm pmc_2_5: - name: "PMSA003i PMC <2.5µm" + name: PMSA003i PMC <2.5µm pmc_5_0: - name: "PMSA003i PMC <5µm" + name: PMSA003i PMC <5µm pmc_10_0: - name: "PMSA003i PMC <10µm" + name: PMSA003i PMC <10µm address: 0x12 - standard_units: True + standard_units: true i2c_id: i2c_bus - platform: pulse_counter - name: "Pulse Counter" + name: Pulse Counter pin: GPIO12 count_mode: rising_edge: INCREMENT @@ -817,7 +821,7 @@ sensor: internal_filter: 13us update_interval: 15s - platform: pulse_meter - name: "Pulse Meter" + name: Pulse Meter id: pulse_meter_sensor pin: GPIO12 internal_filter: 100ms @@ -827,20 +831,20 @@ sensor: id: pulse_meter_sensor value: 12345 total: - name: "Pulse Meter Total" + name: Pulse Meter Total - platform: qmp6988 temperature: - name: "Living Temperature QMP" + name: Living Temperature QMP oversampling: 32x pressure: - name: "Living Pressure QMP" + name: Living Pressure QMP oversampling: 2x address: 0x70 update_interval: 30s iir_filter: 16x i2c_id: i2c_bus - platform: rotary_encoder - name: "Rotary Encoder" + name: Rotary Encoder id: rotary_encoder1 pin_a: GPIO23 pin_b: GPIO25 @@ -860,49 +864,49 @@ sensor: id: rotary_encoder1 value: !lambda "return -1;" on_clockwise: - - logger.log: "Clockwise" + - logger.log: Clockwise on_anticlockwise: - - logger.log: "Anticlockwise" + - logger.log: Anticlockwise - platform: pulse_width name: Pulse Width pin: GPIO12 - platform: sm300d2 uart_id: uart0 co2: - name: "SM300D2 CO2 Value" + name: SM300D2 CO2 Value formaldehyde: - name: "SM300D2 Formaldehyde Value" + name: SM300D2 Formaldehyde Value tvoc: - name: "SM300D2 TVOC Value" + name: SM300D2 TVOC Value pm_2_5: - name: "SM300D2 PM2.5 Value" + name: SM300D2 PM2.5 Value pm_10_0: - name: "SM300D2 PM10 Value" + name: SM300D2 PM10 Value temperature: - name: "SM300D2 Temperature Value" + name: SM300D2 Temperature Value humidity: - name: "SM300D2 Humidity Value" + name: SM300D2 Humidity Value update_interval: 60s - platform: sht3xd temperature: - name: "Living Room Temperature 8" + name: Living Room Temperature 8 humidity: - name: "Living Room Humidity 8" + name: Living Room Humidity 8 address: 0x44 i2c_id: i2c_bus update_interval: 15s - platform: sts3x - name: "Living Room Temperature 9" + name: Living Room Temperature 9 address: 0x4A i2c_id: i2c_bus - platform: scd30 co2: - name: "Living Room CO2 9" + name: Living Room CO2 9 temperature: id: scd30_temperature - name: "Living Room Temperature 9" + name: Living Room Temperature 9 humidity: - name: "Living Room Humidity 9" + name: Living Room Humidity 9 address: 0x61 update_interval: 15s automatic_self_calibration: true @@ -913,12 +917,12 @@ sensor: - platform: scd4x id: scd40 co2: - name: "SCD4X CO2" + name: SCD4X CO2 temperature: id: scd4x_temperature - name: "SCD4X Temperature" + name: SCD4X Temperature humidity: - name: "SCD4X Humidity" + name: SCD4X Humidity update_interval: 15s automatic_self_calibration: true altitude_compensation: 10m @@ -927,63 +931,63 @@ sensor: i2c_id: i2c_bus - platform: sgp30 eco2: - name: "Workshop eCO2" + name: Workshop eCO2 accuracy_decimals: 1 tvoc: - name: "Workshop TVOC" + name: Workshop TVOC accuracy_decimals: 1 address: 0x58 update_interval: 5s i2c_id: i2c_bus - platform: sps30 pm_1_0: - name: "Workshop PM <1µm Weight concentration" - id: "workshop_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" + 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" + 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" + 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" + 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" + 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" + 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" + 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" + name: Workshop PM <10µm Number concentration + id: workshop_PMC_10_0 address: 0x69 update_interval: 10s i2c_id: i2c_bus - platform: sht4x temperature: - name: "SHT4X Temperature" + name: SHT4X Temperature humidity: - name: "SHT4X Humidity" + name: SHT4X Humidity address: 0x44 update_interval: 15s i2c_id: i2c_bus - platform: shtcx temperature: - name: "Living Room Temperature 10" + name: Living Room Temperature 10 humidity: - name: "Living Room Humidity 10" + name: Living Room Humidity 10 address: 0x70 update_interval: 15s i2c_id: i2c_bus - platform: template - name: "Template Sensor" + name: Template Sensor state_class: measurement id: template_sensor lambda: |- @@ -1001,7 +1005,7 @@ sensor: id: template_sensor state: !lambda "return NAN;" - platform: tsl2561 - name: "TSL2561 Ambient Light" + name: TSL2561 Ambient Light address: 0x39 update_interval: 15s is_cs_package: true @@ -1015,17 +1019,17 @@ sensor: integration_time: 600ms gain: high visible: - name: "tsl2591 visible" + name: tsl2591 visible id: tsl2591_vis - unit_of_measurement: "pH" + unit_of_measurement: pH infrared: - name: "tsl2591 infrared" + name: tsl2591 infrared id: tsl2591_ir full_spectrum: - name: "tsl2591 full_spectrum" + name: tsl2591 full_spectrum id: tsl2591_fs calculated_lux: - name: "tsl2591 calculated_lux" + name: tsl2591 calculated_lux id: tsl2591_cl i2c_id: i2c_bus - platform: ultrasonic @@ -1033,17 +1037,17 @@ sensor: echo_pin: number: GPIO23 inverted: true - name: "Ultrasonic Sensor" + name: Ultrasonic Sensor timeout: 5.5m id: ultrasonic_sensor1 - platform: uptime name: Uptime Sensor - platform: wifi_signal - name: "WiFi Signal Sensor" + name: WiFi Signal Sensor update_interval: 15s - platform: mqtt_subscribe - name: "MQTT Subscribe Sensor 1" - topic: "mqtt/topic" + name: MQTT Subscribe Sensor 1 + topic: mqtt/topic id: the_sensor qos: 2 on_value: @@ -1055,9 +1059,9 @@ sensor: - platform: sds011 uart_id: uart0 pm_2_5: - name: "SDS011 PM2.5" + name: SDS011 PM2.5 pm_10_0: - name: "SDS011 PM10.0" + name: SDS011 PM10.0 update_interval: 5min rx_only: false - platform: ccs811 @@ -1070,9 +1074,9 @@ sensor: i2c_id: i2c_bus - platform: tx20 wind_speed: - name: "Windspeed" + name: Windspeed wind_direction_degrees: - name: "Winddirection Degrees" + name: Winddirection Degrees pin: number: GPIO04 mode: INPUT @@ -1080,48 +1084,48 @@ sensor: clock_pin: GPIO5 data_pin: GPIO4 co2: - name: "ZyAura CO2" + name: ZyAura CO2 temperature: - name: "ZyAura Temperature" + name: ZyAura Temperature humidity: - name: "ZyAura Humidity" + name: ZyAura Humidity - platform: as3935 lightning_energy: - name: "Lightning Energy" + name: Lightning Energy distance: - name: "Distance Storm" + name: Distance Storm - platform: tmp117 - name: "TMP117 Temperature" + name: TMP117 Temperature update_interval: 5s i2c_id: i2c_bus - platform: hm3301 pm_1_0: - name: "PM1.0" + name: PM1.0 pm_2_5: - name: "PM2.5" + name: PM2.5 pm_10_0: - name: "PM10.0" + name: PM10.0 aqi: - name: "AQI" - calculation_type: "CAQI" + name: AQI + calculation_type: CAQI i2c_id: i2c_bus - platform: teleinfo - tag_name: "HCHC" - name: "hchc" - unit_of_measurement: "Wh" + tag_name: HCHC + name: hchc + unit_of_measurement: Wh icon: mdi:flash teleinfo_id: myteleinfo - platform: mcp9808 - name: "MCP9808 Temperature" + name: MCP9808 Temperature update_interval: 15s i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: "pH" + unit_of_measurement: pH i2c_id: i2c_bus - platform: sdp3x - name: "HVAC Filter Pressure drop" + name: HVAC Filter Pressure drop id: filter_pressure update_interval: 5s accuracy_decimals: 3 @@ -1129,11 +1133,11 @@ sensor: - platform: cs5460a id: cs5460a1 current: - name: "Socket current" + name: Socket current voltage: - name: "Mains voltage" + name: Mains voltage power: - name: "Socket power" + name: Socket power on_value: then: cs5460a.restart: cs5460a1 @@ -1141,8 +1145,8 @@ sensor: pga_gain: 10X current_gain: 0.01 voltage_gain: 0.000573 - current_hpf: on - voltage_hpf: on + current_hpf: true + voltage_hpf: true phase_offset: 20 pulse_energy: 0.01 kWh cs_pin: @@ -1151,7 +1155,7 @@ sensor: - platform: max9611 i2c_id: i2c_bus shunt_resistance: 0.2 ohm - gain: "1X" + gain: 1X voltage: name: Max9611 Voltage current: @@ -1163,7 +1167,7 @@ sensor: update_interval: 1s esp32_touch: - setup_mode: False + setup_mode: false iir_filter: 10ms sleep_duration: 27ms measurement_duration: 8ms @@ -1180,7 +1184,7 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #1" pin: @@ -1189,7 +1193,7 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #1 with interrupt" pin: @@ -1198,11 +1202,11 @@ binary_sensor: number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP - inverted: False + inverted: false interrupt: FALLING - platform: gpio pin: GPIO9 - name: "Living Room Window" + name: Living Room Window device_class: window filters: - invert: @@ -1242,7 +1246,7 @@ binary_sensor: - OFF for at least 0.2s then: - logger.log: - format: "Multi Clicked TWO" + format: Multi Clicked TWO level: warn - timing: - OFF for 1s to 2s @@ -1250,30 +1254,30 @@ binary_sensor: - OFF for at least 0.5s then: - logger.log: - format: "Multi Clicked LONG SINGLE" + format: Multi Clicked LONG SINGLE level: warn - timing: - ON for at most 1s - OFF for at least 0.5s then: - logger.log: - format: "Multi Clicked SINGLE" + format: Multi Clicked SINGLE level: warn id: binary_sensor1 - platform: gpio pin: number: GPIO9 mode: INPUT_PULLUP - name: "Living Room Window 2" + name: Living Room Window 2 - platform: status - name: "Living Room Status" + name: Living Room Status - platform: esp32_touch - name: "ESP32 Touch Pad GPIO27" + name: ESP32 Touch Pad GPIO27 pin: GPIO27 threshold: 1000 id: btn_left - platform: template - name: "Garage Door Open" + name: Garage Door Open id: garage_door lambda: |- if (isnan(id(${sensorname}_sensor).state)) { @@ -1291,7 +1295,7 @@ binary_sensor: on_press: - binary_sensor.template.publish: id: garage_door - state: OFF + state: false - output.ledc.set_frequency: id: gpio_19 frequency: 500.0Hz @@ -1301,41 +1305,41 @@ binary_sensor: - platform: pn532 pn532_id: pn532_bs uid: 74-10-37-94 - name: "PN532 NFC Tag" + name: PN532 NFC Tag - platform: rdm6300 uid: 7616525 - name: "RDM6300 NFC Tag" + name: RDM6300 NFC Tag - platform: gpio - name: "PCF binary sensor" + name: PCF binary sensor pin: pcf8574: pcf8574_hub number: 1 mode: INPUT - inverted: True + inverted: true - platform: gpio - name: "MCP21 binary sensor" + name: MCP21 binary sensor pin: mcp23xxx: mcp23017_hub number: 1 mode: INPUT - inverted: True + inverted: true - platform: gpio - name: "MCP22 binary sensor" + name: MCP22 binary sensor pin: mcp23xxx: mcp23008_hub number: 7 mode: INPUT_PULLUP - inverted: False + inverted: false - platform: gpio - name: "MCP23 binary sensor" + name: MCP23 binary sensor pin: mcp23016: mcp23016_hub number: 7 mode: INPUT - inverted: False + inverted: false - platform: remote_receiver - name: "Raw Remote Receiver Test" + name: Raw Remote Receiver Test raw: code: [ @@ -1376,7 +1380,7 @@ binary_sensor: 1709, ] - platform: as3935 - name: "Storm Alert" + name: Storm Alert - platform: analog_threshold name: Analog Trheshold 1 sensor_id: template_sensor @@ -1434,7 +1438,7 @@ output: pin: GPIO26 id: gpio_26 power_supply: atx_power_supply - inverted: False + inverted: false - platform: ledc pin: 19 id: gpio_19 @@ -1468,67 +1472,67 @@ output: - platform: tlc59208f id: tlc_0 channel: 0 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_1 channel: 1 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_2 channel: 2 - tlc59208f_id: "tlc59208f_1" + tlc59208f_id: tlc59208f_1 - platform: tlc59208f id: tlc_3 channel: 0 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_4 channel: 1 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_5 channel: 2 - tlc59208f_id: "tlc59208f_2" + tlc59208f_id: tlc59208f_2 - platform: tlc59208f id: tlc_6 channel: 0 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: tlc59208f id: tlc_7 channel: 1 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: tlc59208f id: tlc_8 channel: 2 - tlc59208f_id: "tlc59208f_3" + tlc59208f_id: tlc59208f_3 - platform: gpio id: id2 pin: pcf8574: pcf8574_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id22 pin: mcp23xxx: mcp23017_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id23 pin: mcp23xxx: mcp23008_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio id: id25 pin: mcp23016: mcp23016_hub number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: my9231 id: my_0 channel: 0 @@ -1588,27 +1592,27 @@ e131: light: - platform: binary - name: "Desk Lamp" + name: Desk Lamp output: gpio_26 effects: - strobe: - strobe: - name: "My Strobe" + name: My Strobe colors: - - state: True + - state: true duration: 250ms - - state: False + - state: false duration: 250ms on_turn_on: - switch.template.publish: id: livingroom_lights - state: yes + state: true on_turn_off: - switch.template.publish: id: livingroom_lights - state: yes + state: true - platform: monochromatic - name: "Kitchen Lights" + name: Kitchen Lights id: kitchen output: gpio_19 gamma_correct: 2.8 @@ -1617,7 +1621,7 @@ light: - strobe: - flicker: - flicker: - name: "My Flicker" + name: My Flicker alpha: 98% intensity: 1.5% - lambda: @@ -1629,20 +1633,20 @@ light: if (state == 4) state = 0; - platform: rgb - name: "Living Room Lights" + name: Living Room Lights id: ${roomname}_lights red: pca_0 green: pca_1 blue: pca_2 - platform: rgbw - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 white: pca_6 color_interlock: true - platform: rgbww - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 @@ -1652,7 +1656,7 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: rgbct - name: "Living Room Lights 2" + name: Living Room Lights 2 red: pca_3 green: pca_4 blue: pca_5 @@ -1662,14 +1666,14 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: cwww - name: "Living Room Lights 2" + name: Living Room Lights 2 cold_white: pca_6 warm_white: pca_6 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true - platform: color_temperature - name: "Living Room Lights 2" + name: Living Room Lights 2 color_temperature: pca_6 brightness: pca_6 cold_white_color_temperature: 153 mireds @@ -1683,7 +1687,7 @@ light: max_refresh_rate: 20ms power_supply: atx_power_supply color_correct: [75%, 100%, 50%] - name: "FastLED WS2811 Light" + name: FastLED WS2811 Light effects: - addressable_color_wipe: - addressable_color_wipe: @@ -1698,7 +1702,7 @@ light: blue: 0% num_leds: 1 add_led_interval: 100ms - reverse: False + reverse: false - addressable_scan: - addressable_scan: name: Scan Effect With Custom Values @@ -1726,7 +1730,7 @@ light: update_interval: 16ms intensity: 5% - addressable_lambda: - name: "Test For Custom Lambda Effect" + name: Test For Custom Lambda Effect lambda: |- if (initial_run) { it[0] = current_color; @@ -1762,10 +1766,10 @@ light: data_rate: 2MHz num_leds: 60 rgb_order: BRG - name: "FastLED SPI Light" + name: FastLED SPI Light - platform: neopixelbus id: addr3 - name: "Neopixelbus Light" + name: Neopixelbus Light gamma_correct: 2.8 color_correct: [0.0, 0.0, 0.0, 0.0] default_transition_length: 10s @@ -1781,7 +1785,7 @@ light: num_leds: 60 pin: GPIO23 - platform: partition - name: "Partition Light" + name: Partition Light segments: - id: addr1 from: 0 @@ -1795,13 +1799,13 @@ light: - single_light_id: ${roomname}_lights - platform: shelly_dimmer - name: "Shelly Dimmer Light" + name: Shelly Dimmer Light power: - name: "Shelly Dimmer Power" + name: Shelly Dimmer Power voltage: - name: "Shelly Dimmer Voltage" + name: Shelly Dimmer Voltage current: - name: "Shelly Dimmer Current" + name: Shelly Dimmer Current max_brightness: 500 firmware: "51.6" uart_id: uart0 @@ -1813,8 +1817,8 @@ remote_transmitter: climate: - platform: tcl112 name: TCL112 Climate With Sensor - supports_heat: True - supports_cool: True + supports_heat: true + supports_cool: true sensor: ${sensorname}_sensor - platform: tcl112 name: TCL112 Climate @@ -1836,8 +1840,8 @@ climate: target_temperature_state_topic: target/temperature/state/topic - platform: coolix name: Coolix Climate With Sensor - supports_heat: True - supports_cool: True + supports_heat: true + supports_cool: true sensor: ${sensorname}_sensor - platform: coolix name: Coolix Climate @@ -1871,7 +1875,7 @@ climate: use_fahrenheit: true - platform: midea on_state: - logger.log: "State changed!" + logger.log: State changed! id: midea_unit uart_id: uart0 name: Midea Climate @@ -1905,11 +1909,11 @@ climate: - HORIZONTAL - BOTH outdoor_temperature: - name: "Temp" + name: Temp power_usage: - name: "Power" + name: Power humidity_setpoint: - name: "Humidity" + name: Humidity - platform: anova name: Anova cooker ble_client_id: ble_blah @@ -1955,7 +1959,7 @@ switch: # Use pin number 0 number: 0 mode: OUTPUT - inverted: False + inverted: false - platform: gpio name: "MCP23S17 Pin #0" pin: @@ -1963,12 +1967,12 @@ switch: # Use pin number 0 number: 1 mode: OUTPUT - inverted: False + inverted: false - platform: gpio pin: GPIO25 - name: "Living Room Dehumidifier" + name: Living Room Dehumidifier icon: "mdi:restart" - inverted: True + inverted: true command_topic: custom_command_topic command_retain: true restore_mode: ALWAYS_OFF @@ -2045,20 +2049,20 @@ switch: remote_transmitter.transmit_rc_switch_type_a: group: "11001" device: "01000" - state: True + state: true protocol: pulse_length: 175 sync: [1, 31] zero: [1, 3] one: [3, 1] - inverted: False + inverted: false - platform: template name: RC Switch Type B turn_on_action: remote_transmitter.transmit_rc_switch_type_b: address: 4 channel: 2 - state: True + state: true - platform: template name: RC Switch Type C turn_on_action: @@ -2066,14 +2070,14 @@ switch: family: "a" group: 1 device: 2 - state: True + state: true - platform: template name: RC Switch Type D turn_on_action: remote_transmitter.transmit_rc_switch_type_d: group: "a" device: 2 - state: True + state: true - platform: template name: RC5 turn_on_action: @@ -2091,12 +2095,49 @@ switch: turn_on_action: 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] + 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: Living Room Lights id: livingroom_lights - optimistic: True - assumed_state: yes + optimistic: true + assumed_state: true turn_on_action: - switch.turn_on: living_room_lights_on - output.set_level: @@ -2119,22 +2160,22 @@ switch: level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off - restore_state: False + restore_state: false on_turn_on: - switch.template.publish: id: livingroom_lights - state: yes + state: true - platform: restart - name: "Living Room Restart" + name: Living Room Restart - platform: safe_mode - name: "Living Room Restart (Safe Mode)" + name: Living Room Restart (Safe Mode) - platform: shutdown - name: "Living Room Shutdown" + name: Living Room Shutdown - platform: output - name: "Generic Output" + name: Generic Output output: pca_6 - platform: template - name: "Template Switch" + name: Template Switch id: my_switch lambda: |- if (id(binary_sensor1).state) { @@ -2152,27 +2193,27 @@ switch: // Switch is OFF, do something else here } optimistic: true - assumed_state: no - restore_state: True + assumed_state: false + restore_state: true on_turn_off: - switch.template.publish: id: my_switch state: !lambda "return false;" - platform: uart uart_id: uart0 - name: "UART String Output" - data: "DataToSend" + name: UART String Output + data: DataToSend - platform: uart uart_id: uart0 - name: "UART Bytes Output" + name: UART Bytes Output data: [0xDE, 0xAD, 0xBE, 0xEF] - platform: uart uart_id: uart0 - name: "UART Recurring Output" + name: UART Recurring Output data: [0xDE, 0xAD, 0xBE, 0xEF] send_every: 1s - platform: template - assumed_state: yes + assumed_state: true name: Stepper Switch turn_on_action: - stepper.set_target: @@ -2194,7 +2235,7 @@ switch: sn74hc595: sn74hc595_hub # Use pin number 0 number: 0 - inverted: False + inverted: false - platform: template id: ble1_status optimistic: true @@ -2206,7 +2247,7 @@ switch: fan: - platform: binary output: gpio_26 - name: "Living Room Fan 1" + name: Living Room Fan 1 oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed @@ -2214,7 +2255,7 @@ fan: icon: mdi:weather-windy output: pca_6 speed_count: 10 - name: "Living Room Fan 2" + name: Living Room Fan 2 oscillation_output: gpio_19 direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic @@ -2225,13 +2266,13 @@ fan: speed_command_topic: speed/command/topic on_speed_set: then: - - logger.log: "Fan speed was changed!" + - logger.log: Fan speed was changed! - platform: bedjet name: My Bedjet fan bedjet_id: my_bedjet_client - platform: copy source_id: fan_speed - name: "Fan Speed Copy" + name: Fan Speed Copy interval: - interval: 10s @@ -2242,6 +2283,7 @@ interval: - display.page.show_previous: display1 - interval: 2s then: + # yamllint disable rule:line-length - lambda: |- static uint16_t btn_left_state = id(btn_left)->get_value(); @@ -2250,13 +2292,14 @@ interval: btn_left_state = ((uint32_t) id(btn_left)->get_value() + 63 * (uint32_t)btn_left_state) >> 6; id(btn_left)->set_threshold(btn_left_state * 0.9); + # yamllint enable rule:line-length - if: condition: display.is_displaying_page: id: display1 page_id: page1 then: - - logger.log: "Seeing page 1" + - logger.log: Seeing page 1 color: - id: kbx_red @@ -2328,7 +2371,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1306_i2c - model: "SSD1306_128X64" + model: SSD1306_128X64 reset_pin: GPIO23 address: 0x3C id: display1 @@ -2349,28 +2392,28 @@ display: ESP_LOGD("display", "1 -> 2"); i2c_id: i2c_bus - platform: ssd1306_spi - model: "SSD1306 128x64" + model: SSD1306 128x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi - model: "SSD1322 256x64" + model: SSD1322 256x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1325_spi - model: "SSD1325 128x64" + model: SSD1325 128x64 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1327_i2c - model: "SSD1327 128X128" + model: SSD1327 128X128 reset_pin: GPIO23 address: 0x3D id: display1327 @@ -2384,7 +2427,7 @@ display: // Nothing i2c_id: i2c_bus - platform: ssd1327_spi - model: "SSD1327 128x128" + model: SSD1327 128x128 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2397,7 +2440,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1351_spi - model: "SSD1351 128x128" + model: SSD1351 128x128 cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2420,7 +2463,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 - model: "INITR_BLACKTAB" + model: INITR_BLACKTAB cs_pin: GPIO5 dc_pin: GPIO16 reset_pin: GPIO23 @@ -2432,7 +2475,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9341 - model: "TFT 2.4" + model: TFT 2.4 cs_pin: GPIO5 dc_pin: GPIO4 reset_pin: GPIO22 @@ -2442,7 +2485,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9341 - model: "TFT 2.4" + model: TFT 2.4 cs_pin: GPIO5 dc_pin: GPIO4 reset_pin: GPIO22 @@ -2529,7 +2572,7 @@ rc522_i2c: mcp4728: - id: mcp4728_dac - store_in_eeprom: False + store_in_eeprom: false address: 0x60 i2c_id: i2c_bus @@ -2562,7 +2605,7 @@ time: cover: - platform: template - name: "Template Cover" + name: Template Cover id: template_cover lambda: |- if (id(binary_sensor1).state) { @@ -2575,8 +2618,8 @@ cover: - cover.template.publish: id: template_cover state: CLOSED - assumed_state: no - has_position: yes + assumed_state: false + has_position: true position_state_topic: position/state/topic position_command_topic: position/command/topic tilt_lambda: !lambda "return 0.5;" @@ -2589,12 +2632,12 @@ cover: then: - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 - name: "Test AM43" + name: Test AM43 id: am43_test ble_client_id: ble_foo icon: mdi:blinds - platform: feedback - name: "Feedback Cover" + name: Feedback Cover id: gate device_class: gate @@ -2638,24 +2681,24 @@ tca9548a: i2c_id: multiplex0_chan0 pcf8574: - - id: "pcf8574_hub" + - id: pcf8574_hub address: 0x21 - pcf8575: False + pcf8575: false i2c_id: i2c_bus mcp23017: - - id: "mcp23017_hub" - open_drain_interrupt: "true" + - id: mcp23017_hub + open_drain_interrupt: true i2c_id: i2c_bus mcp23008: - - id: "mcp23008_hub" + - id: mcp23008_hub address: 0x22 - open_drain_interrupt: "true" + open_drain_interrupt: true i2c_id: i2c_bus mcp23016: - - id: "mcp23016_hub" + - id: mcp23016_hub address: 0x23 i2c_id: i2c_bus @@ -2672,29 +2715,29 @@ stepper: globals: - id: glob_int type: int - restore_value: yes + restore_value: true initial_value: "0" - id: glob_float type: float - restore_value: yes + restore_value: true initial_value: "0.0f" - id: glob_bool type: bool - restore_value: no + restore_value: false initial_value: "true" - id: glob_string type: std::string - restore_value: no + restore_value: false # initial_value: "" - id: glob_bool_processed type: bool - restore_value: no + restore_value: false initial_value: "false" text_sensor: - platform: ble_client ble_client_id: ble_foo - name: "Sensor Location" + name: Sensor Location service_uuid: "180d" characteristic_uuid: "2a38" descriptor_uuid: "2902" @@ -2705,7 +2748,7 @@ text_sensor: - lambda: |- ESP_LOGD("green_btn", "Location changed: %s", x.c_str()); - platform: mqtt_subscribe - name: "MQTT Subscribe Text" + name: MQTT Subscribe Text topic: "the/topic" qos: 2 on_value: @@ -2742,25 +2785,25 @@ text_sensor: id: ${textname}_text - platform: wifi_info scan_results: - name: "Scan Results" + name: Scan Results ip_address: - name: "IP Address" + name: IP Address ssid: - name: "SSID" + name: SSID bssid: - name: "BSSID" + name: BSSID mac_address: - name: "Mac Address" + name: Mac Address - platform: version - name: "ESPHome Version No Timestamp" - hide_timestamp: True + name: ESPHome Version No Timestamp + hide_timestamp: true - platform: teleinfo - tag_name: "OPTARIF" - name: "optarif" + tag_name: OPTARIF + name: optarif teleinfo_id: myteleinfo sn74hc595: - - id: "sn74hc595_hub" + - id: sn74hc595_hub data_pin: GPIO21 clock_pin: GPIO23 latch_pin: GPIO22 @@ -2883,7 +2926,7 @@ qr_code: lock: - platform: template id: test_lock1 - name: "Template Switch" + name: Template Switch lambda: |- if (id(binary_sensor1).state) { return LOCK_STATE_LOCKED; @@ -2891,7 +2934,7 @@ lock: return LOCK_STATE_UNLOCKED; } optimistic: true - assumed_state: no + assumed_state: false on_unlock: - lock.template.publish: id: test_lock1 @@ -2901,7 +2944,7 @@ lock: id: test_lock1 state: !lambda "return LOCK_STATE_LOCKED;" - platform: output - name: "Generic Output Lock" + name: Generic Output Lock id: test_lock2 output: pca_6 - platform: copy @@ -2910,7 +2953,7 @@ lock: button: - platform: template - name: "Start calibration" + name: Start calibration on_press: - scd4x.perform_forced_calibration: value: 419 diff --git a/tests/test2.yaml b/tests/test2.yaml index 3dd8f824dd..d01a0a742a 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $devicename platform: ESP32 @@ -28,7 +29,7 @@ api: i2c: sda: 21 scl: 22 - scan: False + scan: false spi: clk_pin: GPIO21 @@ -47,7 +48,7 @@ uart: - lambda: UARTDebug::log_hex(direction, bytes, ':'); ota: - safe_mode: True + safe_mode: true port: 3286 num_attempts: 15 @@ -67,7 +68,7 @@ as3935_i2c: irq_pin: GPIO12 mcp3008: - - id: 'mcp3008_hub' + - id: mcp3008_hub cs_pin: GPIO12 output: @@ -86,35 +87,35 @@ sensor: id: ha_hello_world_temperature - platform: ble_rssi mac_address: AC:37:43:77:5F:4C - name: 'BLE Google Home Mini RSSI value' + name: BLE Google Home Mini RSSI value - platform: ble_rssi - service_uuid: '11aa' - name: 'BLE Test Service 16' + service_uuid: 11aa + name: BLE Test Service 16 - platform: ble_rssi - service_uuid: '11223344' - name: 'BLE Test Service 32' + service_uuid: "11223344" + name: BLE Test Service 32 - platform: ble_rssi - service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' - name: 'BLE Test Service 128' + 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' + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test iBeacon UUID - platform: b_parasite mac_address: F0:CA:F0:CA:01:01 humidity: - name: 'b-parasite Air Humidity' + name: b-parasite Air Humidity temperature: - name: 'b-parasite Air Temperature' + name: b-parasite Air Temperature moisture: - name: 'b-parasite Soil Moisture' + name: b-parasite Soil Moisture battery_voltage: - name: 'b-parasite Battery Voltage' + name: b-parasite Battery Voltage illuminance: - name: 'b-parasite Illuminance' + name: b-parasite Illuminance - platform: senseair id: senseair0 co2: - name: 'SenseAir CO2 Value' + name: SenseAir CO2 Value on_value: then: - senseair.background_calibration: senseair0 @@ -126,167 +127,167 @@ sensor: - platform: ruuvitag mac_address: FF:56:D3:2F:7D:E8 humidity: - name: 'RuuviTag Humidity' + name: RuuviTag Humidity temperature: - name: 'RuuviTag Temperature' + name: RuuviTag Temperature pressure: - name: 'RuuviTag Pressure' + name: RuuviTag Pressure acceleration_x: - name: 'RuuviTag Acceleration X' + name: RuuviTag Acceleration X acceleration_y: - name: 'RuuviTag Acceleration Y' + name: RuuviTag Acceleration Y acceleration_z: - name: 'RuuviTag Acceleration Z' + name: RuuviTag Acceleration Z battery_voltage: - name: 'RuuviTag Battery Voltage' + name: RuuviTag Battery Voltage tx_power: - name: 'RuuviTag TX Power' + name: RuuviTag TX Power movement_counter: - name: 'RuuviTag Movement Counter' + name: RuuviTag Movement Counter measurement_sequence_number: - name: 'RuuviTag Measurement Sequence Number' + name: RuuviTag Measurement Sequence Number - platform: as3935 lightning_energy: - name: 'Lightning Energy' + name: Lightning Energy distance: - name: 'Distance Storm' + name: Distance Storm - platform: xiaomi_hhccjcy01 mac_address: 94:2B:FF:5C:91:61 temperature: - name: 'Xiaomi HHCCJCY01 Temperature' + name: Xiaomi HHCCJCY01 Temperature moisture: - name: 'Xiaomi HHCCJCY01 Moisture' + name: Xiaomi HHCCJCY01 Moisture illuminance: - name: 'Xiaomi HHCCJCY01 Illuminance' + name: Xiaomi HHCCJCY01 Illuminance conductivity: - name: 'Xiaomi HHCCJCY01 Soil Conductivity' + name: Xiaomi HHCCJCY01 Soil Conductivity battery_level: - name: 'Xiaomi HHCCJCY01 Battery Level' + name: Xiaomi HHCCJCY01 Battery Level - platform: xiaomi_lywsdcgq mac_address: 7A:80:8E:19:36:BA temperature: - name: 'Xiaomi LYWSDCGQ Temperature' + name: Xiaomi LYWSDCGQ Temperature humidity: - name: 'Xiaomi LYWSDCGQ Humidity' + name: Xiaomi LYWSDCGQ Humidity battery_level: - name: 'Xiaomi LYWSDCGQ Battery Level' + name: Xiaomi LYWSDCGQ Battery Level - platform: xiaomi_lywsd02 mac_address: 3F:5B:7D:82:58:4E temperature: - name: 'Xiaomi LYWSD02 Temperature' + name: Xiaomi LYWSD02 Temperature humidity: - name: 'Xiaomi LYWSD02 Humidity' + name: Xiaomi LYWSD02 Humidity battery_level: - name: 'Xiaomi LYWSD02 Battery Level' + name: Xiaomi LYWSD02 Battery Level - platform: xiaomi_cgg1 mac_address: 7A:80:8E:19:36:BA temperature: - name: 'Xiaomi CGG1 Temperature' + name: Xiaomi CGG1 Temperature humidity: - name: 'Xiaomi CGG1 Humidity' + name: Xiaomi CGG1 Humidity battery_level: - name: 'Xiaomi CGG1 Battery Level' + name: Xiaomi CGG1 Battery Level - platform: xiaomi_gcls002 - mac_address: '94:2B:FF:5C:91:61' + mac_address: 94:2B:FF:5C:91:61 temperature: - name: 'GCLS02 Temperature' + name: GCLS02 Temperature moisture: - name: 'GCLS02 Moisture' + name: GCLS02 Moisture conductivity: - name: 'GCLS02 Soil Conductivity' + name: GCLS02 Soil Conductivity illuminance: - name: 'GCLS02 Illuminance' + name: GCLS02 Illuminance - platform: xiaomi_hhccpot002 - mac_address: '94:2B:FF:5C:91:61' + mac_address: 94:2B:FF:5C:91:61 moisture: - name: 'HHCCPOT002 Moisture' + name: HHCCPOT002 Moisture conductivity: - name: 'HHCCPOT002 Soil Conductivity' + name: HHCCPOT002 Soil Conductivity - platform: xiaomi_lywsd03mmc - mac_address: 'A4:C1:38:4E:16:78' - bindkey: 'e9efaa6873f9f9c87a5e75a5f814801c' + mac_address: A4:C1:38:4E:16:78 + bindkey: e9efaa6873f9f9c87a5e75a5f814801c temperature: - name: 'Xiaomi LYWSD03MMC Temperature' + name: Xiaomi LYWSD03MMC Temperature humidity: - name: 'Xiaomi LYWSD03MMC Humidity' + name: Xiaomi LYWSD03MMC Humidity battery_level: - name: 'Xiaomi LYWSD03MMC Battery Level' + name: Xiaomi LYWSD03MMC Battery Level - platform: xiaomi_cgd1 - mac_address: 'A4:C1:38:D1:61:7D' - bindkey: 'c99d2313182473b38001086febf781bd' + mac_address: A4:C1:38:D1:61:7D + bindkey: c99d2313182473b38001086febf781bd temperature: - name: 'Xiaomi CGD1 Temperature' + name: Xiaomi CGD1 Temperature humidity: - name: 'Xiaomi CGD1 Humidity' + name: Xiaomi CGD1 Humidity battery_level: - name: 'Xiaomi CGD1 Battery Level' + name: Xiaomi CGD1 Battery Level - platform: xiaomi_jqjcy01ym - mac_address: '7A:80:8E:19:36:BA' + mac_address: 7A:80:8E:19:36:BA temperature: - name: 'JQJCY01YM Temperature' + name: JQJCY01YM Temperature humidity: - name: 'JQJCY01YM Humidity' + name: JQJCY01YM Humidity formaldehyde: - name: 'JQJCY01YM Formaldehyde' + name: JQJCY01YM Formaldehyde battery_level: - name: 'JQJCY01YM Battery Level' + name: JQJCY01YM Battery Level - platform: xiaomi_mhoc303 - mac_address: 'E7:50:59:32:A0:1C' + mac_address: E7:50:59:32:A0:1C temperature: - name: 'MHO-C303 Temperature' + name: MHO-C303 Temperature humidity: - name: 'MHO-C303 Humidity' + name: MHO-C303 Humidity battery_level: - name: 'MHO-C303 Battery Level' + name: MHO-C303 Battery Level - platform: atc_mithermometer - mac_address: 'A4:C1:38:4E:16:78' + mac_address: A4:C1:38:4E:16:78 temperature: - name: 'ATC Temperature' + name: ATC Temperature humidity: - name: 'ATC Humidity' + name: ATC Humidity battery_level: - name: 'ATC Battery-Level' + name: ATC Battery-Level battery_voltage: - name: 'ATC Battery-Voltage' + name: ATC Battery-Voltage - platform: pvvx_mithermometer - mac_address: 'A4:C1:38:4E:16:78' + mac_address: A4:C1:38:4E:16:78 temperature: - name: 'PVVX Temperature' + name: PVVX Temperature humidity: - name: 'PVVX Humidity' + name: PVVX Humidity battery_level: - name: 'PVVX Battery-Level' + name: PVVX Battery-Level battery_voltage: - name: 'PVVX Battery-Voltage' + name: PVVX Battery-Voltage - platform: inkbird_ibsth1_mini mac_address: 38:81:D7:0A:9C:11 temperature: - name: 'Inkbird IBS-TH1 Temperature' + name: Inkbird IBS-TH1 Temperature humidity: - name: 'Inkbird IBS-TH1 Humidity' + name: Inkbird IBS-TH1 Humidity battery_level: - name: 'Inkbird IBS-TH1 Battery Level' + name: Inkbird IBS-TH1 Battery Level - platform: xiaomi_rtcgq02lm id: motion_rtcgq02lm battery_level: - name: 'Mi Motion Sensor 2 Battery level' + name: Mi Motion Sensor 2 Battery level - platform: ltr390 uv: - name: "LTR390 UV" + name: LTR390 UV uv_index: - name: "LTR390 UVI" + name: LTR390 UVI light: - name: "LTR390 Light" + name: LTR390 Light ambient_light: - name: "LTR390 ALS" - gain: "X3" + name: LTR390 ALS + gain: X3 resolution: 18 window_correction_factor: 1.0 address: 0x53 update_interval: 60s - platform: sgp4x voc: - name: "VOC Index" + name: VOC Index id: sgp40_voc_index algorithm_tuning: index_offset: 100 @@ -296,7 +297,7 @@ sensor: std_initial: 50 gain_factor: 230 nox: - name: "NOx" + name: NOx algorithm_tuning: index_offset: 100 learning_time_offset_hours: 12 @@ -307,7 +308,7 @@ sensor: update_interval: 5s - platform: mcp3008 update_interval: 5s - mcp3008_id: 'mcp3008_hub' + mcp3008_id: mcp3008_hub id: freezer_temp_source reference_voltage: 3.19 number: 0 @@ -315,59 +316,59 @@ sensor: ble_client_id: airthings01 update_interval: 5min temperature: - name: "Wave Plus Temperature" + name: Wave Plus Temperature radon: - name: "Wave Plus Radon" + name: Wave Plus Radon radon_long_term: - name: "Wave Plus Radon Long Term" + name: Wave Plus Radon Long Term pressure: - name: "Wave Plus Pressure" + name: Wave Plus Pressure humidity: - name: "Wave Plus Humidity" + name: Wave Plus Humidity co2: - name: "Wave Plus CO2" + name: Wave Plus CO2 tvoc: - name: "Wave Plus VOC" + name: Wave Plus VOC - platform: airthings_wave_mini ble_client_id: airthingsmini01 update_interval: 5min temperature: - name: "Wave Mini Temperature" + name: Wave Mini Temperature humidity: - name: "Wave Mini Humidity" + name: Wave Mini Humidity pressure: - name: "Wave Mini Pressure" + name: Wave Mini Pressure tvoc: - name: "Wave Mini VOC" + name: Wave Mini VOC - platform: ina260 address: 0x40 current: - name: "INA260 Current" + name: INA260 Current power: - name: "INA260 Power" + name: INA260 Power bus_voltage: - name: "INA260 Voltage" + name: INA260 Voltage update_interval: 60s - platform: radon_eye_rd200 ble_client_id: radon_eye_ble_id update_interval: 10min radon: - name: "RD200 Radon" + name: RD200 Radon radon_long_term: - name: "RD200 Radon Long Term" + name: RD200 Radon Long Term - 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" + name: Propane test temp level: - name: "Propane test level" + name: Propane test level distance: - name: "Propane test distance" + name: Propane test distance battery_level: - name: "Propane test battery level" + name: Propane test battery level time: - platform: homeassistant @@ -377,7 +378,7 @@ time: - logger.log: It's 16:00 esp32_touch: - setup_mode: True + setup_mode: true binary_sensor: - platform: homeassistant @@ -389,76 +390,80 @@ binary_sensor: id: ha_hello_world_binary_attribute - platform: ble_presence mac_address: AC:37:43:77:5F:4C - name: 'ESP32 BLE Tracker Google Home Mini' + name: ESP32 BLE Tracker Google Home Mini - platform: ble_presence - service_uuid: '11aa' - name: 'BLE Test Service 16 Presence' + service_uuid: 11aa + name: BLE Test Service 16 Presence - platform: ble_presence - service_uuid: '11223344' - name: 'BLE Test Service 32 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' + service_uuid: 11223344-5566-7788-99aa-bbccddeeff00 + name: BLE Test Service 128 Presence - platform: ble_presence - ibeacon_uuid: '11223344-5566-7788-99aa-bbccddeeff00' + ibeacon_uuid: 11223344-5566-7788-99aa-bbccddeeff00 ibeacon_major: 100 ibeacon_minor: 1 - name: 'BLE Test iBeacon Presence' + name: BLE Test iBeacon Presence - platform: esp32_touch - name: 'ESP32 Touch Pad GPIO27' + name: ESP32 Touch Pad GPIO27 pin: GPIO27 threshold: 1000 - platform: as3935 - name: 'Storm Alert' + name: Storm Alert - platform: xiaomi_mue4094rt - name: 'MUE4094RT Motion' - mac_address: '7A:80:8E:19:36:BA' - timeout: '5s' + name: MUE4094RT Motion + mac_address: 7A:80:8E:19:36:BA + timeout: 5s - platform: xiaomi_mjyd02yla - name: 'MJYD02YL-A Motion' - mac_address: '50:EC:50:CD:32:02' - bindkey: '48403ebe2d385db8d0c187f81e62cb64' + name: MJYD02YL-A Motion + mac_address: 50:EC:50:CD:32:02 + bindkey: 48403ebe2d385db8d0c187f81e62cb64 idle_time: - name: 'MJYD02YL-A Idle Time' + name: MJYD02YL-A Idle Time light: - name: 'MJYD02YL-A Light Status' + name: MJYD02YL-A Light Status battery_level: - name: 'MJYD02YL-A Battery Level' + name: MJYD02YL-A Battery Level - platform: xiaomi_wx08zm - name: 'WX08ZM Activation State' - mac_address: '74:a3:4a:b5:07:34' + name: WX08ZM Activation State + mac_address: 74:a3:4a:b5:07:34 tablet: - name: 'WX08ZM Tablet Resource' + name: WX08ZM Tablet Resource battery_level: - name: 'WX08ZM Battery Level' + name: WX08ZM Battery Level - platform: xiaomi_cgpr1 - name: 'CGPR1 Motion' - mac_address: '12:34:56:12:34:56' - bindkey: '48403ebe2d385db8d0c187f81e62cb64' + name: CGPR1 Motion + mac_address: "12:34:56:12:34:56" + bindkey: 48403ebe2d385db8d0c187f81e62cb64 battery_level: - name: 'CGPR1 battery Level' + name: CGPR1 battery Level idle_time: - name: 'CGPR1 Idle Time' + name: CGPR1 Idle Time illuminance: - name: 'CGPR1 Illuminance' + name: CGPR1 Illuminance - platform: xiaomi_rtcgq02lm id: motion_rtcgq02lm motion: - name: 'Mi Motion Sensor 2' + name: Mi Motion Sensor 2 light: - name: 'Mi Motion Sensor 2 Light' + name: Mi Motion Sensor 2 Light button: - name: 'Mi Motion Sensor 2 Button' + name: Mi Motion Sensor 2 Button esp32_ble_tracker: on_ble_advertise: - 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: @@ -494,16 +499,12 @@ xiaomi_rtcgq02lm: mac_address: 01:02:03:04:05:06 bindkey: '48403ebe2d385db8d0c187f81e62cb64' -#esp32_ble_beacon: -# type: iBeacon -# uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' - status_led: pin: GPIO2 text_sensor: - platform: version - name: 'ESPHome Version' + name: ESPHome Version icon: mdi:icon id: version_sensor on_value: @@ -511,8 +512,10 @@ text_sensor: condition: - api.connected: then: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str()); + # yamllint enable rule:line-length - script.execute: my_script - homeassistant.service: service: notify.html5 @@ -537,19 +540,19 @@ text_sensor: - deep_sleep.enter: sleep_duration: !lambda "return 30 * 60 * 1000;" - platform: template - name: 'Template Text Sensor' + name: Template Text Sensor lambda: |- return {"Hello World"}; filters: - to_upper: - to_lower: - - append: "xyz" - - prepend: "abcd" + - append: xyz + - prepend: abcd - substitute: - Hello -> Goodbye - map: - red -> green - - lambda: return {"1234"}; + - lambda: 'return {"1234"};' - platform: homeassistant entity_id: sensor.hello_world2 id: ha_hello_world2 @@ -587,7 +590,7 @@ stepper: pin_b: GPIO27 pin_c: GPIO25 pin_d: GPIO26 - sleep_when_done: no + sleep_when_done: false step_mode: HALF_STEP max_speed: 250 steps/s @@ -598,7 +601,7 @@ stepper: interval: interval: 5s then: - - logger.log: 'Interval Run' + - logger.log: Interval Run display: @@ -611,7 +614,7 @@ cap1188: switch: - platform: template - name: "Test BLE Write Action" + name: Test BLE Write Action turn_on_action: - ble_client.ble_write: id: airthings01 diff --git a/tests/test3.yaml b/tests/test3.yaml index 4b2224dc3c..de0fc0dfdb 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $device_name comment: $device_comment @@ -9,38 +10,38 @@ esphome: - wifi.connected - time.has_time then: - - logger.log: "Have time" + - logger.log: Have time includes: - custom.h esp8266: board: d1_mini - early_pin_init: True + early_pin_init: true substitutions: device_name: test3 device_comment: test3 device - min_sub: '0.03' - max_sub: '12.0%' + min_sub: "0.03" + max_sub: "12.0%" api: port: 8000 - password: 'pwd' + password: pwd reboot_timeout: 0min encryption: - key: 'bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=' + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= services: - service: hello_world variables: name: string then: - logger.log: - format: 'Hello World %s!' + format: Hello World %s! args: - name.c_str() - service: empty_service then: - - logger.log: 'Service Called' + - logger.log: Service Called - service: all_types variables: bool_: bool @@ -48,7 +49,7 @@ api: float_: float string_: string then: - - logger.log: 'Something happened' + - logger.log: Something happened - stepper.set_target: id: my_stepper2 target: !lambda 'return int_;' @@ -60,7 +61,9 @@ api: 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() @@ -104,7 +107,7 @@ api: then: - dfplayer.play_folder: folder: !lambda 'return folder;' - loop: True + loop: true - service: dfplayer_set_device variables: @@ -122,7 +125,9 @@ api: variables: preset: int then: + # yamllint disable rule:line-length - dfplayer.set_eq: !lambda 'return static_cast(preset);' + # yamllint enable rule:line-length - service: dfplayer_sleep then: @@ -220,7 +225,7 @@ wifi: i2c: sda: 4 scl: 5 - scan: False + scan: false spi: clk_pin: GPIO12 @@ -231,7 +236,7 @@ uart: - id: uart1 tx_pin: number: GPIO1 - inverted: yes + inverted: true rx_pin: GPIO3 baud_rate: 115200 - id: uart2 @@ -279,7 +284,7 @@ modbus: uart_id: uart1 ota: - safe_mode: True + safe_mode: true port: 3286 reboot_timeout: 15min @@ -302,40 +307,40 @@ adalight: sensor: - platform: daly_bms voltage: - name: "Battery Voltage" + name: Battery Voltage current: - name: "Battery Current" + name: Battery Current battery_level: - name: "Battery Level" + name: Battery Level max_cell_voltage: - name: "Max Cell Voltage" + name: Max Cell Voltage max_cell_voltage_number: - name: "Max Cell Voltage Number" + name: Max Cell Voltage Number min_cell_voltage: - name: "Min Cell Voltage" + name: Min Cell Voltage min_cell_voltage_number: - name: "Min Cell Voltage Number" + name: Min Cell Voltage Number max_temperature: - name: "Max Temperature" + name: Max Temperature max_temperature_probe_number: - name: "Max Temperature Probe Number" + name: Max Temperature Probe Number min_temperature: - name: "Min Temperature" + name: Min Temperature min_temperature_probe_number: - name: "Min Temperature Probe Number" + name: Min Temperature Probe Number remaining_capacity: - name: "Remaining Capacity" + name: Remaining Capacity cells_number: - name: "Cells Number" + name: Cells Number temperature_1: - name: "Temperature 1" + name: Temperature 1 temperature_2: - name: "Temperature 2" + name: Temperature 2 - platform: apds9960 type: proximity name: APDS9960 Proximity - platform: vl53l0x - name: 'VL53L0x Distance' + name: VL53L0x Distance address: 0x29 update_interval: 60s enable_pin: GPIO13 @@ -357,32 +362,32 @@ sensor: id: ha_hello_world - platform: aht10 temperature: - name: 'Temperature' + name: Temperature humidity: - name: 'Humidity' + name: Humidity - platform: am2320 temperature: - name: 'Temperature' + name: Temperature humidity: - name: 'Humidity' + name: Humidity - platform: hydreon_rgxx - model: "RG 9" + model: RG 9 uart_id: uart6 - id: "hydreon_rg9" + id: hydreon_rg9 moisture: - name: "hydreon_rain" + name: hydreon_rain id: hydreon_rain - platform: hydreon_rgxx - model: "RG_15" + model: RG_15 uart_id: uart6 acc: - name: "hydreon_acc" + name: hydreon_acc event_acc: - name: "hydreon_event_acc" + name: hydreon_event_acc total_acc: - name: "hydreon_total_acc" + name: hydreon_total_acc r_int: - name: "hydreon_r_int" + name: hydreon_r_int - platform: adc pin: VCC id: my_sensor @@ -496,281 +501,281 @@ sensor: - platform: bl0939 uart_id: uart8 voltage: - name: 'BL0939 Voltage' + name: BL0939 Voltage current_1: - name: 'BL0939 Current 1' + name: BL0939 Current 1 current_2: - name: 'BL0939 Current 2' + name: BL0939 Current 2 active_power_1: - name: 'BL0939 Active Power 1' + name: BL0939 Active Power 1 active_power_2: - name: 'BL0939 Active Power 2' + name: BL0939 Active Power 2 energy_1: - name: 'BL0939 Energy 1' + name: BL0939 Energy 1 energy_2: - name: 'BL0939 Energy 2' + name: BL0939 Energy 2 energy_total: - name: 'BL0939 Total energy' + name: BL0939 Total energy - platform: bl0940 uart_id: uart3 voltage: - name: 'BL0940 Voltage' + name: BL0940 Voltage current: - name: 'BL0940 Current' + name: BL0940 Current power: - name: 'BL0940 Power' + name: BL0940 Power energy: - name: 'BL0940 Energy' + name: BL0940 Energy internal_temperature: - name: 'BL0940 Internal temperature' + name: BL0940 Internal temperature external_temperature: - name: 'BL0940 External temperature' + name: BL0940 External temperature - platform: pzem004t uart_id: uart3 voltage: - name: 'PZEM004T Voltage' + name: PZEM004T Voltage current: - name: 'PZEM004T Current' + name: PZEM004T Current power: - name: 'PZEM004T Power' + name: PZEM004T Power - platform: pzemac id: pzemac1 voltage: - name: 'PZEMAC Voltage' + name: PZEMAC Voltage current: - name: 'PZEMAC Current' + name: PZEMAC Current power: - name: 'PZEMAC Power' + name: PZEMAC Power energy: - name: 'PZEMAC Energy' + name: PZEMAC Energy frequency: - name: 'PZEMAC Frequency' + name: PZEMAC Frequency power_factor: - name: 'PZEMAC Power Factor' + name: PZEMAC Power Factor - platform: pzemdc voltage: - name: 'PZEMDC Voltage' + name: PZEMDC Voltage current: - name: 'PZEMDC Current' + name: PZEMDC Current power: - name: 'PZEMDC Power' + name: PZEMDC Power - platform: tmp102 - name: 'TMP102 Temperature' + name: TMP102 Temperature - platform: hm3301 pm_1_0: - name: 'PM1.0' + name: PM1.0 pm_2_5: - name: 'PM2.5' + name: PM2.5 pm_10_0: - name: 'PM10.0' + name: PM10.0 aqi: - name: 'AQI' - calculation_type: 'AQI' + name: AQI + calculation_type: AQI - platform: pmsx003 uart_id: uart9 type: PMSX003 pm_1_0: - name: 'PM 1.0 Concentration' + name: PM 1.0 Concentration pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration pm_10_0: - name: 'PM 10.0 Concentration' + name: PM 10.0 Concentration pm_1_0_std: - name: 'PM 1.0 Standard Atmospher Concentration' + name: PM 1.0 Standard Atmospher Concentration pm_2_5_std: - name: 'PM 2.5 Standard Atmospher Concentration' + name: PM 2.5 Standard Atmospher Concentration pm_10_0_std: - name: 'PM 10.0 Standard Atmospher Concentration' + name: PM 10.0 Standard Atmospher Concentration pm_0_3um: - name: 'Particulate Count >0.3um' + name: Particulate Count >0.3um pm_0_5um: - name: 'Particulate Count >0.5um' + name: Particulate Count >0.5um pm_1_0um: - name: 'Particulate Count >1.0um' + name: Particulate Count >1.0um pm_2_5um: - name: 'Particulate Count >2.5um' + name: Particulate Count >2.5um pm_5_0um: - name: 'Particulate Count >5.0um' + name: Particulate Count >5.0um pm_10_0um: - name: 'Particulate Count >10.0um' + name: Particulate Count >10.0um update_interval: 30s - platform: pmsx003 uart_id: uart5 type: PMS5003T pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration temperature: - name: 'PMS Temperature' + name: PMS Temperature humidity: - name: 'PMS Humidity' + name: PMS Humidity - platform: pmsx003 uart_id: uart6 type: PMS5003ST pm_1_0: - name: 'PM 1.0 Concentration' + name: PM 1.0 Concentration pm_2_5: - name: 'PM 2.5 Concentration' + name: PM 2.5 Concentration pm_10_0: - name: 'PM 10.0 Concentration' + name: PM 10.0 Concentration pm_1_0_std: - name: 'PM 1.0 Standard Atmospher Concentration' + name: PM 1.0 Standard Atmospher Concentration pm_2_5_std: - name: 'PM 2.5 Standard Atmospher Concentration' + name: PM 2.5 Standard Atmospher Concentration pm_10_0_std: - name: 'PM 10.0 Standard Atmospher Concentration' + name: PM 10.0 Standard Atmospher Concentration pm_0_3um: - name: 'Particulate Count >0.3um' + name: Particulate Count >0.3um pm_0_5um: - name: 'Particulate Count >0.5um' + name: Particulate Count >0.5um pm_1_0um: - name: 'Particulate Count >1.0um' + name: Particulate Count >1.0um pm_2_5um: - name: 'Particulate Count >2.5um' + name: Particulate Count >2.5um pm_5_0um: - name: 'Particulate Count >5.0um' + name: Particulate Count >5.0um pm_10_0um: - name: 'Particulate Count >10.0um' + name: Particulate Count >10.0um temperature: - name: 'PMS Temperature' + name: PMS Temperature humidity: - name: 'PMS Humidity' + name: PMS Humidity formaldehyde: - name: 'PMS Formaldehyde Concentration' + name: PMS Formaldehyde Concentration - platform: cse7761 uart_id: uart7 voltage: - name: 'CSE7761 Voltage' + name: CSE7761 Voltage current_1: - name: 'CSE7761 Current 1' + name: CSE7761 Current 1 current_2: - name: 'CSE7761 Current 2' + name: CSE7761 Current 2 active_power_1: - name: 'CSE7761 Active Power 1' + name: CSE7761 Active Power 1 active_power_2: - name: 'CSE7761 Active Power 2' + name: CSE7761 Active Power 2 - platform: cse7766 uart_id: uart3 voltage: - name: 'CSE7766 Voltage' + name: CSE7766 Voltage current: - name: 'CSE7766 Current' + name: CSE7766 Current power: - name: 'CSE776 Power' + name: CSE776 Power - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: 'pH' + unit_of_measurement: pH - platform: tof10120 - name: "Distance sensor" + name: Distance sensor update_interval: 5s - platform: fingerprint_grow fingerprint_count: - name: "Fingerprint Count" + name: Fingerprint Count status: - name: "Fingerprint Status" + name: Fingerprint Status capacity: - name: "Fingerprint Capacity" + name: Fingerprint Capacity security_level: - name: "Fingerprint Security Level" + name: Fingerprint Security Level last_finger_id: - name: "Fingerprint Last Finger ID" + name: Fingerprint Last Finger ID last_confidence: - name: "Fingerprint Last Confidence" + name: Fingerprint Last Confidence - platform: sdm_meter phase_a: current: - name: 'Phase A Current' + name: Phase A Current voltage: - name: 'Phase A Voltage' + name: Phase A Voltage active_power: - name: 'Phase A Power' + name: Phase A Power power_factor: - name: 'Phase A Power Factor' + name: Phase A Power Factor apparent_power: - name: 'Phase A Apparent Power' + name: Phase A Apparent Power reactive_power: - name: 'Phase A Reactive Power' + name: Phase A Reactive Power phase_angle: - name: 'Phase A Phase Angle' + name: Phase A Phase Angle phase_b: current: - name: 'Phase B Current' + name: Phase B Current voltage: - name: 'Phase B Voltage' + name: Phase B Voltage active_power: - name: 'Phase B Power' + name: Phase B Power power_factor: - name: 'Phase B Power Factor' + name: Phase B Power Factor apparent_power: - name: 'Phase B Apparent Power' + name: Phase B Apparent Power reactive_power: - name: 'Phase B Reactive Power' + name: Phase B Reactive Power phase_angle: - name: 'Phase B Phase Angle' + name: Phase B Phase Angle phase_c: current: - name: 'Phase C Current' + name: Phase C Current voltage: - name: 'Phase C Voltage' + name: Phase C Voltage active_power: - name: 'Phase C Power' + name: Phase C Power power_factor: - name: 'Phase C Power Factor' + name: Phase C Power Factor apparent_power: - name: 'Phase C Apparent Power' + name: Phase C Apparent Power reactive_power: - name: 'Phase C Reactive Power' + name: Phase C Reactive Power phase_angle: - name: 'Phase C Phase Angle' + name: Phase C Phase Angle frequency: - name: 'Frequency' + name: Frequency import_active_energy: - name: 'Import Active Energy' + name: Import Active Energy export_active_energy: - name: 'Export Active Energy' + name: Export Active Energy import_reactive_energy: - name: 'Import Reactive Energy' + name: Import Reactive Energy export_reactive_energy: - name: 'Export Reactive Energy' + name: Export Reactive Energy - platform: dsmr energy_delivered_tariff1: name: dsmr_energy_delivered_tariff1 - platform: nextion id: testnumber - name: 'testnumber' + name: testnumber variable_name: testnumber - platform: nextion id: testwave - name: 'testwave' + name: testwave component_id: 2 wave_channel_id: 1 - platform: mlx90393 oversampling: 1 filter: 0 - gain: "3X" + gain: 3X x_axis: - name: "mlxxaxis" + name: mlxxaxis y_axis: - name: "mlxyaxis" + name: mlxyaxis z_axis: - name: "mlxzaxis" + name: mlxzaxis resolution: 17BIT temperature: - name: "mlxtemp" + name: mlxtemp oversampling: 2 - platform: smt100 uart_id: uart10 counts: - name: "Counts" + name: Counts dielectric_constant: - name: "Dielectric Constant" + name: Dielectric Constant temperature: - name: "Temperature" + name: Temperature moisture: - name: "Moisture" + name: Moisture voltage: - name: "Voltage" + name: Voltage update_interval: 60s time: - platform: homeassistant @@ -786,9 +791,9 @@ mpr121: binary_sensor: - platform: daly_bms charging_mos_enabled: - name: "Charging MOS" + name: Charging MOS discharging_mos_enabled: - name: "Discharging MOS" + name: Discharging MOS - platform: apds9960 direction: up name: APDS9960 Up @@ -816,18 +821,18 @@ binary_sensor: - platform: mpr121 id: touchkey0 channel: 0 - name: 'touchkey0' + name: touchkey0 - platform: mpr121 channel: 1 - name: 'touchkey1' + name: touchkey1 id: bin1 - platform: mpr121 channel: 2 - name: 'touchkey2' + name: touchkey2 id: bin2 - platform: mpr121 channel: 3 - name: 'touchkey3' + name: touchkey3 id: bin3 on_press: then: @@ -839,7 +844,7 @@ binary_sensor: channel: 1 name: TTP229 BSF Test - platform: fingerprint_grow - name: "Fingerprint Enrolling" + name: Fingerprint Enrolling - platform: custom lambda: |- auto s = new CustomBinarySensor(); @@ -851,27 +856,27 @@ binary_sensor: - platform: nextion page_id: 0 component_id: 2 - name: 'Nextion Component 2 Touch' + name: Nextion Component 2 Touch - platform: nextion id: r0_sensor - name: 'R0 Sensor' + name: R0 Sensor component_name: page0.r0 - platform: template - id: 'cover_toggle' + id: cover_toggle on_press: then: - cover.toggle: time_based_cover - cover.toggle: endstop_cover - platform: hydreon_rgxx - hydreon_rgxx_id: "hydreon_rg9" + hydreon_rgxx_id: hydreon_rg9 too_cold: - name: "rg9_toocold" + name: rg9_toocold em_sat: - name: "rg9_emsat" + name: rg9_emsat lens_bad: - name: "rg9_lens_bad" + name: rg9_lens_bad - platform: template - id: 'pzemac_reset_energy' + id: pzemac_reset_energy on_press: then: - pzemac.reset_energy: pzemac1 @@ -891,14 +896,16 @@ status_led: text_sensor: - platform: daly_bms status: - name: "BMS Status" + name: BMS Status - platform: version - name: 'ESPHome Version' + name: ESPHome Version icon: mdi:icon id: version_sensor on_value: + # yamllint disable rule:line-length - lambda: !lambda |- ESP_LOGD("main", "The state is %s=%s", x.c_str(), id(version_sensor).state.c_str()); + # yamllint enable rule:line-length - script.execute: my_script - script.wait: my_script - script.stop: my_script @@ -912,7 +919,7 @@ text_sensor: my_variable: |- return id(version_sensor).state; - platform: template - name: 'Template Text Sensor' + name: Template Text Sensor lambda: |- return {"Hello World"}; - platform: homeassistant @@ -933,9 +940,9 @@ text_sensor: component_name: text0 - platform: dsmr identification: - name: "dsmr_identification" + name: dsmr_identification p1_version: - name: "dsmr_p1_version" + name: dsmr_p1_version script: - id: my_script @@ -948,9 +955,9 @@ sm2135: switch: - platform: template - name: 'mpr121_toggle' + name: mpr121_toggle id: mpr121_toggle - optimistic: True + optimistic: true - platform: gpio id: gpio_switch1 pin: @@ -978,7 +985,7 @@ switch: name: Custom Switch - platform: nextion id: r0 - name: 'R0 Switch' + name: R0 Switch component_name: page0.r0 custom_component: @@ -994,7 +1001,7 @@ stepper: pin_b: GPIO13 pin_c: GPIO14 pin_d: GPIO15 - sleep_when_done: no + sleep_when_done: false step_mode: HALF_STEP max_speed: 250 steps/s acceleration: inf @@ -1010,7 +1017,7 @@ stepper: interval: interval: 5s then: - - logger.log: 'Interval Run' + - logger.log: Interval Run - stepper.set_target: id: my_stepper2 target: 500 @@ -1123,7 +1130,7 @@ climate: default_target_temperature_high: 20°C - platform: pid id: pid_climate - name: 'PID Climate Controller' + name: PID Climate Controller sensor: ha_hello_world default_target_temperature: 21°C heat_output: my_slow_pwm @@ -1134,42 +1141,42 @@ climate: sprinkler: - id: yard_sprinkler_ctrlr - main_switch: "Yard Sprinklers" - auto_advance_switch: "Yard Sprinklers Auto Advance" - reverse_switch: "Yard Sprinklers Reverse" + 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" + - valve_switch: Yard Valve 0 + enable_switch: Enable Yard Valve 0 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Yard Valve 1" - enable_switch: "Enable Yard Valve 1" + - valve_switch: Yard Valve 1 + enable_switch: Enable Yard Valve 1 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Yard Valve 2" - enable_switch: "Enable Yard Valve 2" + - valve_switch: Yard Valve 2 + enable_switch: Enable Yard Valve 2 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - id: garden_sprinkler_ctrlr - main_switch: "Garden Sprinklers" - auto_advance_switch: "Garden Sprinklers Auto Advance" - reverse_switch: "Garden Sprinklers Reverse" + 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" + - valve_switch: Garden Valve 0 + enable_switch: Enable Garden Valve 0 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 - - valve_switch: "Garden Valve 1" - enable_switch: "Enable Garden Valve 1" + - valve_switch: Garden Valve 1 + enable_switch: Enable Garden Valve 1 pump_switch_id: gpio_switch1 run_duration: 10s valve_switch_id: gpio_switch2 @@ -1219,7 +1226,7 @@ cover: - switch.turn_on: gpio_switch2 close_duration: 4.5min - platform: current_based - name: "Current Based Cover" + name: Current Based Cover open_sensor: ade7953_current_a open_moving_current_threshold: 0.5 open_obstacle_current_threshold: 0.8 @@ -1240,7 +1247,7 @@ cover: malfunction_detection: true malfunction_action: then: - - logger.log: "Malfunction Detected" + - logger.log: Malfunction Detected - platform: template name: Template Cover with Tilt tilt_lambda: 'return 0.5;' @@ -1326,7 +1333,7 @@ light: pin_b: out2 - platform: sonoff_d1 uart_id: uart2 - use_rm433_remote: False + use_rm433_remote: false name: Sonoff D1 Dimmer id: d1_light restore_mode: RESTORE_DEFAULT_OFF @@ -1352,7 +1359,7 @@ sim800l: str = sender; str = message; - sim800l.send_sms: - message: 'hello you' + message: hello you recipient: '+1234' - sim800l.dial: recipient: '+1234' @@ -1365,7 +1372,7 @@ dfplayer: condition: not: dfplayer.is_playing then: - logger.log: 'Playback finished event' + logger.log: Playback finished event tm1651: id: tm1651_battery clk_pin: D6 @@ -1394,8 +1401,8 @@ rf_bridge: test = data.length; test = data.protocol; test_code = data.code; - - rf_bridge.start_advanced_sniffing - - rf_bridge.stop_advanced_sniffing + - rf_bridge.start_advanced_sniffing: + - rf_bridge.stop_advanced_sniffing: - rf_bridge.send_advanced_code: length: 0x04 protocol: 0x01 @@ -1413,13 +1420,13 @@ rf_bridge: json: key: !lambda |- return id(version_sensor).state; - greeting: 'Hello World' + greeting: Hello World - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: 'Some data' + body: Some data verify_ssl: false display: @@ -1428,13 +1435,13 @@ display: num_chips: 4 rotate_chip: 0 intensity: 10 - scroll_mode: 'STOP' + scroll_mode: STOP id: my_matrix lambda: |- it.printdigit("hello"); - platform: nextion uart_id: uart1 - tft_url: 'http://esphome.io/default35.tft' + tft_url: http://esphome.io/default35.tft update_interval: 5s on_sleep: then: diff --git a/tests/test4.yaml b/tests/test4.yaml index c3079037c1..d6857de7cd 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -1,3 +1,4 @@ +--- esphome: name: $devicename platform: ESP32 @@ -25,7 +26,7 @@ api: i2c: sda: 21 scl: 22 - scan: False + scan: false spi: clk_pin: GPIO21 @@ -38,7 +39,7 @@ uart: baud_rate: 115200 ota: - safe_mode: True + safe_mode: true port: 3286 logger: @@ -71,7 +72,7 @@ select: 2: Both pipsolar: - id: inverter0 + id: inverter0 sx1509: - id: sx1509_hub @@ -81,9 +82,9 @@ mcp3204: cs_pin: GPIO23 dac7678: - address: 0x4A - id: dac7678_hub1 - internal_reference: true + address: 0x4A + id: dac7678_hub1 + internal_reference: true sensor: - platform: homeassistant @@ -226,22 +227,22 @@ sensor: pv_charging_power: id: inverter0_pv_charging_power name: inverter0_pv_charging_power - - platform: "hrxl_maxsonar_wr" - name: "Rainwater Tank Level" + - platform: hrxl_maxsonar_wr + name: Rainwater Tank Level filters: - sliding_window_moving_average: window_size: 12 send_every: 12 - or: - - throttle: "20min" - - delta: 0.02 + - throttle: 20min + - delta: 0.02 - platform: mcp3204 - name: "MCP3204 Pin 1" + name: MCP3204 Pin 1 number: 1 id: mcp_sensor - platform: copy source_id: mcp_sensor - name: "MCP binary sensor copy" + name: MCP binary sensor copy # # platform sensor.apds9960 requires component apds9960 @@ -321,15 +322,15 @@ binary_sensor: name: inverter0_backlight_on - platform: template id: ar1 - lambda: 'return {};' + lambda: "return {};" filters: - autorepeat: - - delay: 2s - time_off: 100ms - time_on: 900ms - - delay: 4s - time_off: 100ms - time_on: 400ms + - delay: 2s + time_off: 100ms + time_on: 900ms + - delay: 4s + time_off: 100ms + time_on: 400ms on_state: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' @@ -356,8 +357,7 @@ binary_sensor: y_min: 0 y_max: 100 on_press: - - logger.log: "Touched" - + - logger.log: Touched climate: - platform: tuya @@ -392,7 +392,7 @@ switch: light: - platform: fastled_clockless id: led_matrix_32x8 - name: "led_matrix_32x8" + name: led_matrix_32x8 chipset: WS2812B pin: GPIO15 num_leds: 256 @@ -417,7 +417,7 @@ cover: position_datapoint: 2 - platform: copy source_id: tuya_cover - name: "Tuya Cover copy" + name: Tuya Cover copy display: - platform: addressable_light @@ -516,7 +516,7 @@ text_sensor: name: inverter0_last_qflag - platform: copy source_id: inverter0_device_mode - name: "Inverter Text Sensor Copy" + name: Inverter Text Sensor Copy output: - platform: pipsolar @@ -524,37 +524,37 @@ output: battery_recharge_voltage: id: inverter0_battery_recharge_voltage_out - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 0 - id: 'dac7678_1_ch0' + id: dac7678_1_ch0 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 1 - id: 'dac7678_1_ch1' + id: dac7678_1_ch1 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 2 - id: 'dac7678_1_ch2' + id: dac7678_1_ch2 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 3 - id: 'dac7678_1_ch3' + id: dac7678_1_ch3 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 4 - id: 'dac7678_1_ch4' + id: dac7678_1_ch4 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 5 - id: 'dac7678_1_ch5' + id: dac7678_1_ch5 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 6 - id: 'dac7678_1_ch6' + id: dac7678_1_ch6 - platform: dac7678 - dac7678_id: 'dac7678_hub1' + dac7678_id: dac7678_hub1 channel: 7 - id: 'dac7678_1_ch7' + id: dac7678_1_ch7 esp32_camera: name: ESP-32 Camera data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] @@ -581,9 +581,9 @@ esp32_camera_web_server: external_components: - source: github://esphome/esphome@dev refresh: 1d - components: ["bh1750"] + components: [bh1750] - source: ../esphome/components - components: ["sntp"] + components: [sntp] xpt2046: id: xpt_touchscreen cs_pin: 17 @@ -597,8 +597,9 @@ xpt2046: calibration_x_max: 280 calibration_y_min: 340 calibration_y_max: 3860 - swap_x_y: False + swap_x_y: false on_state: + # yamllint disable rule:line-length - lambda: |- ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", @@ -609,6 +610,7 @@ xpt2046: id(xpt_touchscreen).y_raw, id(xpt_touchscreen).z_raw ); + # yamllint enable rule:line-length button: - platform: restart @@ -622,7 +624,6 @@ button: source_id: shutdown_btn name: Shutdown Button Copy - touchscreen: - platform: ektf2232 interrupt_pin: GPIO36 @@ -631,7 +632,7 @@ touchscreen: on_touch: - logger.log: format: Touch at (%d, %d) - args: ["touch.x", "touch.y"] + args: [touch.x, touch.y] - platform: lilygo_t5_47 id: lilygo_touchscreen @@ -640,7 +641,7 @@ touchscreen: on_touch: - logger.log: format: Touch at (%d, %d) - args: ["touch.x", "touch.y"] + args: [touch.x, touch.y] media_player: - platform: i2s_audio @@ -673,4 +674,4 @@ prometheus: relabel: ha_hello_world: id: hellow_world - name: "Hello World" + name: Hello World diff --git a/tests/test5.yaml b/tests/test5.yaml index c193009ead..3b6862a4d4 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -1,3 +1,4 @@ +--- esphome: name: test5 build_path: build/test5 @@ -60,8 +61,10 @@ mqtt: 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()); + ESP_LOGD("Mqtt Test", "testing/sensor/testing_sensor/state=[%s]", x.c_str()); + # yamllint enable rule:line-length binary_sensor: - platform: gpio @@ -74,8 +77,8 @@ binary_sensor: id: modbus_binsensortest register_type: read address: 0x3200 - bitmask: 0x80 #(bit 8) - lambda: !lambda "{ return x ;}" + bitmask: 0x80 # (bit 8) + lambda: "return x;" tlc5947: data_pin: GPIO12 @@ -108,8 +111,8 @@ demo: esp32_ble: esp32_ble_server: - manufacturer: "ESPHome" - model: "Test5" + manufacturer: ESPHome + model: Test5 esp32_improv: authorizer: io0_button @@ -128,12 +131,12 @@ number: mode: slider on_value: - logger.log: - format: "Number changed to %f" - args: ["x"] + format: Number changed to %f + args: [x] set_action: - logger.log: - format: "Template Number set to %f" - args: ["x"] + format: Template Number set to %f + args: [x] - number.set: id: template_number_id value: 50 @@ -163,10 +166,10 @@ number: - id: modbus_numbertest platform: modbus_controller modbus_controller_id: modbus_controller_test - name: "ModbusNumber" + name: ModbusNumber address: 0x9002 value_type: U_WORD - lambda: "return x * 1.0; " + lambda: "return x * 1.0;" write_lambda: |- return x * 1.0 ; multiply: 1.0 @@ -180,11 +183,11 @@ select: restore_value: true on_value: - logger.log: - format: "Select changed to %s (index %d)" + format: Select changed to %s (index %d)" args: ["x.c_str()", "i"] set_action: - logger.log: - format: "Template Select set to %s" + format: Template Select set to %s args: ["x.c_str()"] - select.set: id: template_select_id @@ -216,7 +219,7 @@ select: - three - platform: modbus_controller - name: "Modbus Select Register 1000" + name: Modbus Select Register 1000 address: 1000 value_type: U_WORD optionsmap: @@ -228,41 +231,41 @@ select: sensor: - platform: selec_meter total_active_energy: - name: "SelecEM2M Total Active Energy" + name: SelecEM2M Total Active Energy import_active_energy: - name: "SelecEM2M Import Active Energy" + name: SelecEM2M Import Active Energy export_active_energy: - name: "SelecEM2M Export Active Energy" + name: SelecEM2M Export Active Energy total_reactive_energy: - name: "SelecEM2M Total Reactive Energy" + name: SelecEM2M Total Reactive Energy import_reactive_energy: - name: "SelecEM2M Import Reactive Energy" + name: SelecEM2M Import Reactive Energy export_reactive_energy: - name: "SelecEM2M Export Reactive Energy" + name: SelecEM2M Export Reactive Energy apparent_energy: - name: "SelecEM2M Apparent Energy" + name: SelecEM2M Apparent Energy active_power: - name: "SelecEM2M Active Power" + name: SelecEM2M Active Power reactive_power: - name: "SelecEM2M Reactive Power" + name: SelecEM2M Reactive Power apparent_power: - name: "SelecEM2M Apparent Power" + name: SelecEM2M Apparent Power voltage: - name: "SelecEM2M Voltage" + name: SelecEM2M Voltage current: - name: "SelecEM2M Current" + name: SelecEM2M Current power_factor: - name: "SelecEM2M Power Factor" + name: SelecEM2M Power Factor frequency: - name: "SelecEM2M Frequency" + name: SelecEM2M Frequency maximum_demand_active_power: - name: "SelecEM2M Maximum Demand Active Power" + name: SelecEM2M Maximum Demand Active Power disabled_by_default: true maximum_demand_reactive_power: - name: "SelecEM2M Maximum Demand Reactive Power" + name: SelecEM2M Maximum Demand Reactive Power disabled_by_default: true maximum_demand_apparent_power: - name: "SelecEM2M Maximum Demand Apparent Power" + name: SelecEM2M Maximum Demand Apparent Power disabled_by_default: true - id: modbus_sensortest @@ -279,41 +282,41 @@ sensor: - platform: bmp3xx temperature: - name: "BMP Temperature" + name: BMP Temperature oversampling: 16x pressure: - name: "BMP Pressure" + name: BMP Pressure address: 0x77 iir_filter: 2X - platform: sen5x id: sen54 temperature: - name: "Temperature" + name: Temperature accuracy_decimals: 1 humidity: - name: "Humidity" + name: Humidity accuracy_decimals: 0 pm_1_0: - name: " PM <1µm Weight concentration" + name: PM <1µm Weight concentration id: pm_1_0 accuracy_decimals: 1 pm_2_5: - name: " PM <2.5µm Weight concentration" + name: PM <2.5µm Weight concentration id: pm_2_5 accuracy_decimals: 1 pm_4_0: - name: " PM <4µm Weight concentration" + name: PM <4µm Weight concentration id: pm_4_0 accuracy_decimals: 1 pm_10_0: - name: " PM <10µm Weight concentration" + name: PM <10µm Weight concentration id: pm_10_0 accuracy_decimals: 1 nox: - name: "NOx" + name: NOx voc: - name: "VOC" + name: VOC algorithm_tuning: index_offset: 100 learning_time_offset_hours: 12 @@ -332,9 +335,9 @@ sensor: - platform: mcp9600 thermocouple_type: K hot_junction: - name: "Thermocouple Temperature" + name: Thermocouple Temperature cold_junction: - name: "Ambient Temperature" + name: Ambient Temperature script: - id: automation_test @@ -342,7 +345,7 @@ script: - repeat: count: 5 then: - - logger.log: "looping!" + - logger.log: looping! switch: - platform: modbus_controller diff --git a/tests/test_packages/test_packages_package1.yaml b/tests/test_packages/test_packages_package1.yaml index 0495984d42..312bbe574a 100644 --- a/tests/test_packages/test_packages_package1.yaml +++ b/tests/test_packages/test_packages_package1.yaml @@ -1,2 +1,3 @@ +--- sensor: - <<: !include ./test_uptime_sensor.yaml diff --git a/tests/test_packages/test_packages_package_wifi.yaml b/tests/test_packages/test_packages_package_wifi.yaml index 7d5d41ddab..a8c610edfd 100644 --- a/tests/test_packages/test_packages_package_wifi.yaml +++ b/tests/test_packages/test_packages_package_wifi.yaml @@ -1,4 +1,5 @@ +--- wifi: networks: - - ssid: 'WiFiFromPackage' - password: 'password1' + - ssid: "WiFiFromPackage" + password: "password1" diff --git a/tests/test_packages/test_uptime_sensor.yaml b/tests/test_packages/test_uptime_sensor.yaml index 1bf52a6d0b..f15d968fee 100644 --- a/tests/test_packages/test_uptime_sensor.yaml +++ b/tests/test_packages/test_uptime_sensor.yaml @@ -1,3 +1,4 @@ +--- # Uptime sensor. platform: uptime id: ${devicename}_uptime_pcg diff --git a/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml index ddd2156b5e..89879248aa 100644 --- a/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml +++ b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml @@ -1 +1,2 @@ +--- ${var1} diff --git a/tests/unit_tests/fixtures/yaml_util/includetest.yaml b/tests/unit_tests/fixtures/yaml_util/includetest.yaml index 959283df60..af0a4e2030 100644 --- a/tests/unit_tests/fixtures/yaml_util/includetest.yaml +++ b/tests/unit_tests/fixtures/yaml_util/includetest.yaml @@ -8,10 +8,11 @@ wifi: !include name: my_custom_ssid esphome: - # should be substituted as 'original', not overwritten by vars in the !include above + # 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 } } + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} - libraries: !include { file: includes/list.yaml, vars: { var1: Wire } } + libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} From 635851807a660d401f60990520906d426e3bb6bc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:41:23 +1200 Subject: [PATCH 043/838] Fix HA addon auth using HA credentials (#3758) --- esphome/dashboard/dashboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index e7a17cba4c..889f5cc0bf 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -816,15 +816,16 @@ class LoginHandler(BaseHandler): import requests headers = { - "Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}", + "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, data=data, timeout=30 + "http://supervisor/auth", headers=headers, json=data, timeout=30 ) if req.status_code == 200: self.set_secure_cookie("authenticated", cookie_authenticated_yes) From acd0b50b40115d63cc345b3211ad551cee764307 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:41:23 +1200 Subject: [PATCH 044/838] Fix HA addon auth using HA credentials (#3758) --- esphome/dashboard/dashboard.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 1fadac968d..21730033a5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -816,14 +816,17 @@ class LoginHandler(BaseHandler): import requests headers = { - "Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}", + "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, data=data) + 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("/") From 733a84df7515019c20cf660139737c3a4818966f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 6 Sep 2022 16:50:17 +1200 Subject: [PATCH 045/838] Bump version to 2022.8.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7d916e30af..d65aea7159 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.8.2" +__version__ = "2022.8.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From f77118a90cf7e0cad80fd226b59db0a1ca7f7408 Mon Sep 17 00:00:00 2001 From: Philippe FOUQUET Date: Thu, 8 Sep 2022 01:40:27 +0200 Subject: [PATCH 046/838] Add support to tm1621 display (#3737) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/tm1621/__init__.py | 1 + esphome/components/tm1621/display.py | 47 +++++ esphome/components/tm1621/tm1621.cpp | 283 ++++++++++++++++++++++++++ esphome/components/tm1621/tm1621.h | 74 +++++++ esphome/const.py | 2 + tests/test1.yaml | 13 ++ 7 files changed, 421 insertions(+) create mode 100644 esphome/components/tm1621/__init__.py create mode 100644 esphome/components/tm1621/display.py create mode 100644 esphome/components/tm1621/tm1621.cpp create mode 100644 esphome/components/tm1621/tm1621.h diff --git a/CODEOWNERS b/CODEOWNERS index ae112b3330..f8efe79c81 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -225,6 +225,7 @@ esphome/components/teleinfo/* @0hax esphome/components/thermostat/* @kbx81 esphome/components/time/* @OttoWinter esphome/components/tlc5947/* @rnauber +esphome/components/tm1621/* @Philippe12 esphome/components/tm1637/* @glmnet esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath diff --git a/esphome/components/tm1621/__init__.py b/esphome/components/tm1621/__init__.py new file mode 100644 index 0000000000..2e88d4f366 --- /dev/null +++ b/esphome/components/tm1621/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Philippe12"] diff --git a/esphome/components/tm1621/display.py b/esphome/components/tm1621/display.py new file mode 100644 index 0000000000..edbc5f6928 --- /dev/null +++ b/esphome/components/tm1621/display.py @@ -0,0 +1,47 @@ +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_DATA_PIN, + CONF_CS_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_READ_PIN, + CONF_WRITE_PIN, +) + +tm1621_ns = cg.esphome_ns.namespace("tm1621") +TM1621Display = tm1621_ns.class_("TM1621Display", cg.PollingComponent) +TM1621DisplayRef = TM1621Display.operator("ref") + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1621Display), + cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_READ_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_WRITE_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("1s")) + + +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]) + cg.add(var.set_cs_pin(cs)) + data = await cg.gpio_pin_expression(config[CONF_DATA_PIN]) + cg.add(var.set_data_pin(data)) + read = await cg.gpio_pin_expression(config[CONF_READ_PIN]) + cg.add(var.set_read_pin(read)) + write = await cg.gpio_pin_expression(config[CONF_WRITE_PIN]) + cg.add(var.set_write_pin(write)) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(TM1621DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1621/tm1621.cpp b/esphome/components/tm1621/tm1621.cpp new file mode 100644 index 0000000000..ebaa5a3457 --- /dev/null +++ b/esphome/components/tm1621/tm1621.cpp @@ -0,0 +1,283 @@ +#include "tm1621.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1621 { + +static const char *const TAG = "tm1621"; + +const uint8_t TM1621_PULSE_WIDTH = 10; // microseconds (Sonoff = 100) + +const uint8_t TM1621_SYS_EN = 0x01; // 0b00000001 +const uint8_t TM1621_LCD_ON = 0x03; // 0b00000011 +const uint8_t TM1621_TIMER_DIS = 0x04; // 0b00000100 +const uint8_t TM1621_WDT_DIS = 0x05; // 0b00000101 +const uint8_t TM1621_TONE_OFF = 0x08; // 0b00001000 +const uint8_t TM1621_BIAS = 0x29; // 0b00101001 = LCD 1/3 bias 4 commons option +const uint8_t TM1621_IRQ_DIS = 0x80; // 0b100x0xxx + +enum Tm1621Device { TM1621_USER, TM1621_POWR316D, TM1621_THR316D }; + +const uint8_t TM1621_COMMANDS[] = {TM1621_SYS_EN, TM1621_LCD_ON, TM1621_BIAS, TM1621_TIMER_DIS, + TM1621_WDT_DIS, TM1621_TONE_OFF, TM1621_IRQ_DIS}; + +const char TM1621_KCHAR[] PROGMEM = {"0|1|2|3|4|5|6|7|8|9|-| "}; +// 0 1 2 3 4 5 6 7 8 9 - off +const uint8_t TM1621_DIGIT_ROW[2][12] = {{0x5F, 0x50, 0x3D, 0x79, 0x72, 0x6B, 0x6F, 0x51, 0x7F, 0x7B, 0x20, 0x00}, + {0xF5, 0x05, 0xB6, 0x97, 0x47, 0xD3, 0xF3, 0x85, 0xF7, 0xD7, 0x02, 0x00}}; + +void TM1621Display::setup() { + ESP_LOGCONFIG(TAG, "Setting up TM1621..."); + + this->cs_pin_->setup(); // OUTPUT + this->cs_pin_->digital_write(true); + this->data_pin_->setup(); // OUTPUT + this->data_pin_->digital_write(true); + this->read_pin_->setup(); // OUTPUT + this->read_pin_->digital_write(true); + this->write_pin_->setup(); // OUTPUT + this->write_pin_->digital_write(true); + + this->state_ = 100; + + this->cs_pin_->digital_write(false); + delayMicroseconds(80); + this->read_pin_->digital_write(false); + delayMicroseconds(15); + this->write_pin_->digital_write(false); + delayMicroseconds(25); + this->data_pin_->digital_write(false); + delayMicroseconds(TM1621_PULSE_WIDTH); + this->data_pin_->digital_write(true); + + for (uint8_t tm1621_command : TM1621_COMMANDS) { + this->send_command_(tm1621_command); + } + + this->send_address_(0x00); + for (uint32_t segment = 0; segment < 16; segment++) { + this->send_common_(0); + } + this->stop_(); + + snprintf(this->row_[0], sizeof(this->row_[0]), "----"); + snprintf(this->row_[1], sizeof(this->row_[1]), "----"); + + this->display(); +} +void TM1621Display::dump_config() { + ESP_LOGCONFIG(TAG, "TM1621:"); + LOG_PIN(" CS Pin: ", this->cs_pin_); + LOG_PIN(" DATA Pin: ", this->data_pin_); + LOG_PIN(" READ Pin: ", this->read_pin_); + LOG_PIN(" WRITE Pin: ", this->write_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void TM1621Display::update() { + // memset(this->row, 0, sizeof(this->row)); + if (this->writer_.has_value()) + (*this->writer_)(*this); + this->display(); +} + +float TM1621Display::get_setup_priority() const { return setup_priority::PROCESSOR; } +void TM1621Display::bit_delay_() { delayMicroseconds(100); } + +void TM1621Display::stop_() { + this->cs_pin_->digital_write(true); // Stop command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + this->data_pin_->digital_write(true); // Reset data +} + +void TM1621Display::display() { + // Tm1621.row[x] = "text", "----", " " or a number with one decimal like "0.4", "237.5", "123456.7" + // "123456.7" will be shown as "9999" being a four digit overflow + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Row1 '%s', Row2 '%s'"), Tm1621.row[0], Tm1621.row[1]); + + uint8_t buffer[8] = {0}; // TM1621 16-segment 4-bit common buffer + char row[4]; + for (uint32_t j = 0; j < 2; j++) { + // 0.4V => " 04", 0.0A => " ", 1234.5V => "1234" + uint32_t len = strlen(this->row_[j]); + char *dp = nullptr; // Expect number larger than "123" + int row_idx = len - 3; // "1234.5" + if (len <= 5) { // "----", " ", "0.4", "237.5" + dp = strchr(this->row_[j], '.'); + row_idx = len - 1; + } else if (len > 6) { // "12345.6" + snprintf(this->row_[j], sizeof(this->row_[j]), "9999"); + row_idx = 3; + } + row[3] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + if ((row_idx >= 0) && dp) { + row_idx--; + } + row[2] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + row[1] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + row[0] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump%d %4_H"), j +1, row); + + char command[10]; + char needle[2] = {0}; + for (uint32_t i = 0; i < 4; i++) { + needle[0] = row[i]; + int index = this->get_command_code_(command, sizeof(command), (const char *) needle, TM1621_KCHAR); + if (-1 == index) { + index = 11; + } + uint32_t bidx = (0 == j) ? i : 7 - i; + buffer[bidx] = TM1621_DIGIT_ROW[j][index]; + } + if (dp) { + if (0 == j) { + buffer[2] |= 0x80; // Row 1 decimal point + } else { + buffer[5] |= 0x08; // Row 2 decimal point + } + } + } + + if (this->fahrenheit_) { + buffer[1] |= 0x80; + } + if (this->celsius_) { + buffer[3] |= 0x80; + } + if (this->kwh_) { + buffer[4] |= 0x08; + } + if (this->humidity_) { + buffer[6] |= 0x08; + } + if (this->voltage_) { + buffer[7] |= 0x08; + } + + // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump3 %8_H"), buffer); + + this->send_address_(0x10); // Sonoff only uses the upper 16 Segments + for (uint8_t i : buffer) { + this->send_common_(i); + } + this->stop_(); +} + +bool TM1621Display::send_command_(uint16_t command) { + uint16_t full_command = (0x0400 | command) << 5; // 0b100cccccccc00000 + this->cs_pin_->digital_write(false); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 12; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (full_command & 0x8000) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_command <<= 1; + } + this->stop_(); + return true; +} + +bool TM1621Display::send_common_(uint8_t common) { + for (uint32_t i = 0; i < 8; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (common & 1) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + common >>= 1; + } + return true; +} + +bool TM1621Display::send_address_(uint16_t address) { + uint16_t full_address = (address | 0x0140) << 7; // 0b101aaaaaa0000000 + this->cs_pin_->digital_write(false); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 9; i++) { + this->write_pin_->digital_write(false); // Start write sequence + if (full_address & 0x8000) { + this->data_pin_->digital_write(true); // Set data + } else { + this->data_pin_->digital_write(false); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + this->write_pin_->digital_write(true); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_address <<= 1; + } + return true; +} + +uint8_t TM1621Display::print(uint8_t start_pos, const char *str) { + // ESP_LOGD(TAG, "Print at %d: %s", start_pos, str); + return snprintf(this->row_[start_pos], sizeof(this->row_[start_pos]), "%s", str); +} +uint8_t TM1621Display::print(const char *str) { return this->print(0, str); } +uint8_t TM1621Display::printf(uint8_t pos, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1621Display::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(buffer); + return 0; +} + +int TM1621Display::get_command_code_(char *destination, size_t destination_size, const char *needle, + const char *haystack) { + // Returns -1 of not found + // Returns index and command if found + int result = -1; + const char *read = haystack; + char *write = destination; + + while (true) { + result++; + size_t size = destination_size - 1; + write = destination; + char ch = '.'; + while ((ch != '\0') && (ch != '|')) { + ch = *(read++); + if (size && (ch != '|')) { + *write++ = ch; + size--; + } + } + *write = '\0'; + if (!strcasecmp(needle, destination)) { + break; + } + if (0 == ch) { + result = -1; + break; + } + } + return result; +} +} // namespace tm1621 +} // namespace esphome diff --git a/esphome/components/tm1621/tm1621.h b/esphome/components/tm1621/tm1621.h new file mode 100644 index 0000000000..b9f330e96e --- /dev/null +++ b/esphome/components/tm1621/tm1621.h @@ -0,0 +1,74 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1621 { + +class TM1621Display; + +using tm1621_writer_t = std::function; + +class TM1621Display : public PollingComponent { + public: + void set_writer(tm1621_writer_t &&writer) { this->writer_ = writer; } + + void setup() override; + + void dump_config() override; + + void set_cs_pin(GPIOPin *pin) { cs_pin_ = pin; } + void set_data_pin(GPIOPin *pin) { data_pin_ = pin; } + void set_read_pin(GPIOPin *pin) { read_pin_ = pin; } + void set_write_pin(GPIOPin *pin) { write_pin_ = pin; } + + void display_celsius(bool d) { celsius_ = d; } + void display_fahrenheit(bool d) { fahrenheit_ = d; } + void display_humidity(bool d) { humidity_ = d; } + void display_voltage(bool d) { voltage_ = d; } + void display_kwh(bool d) { kwh_ = d; } + + float get_setup_priority() const override; + + void update() override; + + /// Evaluate the printf-format and print the result at the given position. + uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4))); + /// Evaluate the printf-format and print the result at position 0. + uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + + /// Print `str` at the given position. + uint8_t print(uint8_t pos, const char *str); + /// Print `str` at position 0. + uint8_t print(const char *str); + + void display(); + + protected: + void bit_delay_(); + void setup_pins_(); + bool send_command_(uint16_t command); + bool send_common_(uint8_t common); + bool send_address_(uint16_t address); + void stop_(); + int get_command_code_(char *destination, size_t destination_size, const char *needle, const char *haystack); + + GPIOPin *data_pin_; + GPIOPin *cs_pin_; + GPIOPin *read_pin_; + GPIOPin *write_pin_; + optional writer_{}; + char row_[2][12]; + uint8_t state_; + uint8_t device_; + bool celsius_; + bool fahrenheit_; + bool humidity_; + bool voltage_; + bool kwh_; +}; + +} // namespace tm1621 +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 8245ed26f7..adbd20c620 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -558,6 +558,7 @@ CONF_RAW_DATA_ID = "raw_data_id" CONF_RC_CODE_1 = "rc_code_1" CONF_RC_CODE_2 = "rc_code_2" CONF_REACTIVE_POWER = "reactive_power" +CONF_READ_PIN = "read_pin" CONF_REBOOT_TIMEOUT = "reboot_timeout" CONF_RECEIVE_TIMEOUT = "receive_timeout" CONF_RED = "red" @@ -769,6 +770,7 @@ CONF_WILL_MESSAGE = "will_message" CONF_WIND_DIRECTION_DEGREES = "wind_direction_degrees" CONF_WIND_SPEED = "wind_speed" CONF_WINDOW_SIZE = "window_size" +CONF_WRITE_PIN = "write_pin" CONF_X_GRID = "x_grid" CONF_Y_GRID = "y_grid" CONF_ZERO = "zero" diff --git a/tests/test1.yaml b/tests/test1.yaml index 274b25548b..20209923aa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -547,8 +547,10 @@ sensor: - platform: dht pin: GPIO26 temperature: + id: dht_temperature name: Living Room Temperature 3 humidity: + id: dht_humidity name: Living Room Humidity 3 model: AM2302 update_interval: 15s @@ -2514,6 +2516,17 @@ display: it.print_sad(true); it.print_bracket(true); it.print_battery(true); + - platform: tm1621 + id: tm1621_display + cs_pin: GPIO17 + data_pin: GPIO5 + read_pin: GPIO23 + write_pin: GPIO18 + lambda: |- + it.printf(0, "%.1f", id(dht_temperature).state); + it.display_celsius(true); + it.printf(1, "%.1f", id(dht_humidity).state); + it.display_humidity(true); tm1651: id: tm1651_battery From e3f2562047847cd849ccd05674929bf2c2238f6f Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 8 Sep 2022 12:30:07 +0200 Subject: [PATCH 047/838] u-fire EC sensor (#3774) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ufire_ec/__init__.py | 1 + esphome/components/ufire_ec/sensor.py | 126 +++++++++++++++++++++++ esphome/components/ufire_ec/ufire_ec.cpp | 119 +++++++++++++++++++++ esphome/components/ufire_ec/ufire_ec.h | 87 ++++++++++++++++ esphome/const.py | 2 + tests/test2.yaml | 7 ++ tests/test4.yaml | 8 ++ 8 files changed, 351 insertions(+) create mode 100644 esphome/components/ufire_ec/__init__.py create mode 100644 esphome/components/ufire_ec/sensor.py create mode 100644 esphome/components/ufire_ec/ufire_ec.cpp create mode 100644 esphome/components/ufire_ec/ufire_ec.h diff --git a/CODEOWNERS b/CODEOWNERS index f8efe79c81..ff497c35ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -241,6 +241,7 @@ esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core +esphome/components/ufire_ec/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core esphome/components/wake_on_lan/* @willwill2will54 diff --git a/esphome/components/ufire_ec/__init__.py b/esphome/components/ufire_ec/__init__.py new file mode 100644 index 0000000000..08f36c7934 --- /dev/null +++ b/esphome/components/ufire_ec/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@pvizeli"] diff --git a/esphome/components/ufire_ec/sensor.py b/esphome/components/ufire_ec/sensor.py new file mode 100644 index 0000000000..9602d0c2d0 --- /dev/null +++ b/esphome/components/ufire_ec/sensor.py @@ -0,0 +1,126 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_EC, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_MILLISIEMENS_PER_CENTIMETER, +) + +DEPENDENCIES = ["i2c"] + +CONF_SOLUTION = "solution" +CONF_TEMPERATURE_SENSOR = "temperature_sensor" +CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" +CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" + +ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") +UFireECComponent = ufire_ec_ns.class_( + "UFireECComponent", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +UFireECCalibrateProbeAction = ufire_ec_ns.class_( + "UFireECCalibrateProbeAction", automation.Action +) +UFireECResetAction = ufire_ec_ns.class_("UFireECResetAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UFireECComponent), + cv.Exclusive(CONF_TEMPERATURE, "temperature"): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Optional(CONF_EC): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISIEMENS_PER_CENTIMETER, + icon=ICON_EMPTY, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Exclusive(CONF_TEMPERATURE_SENSOR, "temperature"): cv.use_id( + sensor.Sensor + ), + cv.Optional(CONF_TEMPERATURE_COMPENSATION, default=21.0): cv.temperature, + cv.Optional(CONF_TEMPERATURE_COEFFICIENT, default=0.019): cv.float_range( + min=0.01, max=0.04 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x3C)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION])) + cg.add(var.set_temperature_coefficient(config[CONF_TEMPERATURE_COEFFICIENT])) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_EC in config: + sens = await sensor.new_sensor(config[CONF_EC]) + cg.add(var.set_ec_sensor(sens)) + + if CONF_TEMPERATURE_SENSOR in config: + sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR]) + cg.add(var.set_temperature_sensor_external(sens)) + + await i2c.register_i2c_device(var, config) + + +UFIRE_EC_CALIBRATE_PROBE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireECComponent), + cv.Required(CONF_SOLUTION): cv.templatable(float), + cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), + } +) + + +@automation.register_action( + "ufire_ec.calibrate_probe", + UFireECCalibrateProbeAction, + UFIRE_EC_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ec_calibrate_probe_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) + solution_ = await cg.templatable(config[CONF_SOLUTION], args, float) + temperature_ = await cg.templatable(config[CONF_TEMPERATURE], args, float) + cg.add(var.set_solution(solution_)) + cg.add(var.set_temperature(temperature_)) + return var + + +UFIRE_EC_RESET_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireECComponent), + } +) + + +@automation.register_action( + "ufire_ec.reset", + UFireECResetAction, + UFIRE_EC_RESET_SCHEMA, +) +async def ufire_ec_reset_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) + return var diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp new file mode 100644 index 0000000000..d7ed890e21 --- /dev/null +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -0,0 +1,119 @@ +#include "esphome/core/log.h" +#include "ufire_ec.h" + +namespace esphome { +namespace ufire_ec { + +static const char *const TAG = "ufire_ec"; + +void UFireECComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up uFire_ec..."); + + uint8_t version; + if (!this->read_byte(REGISTER_VERSION, &version) && version != 0xFF) { + this->mark_failed(); + this->status_set_error(); + return; + } + ESP_LOGI(TAG, "Found ufire_ec board version 0x%02X", version); + + // Write option for temperature adjustments + uint8_t config; + this->read_byte(REGISTER_CONFIG, &config); + if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) { + config &= ~CONFIG_TEMP_COMPENSATION; + } else { + config |= CONFIG_TEMP_COMPENSATION; + } + this->write_byte(REGISTER_CONFIG, config); + + // Update temperature compensation + this->set_compensation_(this->temperature_compensation_); + this->set_coefficient_(this->temperature_coefficient_); +} + +void UFireECComponent::update() { + int wait = 0; + + if (this->temperature_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_TEMP); + wait += 750; + } else if (this->temperature_sensor_external_ != nullptr) { + this->set_temperature_(this->temperature_sensor_external_->state); + } + + if (this->ec_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_EC); + wait += 750; + } + + if (wait > 0) { + this->set_timeout("data", wait, [this]() { this->update_internal_(); }); + } +} + +void UFireECComponent::update_internal_() { + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(this->measure_temperature_()); + if (this->ec_sensor_ != nullptr) + this->ec_sensor_->publish_state(this->measure_ms_()); +} + +float UFireECComponent::measure_temperature_() { return this->read_data_(REGISTER_TEMP); } + +float UFireECComponent::measure_ms_() { return this->read_data_(REGISTER_MS); } + +void UFireECComponent::set_solution_(float solution, float temperature) { + solution /= (1 - (this->temperature_coefficient_ * (temperature - 25))); + this->write_data_(REGISTER_SOLUTION, solution); +} + +void UFireECComponent::set_compensation_(float temperature) { this->write_data_(REGISTER_COMPENSATION, temperature); } + +void UFireECComponent::set_coefficient_(float coefficient) { this->write_data_(REGISTER_COEFFICENT, coefficient); } + +void UFireECComponent::set_temperature_(float temperature) { this->write_data_(REGISTER_TEMP, temperature); } + +void UFireECComponent::calibrate_probe(float solution, float temperature) { + this->set_solution_(solution, temperature); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_PROBE); +} + +void UFireECComponent::reset_board() { this->write_data_(REGISTER_CALIBRATE_OFFSET, NAN); } + +float UFireECComponent::read_data_(uint8_t reg) { + float f; + uint8_t temp[4]; + + this->write(®, 1); + delay(10); + + for (uint8_t i = 0; i < 4; i++) { + this->read_bytes_raw(temp + i, 1); + } + memcpy(&f, temp, sizeof(f)); + + return f; +} + +void UFireECComponent::write_data_(uint8_t reg, float data) { + uint8_t temp[4]; + + memcpy(temp, &data, sizeof(data)); + this->write_bytes(reg, temp, 4); + delay(10); +} + +void UFireECComponent::dump_config() { + ESP_LOGCONFIG(TAG, "uFire-EC"); + LOG_I2C_DEVICE(this) + LOG_UPDATE_INTERVAL(this) + LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_) + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) + ESP_LOGCONFIG(TAG, " Temperature Compensation: %f", this->temperature_compensation_); + ESP_LOGCONFIG(TAG, " Temperature Coefficient: %f", this->temperature_coefficient_); +} + +} // namespace ufire_ec +} // namespace esphome diff --git a/esphome/components/ufire_ec/ufire_ec.h b/esphome/components/ufire_ec/ufire_ec.h new file mode 100644 index 0000000000..3d436555a2 --- /dev/null +++ b/esphome/components/ufire_ec/ufire_ec.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/core/automation.h" +#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 ufire_ec { + +static const uint8_t CONFIG_TEMP_COMPENSATION = 0x02; + +static const uint8_t REGISTER_VERSION = 0; +static const uint8_t REGISTER_MS = 1; +static const uint8_t REGISTER_TEMP = 5; +static const uint8_t REGISTER_SOLUTION = 9; +static const uint8_t REGISTER_COEFFICENT = 13; +static const uint8_t REGISTER_CALIBRATE_OFFSET = 33; +static const uint8_t REGISTER_COMPENSATION = 45; +static const uint8_t REGISTER_CONFIG = 54; +static const uint8_t REGISTER_TASK = 55; + +static const uint8_t COMMAND_CALIBRATE_PROBE = 20; +static const uint8_t COMMAND_MEASURE_TEMP = 40; +static const uint8_t COMMAND_MEASURE_EC = 80; + +class UFireECComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_temperature_sensor_external(sensor::Sensor *temperature_sensor) { + this->temperature_sensor_external_ = temperature_sensor; + } + void set_ec_sensor(sensor::Sensor *ec_sensor) { this->ec_sensor_ = ec_sensor; } + void set_temperature_compensation(float compensation) { this->temperature_compensation_ = compensation; } + void set_temperature_coefficient(float coefficient) { this->temperature_coefficient_ = coefficient; } + void calibrate_probe(float solution, float temperature); + void reset_board(); + + protected: + float measure_temperature_(); + float measure_ms_(); + void set_solution_(float solution, float temperature); + void set_compensation_(float temperature); + void set_coefficient_(float coefficient); + void set_temperature_(float temperature); + float read_data_(uint8_t reg); + void write_data_(uint8_t reg, float data); + void update_internal_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_external_{nullptr}; + sensor::Sensor *ec_sensor_{nullptr}; + float temperature_compensation_{0.0}; + float temperature_coefficient_{0.0}; +}; + +template class UFireECCalibrateProbeAction : public Action { + public: + UFireECCalibrateProbeAction(UFireECComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + TEMPLATABLE_VALUE(float, temperature) + + void play(Ts... x) override { + this->parent_->calibrate_probe(this->solution_.value(x...), this->temperature_.value(x...)); + } + + protected: + UFireECComponent *parent_; +}; + +template class UFireECResetAction : public Action { + public: + UFireECResetAction(UFireECComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->reset_board(); } + + protected: + UFireECComponent *parent_; +}; + +} // namespace ufire_ec +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index adbd20c620..51c07ab666 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -192,6 +192,7 @@ CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id" CONF_DUMP = "dump" CONF_DURATION = "duration" CONF_EAP = "eap" +CONF_EC = "ec" CONF_ECHO_PIN = "echo_pin" CONF_ECO2 = "eco2" CONF_EFFECT = "effect" @@ -871,6 +872,7 @@ UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" UNIT_MILLISECOND = "ms" +UNIT_MILLISIEMENS_PER_CENTIMETER = "mS/cm" UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" diff --git a/tests/test2.yaml b/tests/test2.yaml index d01a0a742a..f8ed04d389 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -369,6 +369,13 @@ sensor: name: Propane test distance battery_level: name: Propane test battery level + - platform: ufire_ec + id: ufire_ec_board + ec: + name: Ufire EC + temperature_sensor: ha_hello_world_temperature + temperature_compensation: 20.0 + temperature_coefficient: 0.019 time: - platform: homeassistant diff --git a/tests/test4.yaml b/tests/test4.yaml index d6857de7cd..d79b421cf8 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -243,6 +243,14 @@ sensor: - platform: copy source_id: mcp_sensor name: MCP binary sensor copy + - platform: ufire_ec + id: ufire_ec_board + temperature: + name: Ufire Temperature + ec: + name: Ufire EC + temperature_compensation: 20.0 + temperature_coefficient: 0.019 # # platform sensor.apds9960 requires component apds9960 From d66b2a1778c0add96b353328f67d6f514688751d Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Fri, 9 Sep 2022 01:18:02 -0400 Subject: [PATCH 048/838] Add support for MPL3115A2 Pressure/Altitude and Temperature Sensor (#3371) * Add support for mpl3115a2 * Add codeowner * Linter/test updates * Minor changes * Made pressure/altitude exclusive Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/mpl3115a2/__init__.py | 0 esphome/components/mpl3115a2/mpl3115a2.cpp | 99 +++++++++++++++++++ esphome/components/mpl3115a2/mpl3115a2.h | 108 +++++++++++++++++++++ esphome/components/mpl3115a2/sensor.py | 75 ++++++++++++++ tests/test1.yaml | 7 ++ 6 files changed, 290 insertions(+) create mode 100644 esphome/components/mpl3115a2/__init__.py create mode 100644 esphome/components/mpl3115a2/mpl3115a2.cpp create mode 100644 esphome/components/mpl3115a2/mpl3115a2.h create mode 100644 esphome/components/mpl3115a2/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index ff497c35ec..0441a6bfcd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -143,6 +143,7 @@ esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras esphome/components/mopeka_ble/* @spbrogan esphome/components/mopeka_pro_check/* @spbrogan +esphome/components/mpl3115a2/* @kbickar esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw diff --git a/esphome/components/mpl3115a2/__init__.py b/esphome/components/mpl3115a2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mpl3115a2/mpl3115a2.cpp b/esphome/components/mpl3115a2/mpl3115a2.cpp new file mode 100644 index 0000000000..f1e553e107 --- /dev/null +++ b/esphome/components/mpl3115a2/mpl3115a2.cpp @@ -0,0 +1,99 @@ +#include "mpl3115a2.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mpl3115a2 { + +static const char *const TAG = "mpl3115a2"; + +void MPL3115A2Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MPL3115A2..."); + + uint8_t whoami = 0xFF; + if (!this->read_byte(MPL3115A2_WHOAMI, &whoami, false)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + if (whoami != 0xC4) { + this->error_code_ = WRONG_ID; + this->mark_failed(); + return; + } + + // reset + this->write_byte(MPL3115A2_CTRL_REG1, MPL3115A2_CTRL_REG1_RST); + delay(15); + + // enable data ready events for pressure/altitude and temperature + this->write_byte(MPL3115A2_PT_DATA_CFG, + MPL3115A2_PT_DATA_CFG_TDEFE | MPL3115A2_PT_DATA_CFG_PDEFE | MPL3115A2_PT_DATA_CFG_DREM); +} + +void MPL3115A2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MPL3115A2:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with MPL3115A2 failed!"); + break; + case WRONG_ID: + ESP_LOGE(TAG, "MPL3115A2 has invalid id"); + break; + default: + ESP_LOGE(TAG, "Setting up MPL3115A2 registers failed!"); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Pressure", this->pressure_); + LOG_SENSOR(" ", "Altitude", this->altitude_); +} + +void MPL3115A2Component::update() { + uint8_t mode = MPL3115A2_CTRL_REG1_OS128; + this->write_byte(MPL3115A2_CTRL_REG1, mode, true); + // Trigger a new reading + mode |= MPL3115A2_CTRL_REG1_OST; + if (this->altitude_ != nullptr) + mode |= MPL3115A2_CTRL_REG1_ALT; + this->write_byte(MPL3115A2_CTRL_REG1, mode, true); + + // Wait until status shows reading available + uint8_t status = 0; + if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) { + delay(10); + if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) { + return; + } + } + + uint8_t buffer[5] = {0, 0, 0, 0, 0}; + this->read_register(MPL3115A2_REGISTER_PRESSURE_MSB, buffer, 5, false); + + float altitude = 0, pressure = 0; + if (this->altitude_ != nullptr) { + int32_t alt = encode_uint32(buffer[0], buffer[1], buffer[2], 0); + altitude = float(alt) / 65536.0; + this->altitude_->publish_state(altitude); + } else { + uint32_t p = encode_uint32(0, buffer[0], buffer[1], buffer[2]); + pressure = float(p) / 6400.0; + if (this->pressure_ != nullptr) + this->pressure_->publish_state(pressure); + } + int16_t t = encode_uint16(buffer[3], buffer[4]); + float temperature = float(t) / 256.0; + if (this->temperature_ != nullptr) + this->temperature_->publish_state(temperature); + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Altitude=%.1f Pressure=%.1f", temperature, altitude, pressure); + + this->status_clear_warning(); +} + +} // namespace mpl3115a2 +} // namespace esphome diff --git a/esphome/components/mpl3115a2/mpl3115a2.h b/esphome/components/mpl3115a2/mpl3115a2.h new file mode 100644 index 0000000000..00a6d90c52 --- /dev/null +++ b/esphome/components/mpl3115a2/mpl3115a2.h @@ -0,0 +1,108 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mpl3115a2 { + +// enums from https://github.com/adafruit/Adafruit_MPL3115A2_Library/ +/** MPL3115A2 registers **/ +enum { + MPL3115A2_REGISTER_STATUS = (0x00), + + MPL3115A2_REGISTER_PRESSURE_MSB = (0x01), + MPL3115A2_REGISTER_PRESSURE_CSB = (0x02), + MPL3115A2_REGISTER_PRESSURE_LSB = (0x03), + + MPL3115A2_REGISTER_TEMP_MSB = (0x04), + MPL3115A2_REGISTER_TEMP_LSB = (0x05), + + MPL3115A2_REGISTER_DR_STATUS = (0x06), + + MPL3115A2_OUT_P_DELTA_MSB = (0x07), + MPL3115A2_OUT_P_DELTA_CSB = (0x08), + MPL3115A2_OUT_P_DELTA_LSB = (0x09), + + MPL3115A2_OUT_T_DELTA_MSB = (0x0A), + MPL3115A2_OUT_T_DELTA_LSB = (0x0B), + + MPL3115A2_WHOAMI = (0x0C), + + MPL3115A2_BAR_IN_MSB = (0x14), + MPL3115A2_BAR_IN_LSB = (0x15), +}; + +/** MPL3115A2 status register bits **/ +enum { + MPL3115A2_REGISTER_STATUS_TDR = 0x02, + MPL3115A2_REGISTER_STATUS_PDR = 0x04, + MPL3115A2_REGISTER_STATUS_PTDR = 0x08, +}; + +/** MPL3115A2 PT DATA register bits **/ +enum { + MPL3115A2_PT_DATA_CFG = 0x13, + MPL3115A2_PT_DATA_CFG_TDEFE = 0x01, + MPL3115A2_PT_DATA_CFG_PDEFE = 0x02, + MPL3115A2_PT_DATA_CFG_DREM = 0x04, +}; + +/** MPL3115A2 control registers **/ +enum { + + MPL3115A2_CTRL_REG1 = (0x26), + MPL3115A2_CTRL_REG2 = (0x27), + MPL3115A2_CTRL_REG3 = (0x28), + MPL3115A2_CTRL_REG4 = (0x29), + MPL3115A2_CTRL_REG5 = (0x2A), +}; + +/** MPL3115A2 control register bits **/ +enum { + MPL3115A2_CTRL_REG1_SBYB = 0x01, + MPL3115A2_CTRL_REG1_OST = 0x02, + MPL3115A2_CTRL_REG1_RST = 0x04, + MPL3115A2_CTRL_REG1_RAW = 0x40, + MPL3115A2_CTRL_REG1_ALT = 0x80, + MPL3115A2_CTRL_REG1_BAR = 0x00, +}; + +/** MPL3115A2 oversample values **/ +enum { + MPL3115A2_CTRL_REG1_OS1 = 0x00, + MPL3115A2_CTRL_REG1_OS2 = 0x08, + MPL3115A2_CTRL_REG1_OS4 = 0x10, + MPL3115A2_CTRL_REG1_OS8 = 0x18, + MPL3115A2_CTRL_REG1_OS16 = 0x20, + MPL3115A2_CTRL_REG1_OS32 = 0x28, + MPL3115A2_CTRL_REG1_OS64 = 0x30, + MPL3115A2_CTRL_REG1_OS128 = 0x38, +}; + +class MPL3115A2Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_altitude(sensor::Sensor *altitude) { altitude_ = altitude; } + void set_pressure(sensor::Sensor *pressure) { pressure_ = pressure; } + + void setup() override; + void dump_config() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *altitude_{nullptr}; + sensor::Sensor *pressure_{nullptr}; + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + WRONG_ID, + } error_code_{NONE}; +}; + +} // namespace mpl3115a2 +} // namespace esphome diff --git a/esphome/components/mpl3115a2/sensor.py b/esphome/components/mpl3115a2/sensor.py new file mode 100644 index 0000000000..68ed0e08a8 --- /dev/null +++ b/esphome/components/mpl3115a2/sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ALTITUDE, + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_METER, +) + +CODEOWNERS = ["@kbickar"] +DEPENDENCIES = ["i2c"] + +mpl3115a2_ns = cg.esphome_ns.namespace("mpl3115a2") +MPL3115A2Component = mpl3115a2_ns.class_( + "MPL3115A2Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPL3115A2Component), + cv.Exclusive( + CONF_PRESSURE, + "pressure", + f"{CONF_PRESSURE} and {CONF_ALTITUDE} can't be used together", + ): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Exclusive( + CONF_ALTITUDE, + "pressure", + f"{CONF_PRESSURE} and {CONF_ALTITUDE} can't be used together", + ): sensor.sensor_schema( + unit_of_measurement=UNIT_METER, + accuracy_decimals=1, + 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, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x60)) +) + + +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 CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + elif CONF_ALTITUDE in config: + sens = await sensor.new_sensor(config[CONF_ALTITUDE]) + cg.add(var.set_altitude(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 20209923aa..e5e9754d74 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1167,6 +1167,13 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s + - platform: mpl3115a2 + i2c_id: i2c_bus + temperature: + name: "MPL3115A2 Temperature" + pressure: + name: "MPL3115A2 Pressure" + update_interval: 10s esp32_touch: setup_mode: false From 8ba207fc7f9ba08cb87eb31c9a020ce3541a4d2f Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sun, 11 Sep 2022 23:36:09 +0200 Subject: [PATCH 049/838] Add support for BL0942 voltage, current, energy and power Sensor (#3777) --- CODEOWNERS | 1 + esphome/components/bl0942/__init__.py | 1 + esphome/components/bl0942/bl0942.cpp | 121 ++++++++++++++++++++++++++ esphome/components/bl0942/bl0942.h | 68 +++++++++++++++ esphome/components/bl0942/sensor.py | 93 ++++++++++++++++++++ tests/test3.yaml | 12 +++ 6 files changed, 296 insertions(+) create mode 100644 esphome/components/bl0942/__init__.py create mode 100644 esphome/components/bl0942/bl0942.cpp create mode 100644 esphome/components/bl0942/bl0942.h create mode 100644 esphome/components/bl0942/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 0441a6bfcd..e63528fabc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -35,6 +35,7 @@ esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- +esphome/components/bl0942/* @dbuezas esphome/components/ble_client/* @buxtronix esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bme680_bsec/* @trvrnrth diff --git a/esphome/components/bl0942/__init__.py b/esphome/components/bl0942/__init__.py new file mode 100644 index 0000000000..8ef7857b7b --- /dev/null +++ b/esphome/components/bl0942/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@dbuezas"] diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp new file mode 100644 index 0000000000..e6d18a82a7 --- /dev/null +++ b/esphome/components/bl0942/bl0942.cpp @@ -0,0 +1,121 @@ +#include "bl0942.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bl0942 { + +static const char *const TAG = "bl0942"; + +static const uint8_t BL0942_READ_COMMAND = 0x58; +static const uint8_t BL0942_FULL_PACKET = 0xAA; +static const uint8_t BL0942_PACKET_HEADER = 0x55; + +static const uint8_t BL0942_WRITE_COMMAND = 0xA8; +static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; +static const uint8_t BL0942_REG_MODE = 0x18; +static const uint8_t BL0942_REG_SOFT_RESET = 0x19; +static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; +static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; + +// TODO: Confirm insialisation works as intended +const uint8_t BL0942_INIT[5][6] = { + // Reset to default + {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, + // Enable User Operation Write + {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, + // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS + {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, + // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS + {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, + // 0x181C = Half cycle, Fast RMS threshold 6172 + {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; + +void BL0942::loop() { + DataPacket buffer; + if (!this->available()) { + return; + } + if (read_array((uint8_t *) &buffer, sizeof(buffer))) { + if (validate_checksum(&buffer)) { + received_package_(&buffer); + } + } else { + ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); + while (read() >= 0) + ; + } +} + +bool BL0942::validate_checksum(DataPacket *data) { + uint8_t checksum = BL0942_READ_COMMAND; + // Whole package but checksum + uint8_t *raw = (uint8_t *) data; + for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { + checksum += raw[i]; + } + checksum ^= 0xFF; + if (checksum != data->checksum) { + ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); + } + return checksum == data->checksum; +} + +void BL0942::update() { + this->flush(); + this->write_byte(BL0942_READ_COMMAND); + this->write_byte(BL0942_FULL_PACKET); +} + +void BL0942::setup() { + for (auto *i : BL0942_INIT) { + this->write_array(i, 6); + delay(1); + } + this->flush(); +} + +void BL0942::received_package_(DataPacket *data) { + // Bad header + if (data->frame_header != BL0942_PACKET_HEADER) { + ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); + return; + } + + float v_rms = (uint24_t) data->v_rms / voltage_reference_; + float i_rms = (uint24_t) data->i_rms / current_reference_; + float watt = (int24_t) data->watt / power_reference_; + uint32_t cf_cnt = (uint24_t) data->cf_cnt; + float total_energy_consumption = cf_cnt / energy_reference_; + float frequency = 1000000.0f / data->frequency; + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(v_rms); + } + if (current_sensor_ != nullptr) { + current_sensor_->publish_state(i_rms); + } + if (power_sensor_ != nullptr) { + power_sensor_->publish_state(watt); + } + if (energy_sensor_ != nullptr) { + energy_sensor_->publish_state(total_energy_consumption); + } + if (frequency_sensor_ != nullptr) { + frequency_sensor_->publish_state(frequency); + } + + ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt, + cf_cnt, total_energy_consumption, frequency, data->status); +} + +void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) + ESP_LOGCONFIG(TAG, "BL0942:"); + LOG_SENSOR("", "Voltage", this->voltage_sensor_); + LOG_SENSOR("", "Current", this->current_sensor_); + LOG_SENSOR("", "Power", this->power_sensor_); + LOG_SENSOR("", "Energy", this->energy_sensor_); + LOG_SENSOR("", "frequency", this->frequency_sensor_); +} + +} // namespace bl0942 +} // namespace esphome diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h new file mode 100644 index 0000000000..8149b7493b --- /dev/null +++ b/esphome/components/bl0942/bl0942.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/datatypes.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace bl0942 { + +static const float BL0942_PREF = 596; // taken from tasmota +static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 +static const float BL0942_IREF = 251213.46469622; // 305978/1.218 +static const float BL0942_EREF = 3304.61127328; // Measured + +struct DataPacket { + uint8_t frame_header; + uint24_le_t i_rms; + uint24_le_t v_rms; + uint24_le_t i_fast_rms; + int24_le_t watt; + uint24_le_t cf_cnt; + uint16_le_t frequency; + uint8_t reserved1; + uint8_t status; + uint8_t reserved2; + uint8_t reserved3; + uint8_t checksum; +} __attribute__((packed)); + +class BL0942 : public PollingComponent, public uart::UARTDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } + void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } + void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } + void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } + + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + sensor::Sensor *voltage_sensor_; + sensor::Sensor *current_sensor_; + // NB This may be negative as the circuits is seemingly able to measure + // power in both directions + sensor::Sensor *power_sensor_; + sensor::Sensor *energy_sensor_; + sensor::Sensor *frequency_sensor_; + + // Divide by this to turn into Watt + float power_reference_ = BL0942_PREF; + // Divide by this to turn into Volt + float voltage_reference_ = BL0942_UREF; + // Divide by this to turn into Ampere + float current_reference_ = BL0942_IREF; + // Divide by this to turn into kWh + float energy_reference_ = BL0942_EREF; + + static bool validate_checksum(DataPacket *data); + + void received_package_(DataPacket *data); +}; +} // namespace bl0942 +} // namespace esphome diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py new file mode 100644 index 0000000000..f23375b309 --- /dev/null +++ b/esphome/components/bl0942/sensor.py @@ -0,0 +1,93 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_CURRENT, + CONF_ENERGY, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + CONF_FREQUENCY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_FREQUENCY, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, + UNIT_HERTZ, +) + +DEPENDENCIES = ["uart"] + +bl0942_ns = cg.esphome_ns.namespace("bl0942") +BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0942), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + ), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .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) + + if CONF_VOLTAGE in config: + conf = config[CONF_VOLTAGE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_voltage_sensor(sens)) + if CONF_CURRENT in config: + conf = config[CONF_CURRENT] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor(sens)) + if CONF_POWER in config: + conf = config[CONF_POWER] + sens = await sensor.new_sensor(conf) + cg.add(var.set_power_sensor(sens)) + if CONF_ENERGY in config: + conf = config[CONF_ENERGY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_energy_sensor(sens)) + if CONF_FREQUENCY in config: + conf = config[CONF_FREQUENCY] + sens = await sensor.new_sensor(conf) + cg.add(var.set_frequency_sensor(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index de0fc0dfdb..1d4b4fb076 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -530,6 +530,18 @@ sensor: name: BL0940 Internal temperature external_temperature: name: BL0940 External temperature + - platform: bl0942 + uart_id: uart3 + voltage: + name: 'BL0942 Voltage' + current: + name: 'BL0942 Current' + power: + name: 'BL0942 Power' + energy: + name: 'BL0942 Energy' + frequency: + name: "BL0942 Frequency" - platform: pzem004t uart_id: uart3 voltage: From 790280ace93b153a6c255ca94edcb99dafa2436d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 10:16:18 +1200 Subject: [PATCH 050/838] Bump pylint from 2.15.0 to 2.15.2 (#3785) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1d57fef954..5ef791906d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.15.0 +pylint==2.15.2 flake8==5.0.4 black==22.8.0 # also change in .pre-commit-config.yaml when updating pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating From 9a5f865eeabc3aab37c70debe8c19d33cac8045a Mon Sep 17 00:00:00 2001 From: anatoly-savchenkov <48646998+anatoly-savchenkov@users.noreply.github.com> Date: Mon, 12 Sep 2022 01:23:46 +0300 Subject: [PATCH 051/838] Add Factory Reset button and switch (#3724) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/esp32/preferences.cpp | 13 ++++++- esphome/components/esp8266/preferences.cpp | 21 +++++++++-- esphome/components/factory_reset/__init__.py | 5 +++ .../factory_reset/button/__init__.py | 30 ++++++++++++++++ .../button/factory_reset_button.cpp | 21 +++++++++++ .../button/factory_reset_button.h | 18 ++++++++++ .../factory_reset/switch/__init__.py | 35 +++++++++++++++++++ .../switch/factory_reset_switch.cpp | 26 ++++++++++++++ .../switch/factory_reset_switch.h | 18 ++++++++++ esphome/core/preferences.h | 8 +++++ tests/test1.yaml | 2 ++ tests/test3.yaml | 3 ++ 13 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 esphome/components/factory_reset/__init__.py create mode 100644 esphome/components/factory_reset/button/__init__.py create mode 100644 esphome/components/factory_reset/button/factory_reset_button.cpp create mode 100644 esphome/components/factory_reset/button/factory_reset_button.h create mode 100644 esphome/components/factory_reset/switch/__init__.py create mode 100644 esphome/components/factory_reset/switch/factory_reset_switch.cpp create mode 100644 esphome/components/factory_reset/switch/factory_reset_switch.h diff --git a/CODEOWNERS b/CODEOWNERS index e63528fabc..85ac107975 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -77,6 +77,7 @@ esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb +esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/fastled_base/* @OttoWinter esphome/components/feedback/* @ianchi esphome/components/fingerprint_grow/* @OnFreund @loongyh diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index d070a20d82..6a6305cf87 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -141,7 +141,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGD(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), + ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), last_key.c_str()); } @@ -170,6 +170,17 @@ class ESP32Preferences : public ESPPreferences { } return to_save.data != stored_data.data; } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + s_pending_save.clear(); + + nvs_flash_deinit(); + nvs_flash_erase(); + // Make the handle invalid to prevent any saves until restart + nvs_handle = 0; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 0e42cea576..1bd20f16ae 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -243,17 +243,34 @@ class ESP8266Preferences : public ESPPreferences { } } if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); return false; } if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + ESP_LOGE(TAG, "Write ESP8266 flash failed!"); return false; } s_flash_dirty = false; return true; } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + SpiFlashOpResult erase_res; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); + return false; + } + + // Protect flash from writing till restart + s_prevent_write = true; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py new file mode 100644 index 0000000000..f1bcfd8c55 --- /dev/null +++ b/esphome/components/factory_reset/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@anatoly-savchenkov"] + +factory_reset_ns = cg.esphome_ns.namespace("factory_reset") diff --git a/esphome/components/factory_reset/button/__init__.py b/esphome/components/factory_reset/button/__init__.py new file mode 100644 index 0000000000..d5beac34b5 --- /dev/null +++ b/esphome/components/factory_reset/button/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) +from .. import factory_reset_ns + +FactoryResetButton = factory_reset_ns.class_( + "FactoryResetButton", button.Button, cg.Component +) + +CONFIG_SCHEMA = ( + button.button_schema( + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ) + .extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)}) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/factory_reset/button/factory_reset_button.cpp b/esphome/components/factory_reset/button/factory_reset_button.cpp new file mode 100644 index 0000000000..9354a3363e --- /dev/null +++ b/esphome/components/factory_reset/button/factory_reset_button.cpp @@ -0,0 +1,21 @@ +#include "factory_reset_button.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace factory_reset { + +static const char *const TAG = "factory_reset.button"; + +void FactoryResetButton::dump_config() { LOG_BUTTON("", "Factory Reset Button", this); } +void FactoryResetButton::press_action() { + ESP_LOGI(TAG, "Resetting to factory defaults..."); + // Let MQTT settle a bit + delay(100); // NOLINT + global_preferences->reset(); + App.safe_reboot(); +} + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/button/factory_reset_button.h b/esphome/components/factory_reset/button/factory_reset_button.h new file mode 100644 index 0000000000..9996a860d9 --- /dev/null +++ b/esphome/components/factory_reset/button/factory_reset_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace factory_reset { + +class FactoryResetButton : public button::Button, public Component { + public: + void dump_config() override; + + protected: + void press_action() override; +}; + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/switch/__init__.py b/esphome/components/factory_reset/switch/__init__.py new file mode 100644 index 0000000000..3cc19a35a3 --- /dev/null +++ b/esphome/components/factory_reset/switch/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART_ALERT, +) +from .. import factory_reset_ns + +FactoryResetSwitch = factory_reset_ns.class_( + "FactoryResetSwitch", switch.Switch, cg.Component +) + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(FactoryResetSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Factory Reset switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): cv.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) diff --git a/esphome/components/factory_reset/switch/factory_reset_switch.cpp b/esphome/components/factory_reset/switch/factory_reset_switch.cpp new file mode 100644 index 0000000000..7bc8676736 --- /dev/null +++ b/esphome/components/factory_reset/switch/factory_reset_switch.cpp @@ -0,0 +1,26 @@ +#include "factory_reset_switch.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace factory_reset { + +static const char *const TAG = "factory_reset.switch"; + +void FactoryResetSwitch::dump_config() { LOG_SWITCH("", "Factory Reset Switch", this); } +void FactoryResetSwitch::write_state(bool state) { + // Acknowledge + this->publish_state(false); + + if (state) { + ESP_LOGI(TAG, "Resetting to factory defaults..."); + // Let MQTT settle a bit + delay(100); // NOLINT + global_preferences->reset(); + App.safe_reboot(); + } +} + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/components/factory_reset/switch/factory_reset_switch.h b/esphome/components/factory_reset/switch/factory_reset_switch.h new file mode 100644 index 0000000000..2c914ea76d --- /dev/null +++ b/esphome/components/factory_reset/switch/factory_reset_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace factory_reset { + +class FactoryResetSwitch : public switch_::Switch, public Component { + public: + void dump_config() override; + + protected: + void write_state(bool state) override; +}; + +} // namespace factory_reset +} // namespace esphome diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 2b13061a59..6d2dd967e9 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -46,6 +46,14 @@ class ESPPreferences { */ virtual bool sync() = 0; + /** + * Forget all unsaved changes and re-initialize the permanent preferences storage. + * Usually followed by a restart which moves the system to "factory" conditions + * + * @return true if operation is successful. + */ + virtual bool reset() = 0; + template::value, bool> = true> ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { return this->make_preference(sizeof(T), type, in_flash); diff --git a/tests/test1.yaml b/tests/test1.yaml index e5e9754d74..e213a8b041 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2178,6 +2178,8 @@ switch: name: Living Room Restart - platform: safe_mode name: Living Room Restart (Safe Mode) + - platform: factory_reset + name: Living Room Restart (Factory Default Settings) - platform: shutdown name: Living Room Shutdown - platform: output diff --git a/tests/test3.yaml b/tests/test3.yaml index 1d4b4fb076..4eee0fd2c9 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1529,6 +1529,9 @@ button: target_mac_address: 12:34:56:78:90:ab name: wol_test_1 id: wol_1 + - platform: factory_reset + name: Restart Button (Factory Default Settings) + cd74hc4067: pin_s0: GPIO12 From be473b97c49562996fa0a257a6e31b933eb0b431 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 12 Sep 2022 00:32:07 +0200 Subject: [PATCH 052/838] [MPU6050] Support devices with WHOAMI 0x98 (#3784) --- esphome/components/mpu6050/mpu6050.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mpu6050/mpu6050.cpp b/esphome/components/mpu6050/mpu6050.cpp index 8a0756e63c..cc426e58a2 100644 --- a/esphome/components/mpu6050/mpu6050.cpp +++ b/esphome/components/mpu6050/mpu6050.cpp @@ -23,7 +23,7 @@ const float GRAVITY_EARTH = 9.80665f; void MPU6050Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MPU6050..."); uint8_t who_am_i; - if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || who_am_i != 0x68) { + if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || (who_am_i != 0x68 && who_am_i != 0x98)) { this->mark_failed(); return; } From 9ff187c3f8b2e2653ca1c49e274cbcc735b224c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 10:32:33 +1200 Subject: [PATCH 053/838] Bump zeroconf from 0.39.0 to 0.39.1 (#3782) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f4cb4285ab..bd0c70bb72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==3.3.1 click==8.1.3 esphome-dashboard==20220508.0 aioesphomeapi==10.13.0 -zeroconf==0.39.0 +zeroconf==0.39.1 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From cbd8d70431decfcaaf1c36e931a693f9ca398d30 Mon Sep 17 00:00:00 2001 From: "Jordan W. Cobb" Date: Mon, 12 Sep 2022 11:30:15 -0400 Subject: [PATCH 054/838] Add support for TM1638 Led and Key component (#3340) --- CODEOWNERS | 1 + esphome/components/tm1638/__init__.py | 0 .../tm1638/binary_sensor/__init__.py | 22 ++ .../tm1638/binary_sensor/tm1638_key.cpp | 13 + .../tm1638/binary_sensor/tm1638_key.h | 19 ++ esphome/components/tm1638/display.py | 55 ++++ esphome/components/tm1638/output/__init__.py | 25 ++ .../tm1638/output/tm1638_output_led.cpp | 17 ++ .../tm1638/output/tm1638_output_led.h | 25 ++ esphome/components/tm1638/sevenseg.h | 107 +++++++ esphome/components/tm1638/switch/__init__.py | 24 ++ .../tm1638/switch/tm1638_switch_led.cpp | 20 ++ .../tm1638/switch/tm1638_switch_led.h | 23 ++ esphome/components/tm1638/tm1638.cpp | 288 ++++++++++++++++++ esphome/components/tm1638/tm1638.h | 81 +++++ esphome/const.py | 2 + tests/test5.yaml | 133 ++++++++ 17 files changed, 855 insertions(+) create mode 100644 esphome/components/tm1638/__init__.py create mode 100644 esphome/components/tm1638/binary_sensor/__init__.py create mode 100644 esphome/components/tm1638/binary_sensor/tm1638_key.cpp create mode 100644 esphome/components/tm1638/binary_sensor/tm1638_key.h create mode 100644 esphome/components/tm1638/display.py create mode 100644 esphome/components/tm1638/output/__init__.py create mode 100644 esphome/components/tm1638/output/tm1638_output_led.cpp create mode 100644 esphome/components/tm1638/output/tm1638_output_led.h create mode 100644 esphome/components/tm1638/sevenseg.h create mode 100644 esphome/components/tm1638/switch/__init__.py create mode 100644 esphome/components/tm1638/switch/tm1638_switch_led.cpp create mode 100644 esphome/components/tm1638/switch/tm1638_switch_led.h create mode 100644 esphome/components/tm1638/tm1638.cpp create mode 100644 esphome/components/tm1638/tm1638.h diff --git a/CODEOWNERS b/CODEOWNERS index 85ac107975..3a1087191c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -230,6 +230,7 @@ esphome/components/time/* @OttoWinter esphome/components/tlc5947/* @rnauber esphome/components/tm1621/* @Philippe12 esphome/components/tm1637/* @glmnet +esphome/components/tm1638/* @skykingjwc esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka diff --git a/esphome/components/tm1638/__init__.py b/esphome/components/tm1638/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/tm1638/binary_sensor/__init__.py b/esphome/components/tm1638/binary_sensor/__init__.py new file mode 100644 index 0000000000..7262d9e9e1 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/__init__.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_KEY +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638Key = tm1638_ns.class_("TM1638Key", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Key), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_KEY): cv.int_range(min=0, max=15), + } +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + cg.add(var.set_keycode(config[CONF_KEY])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(hub.register_listener(var)) diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.cpp b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp new file mode 100644 index 0000000000..c143bafaea --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.cpp @@ -0,0 +1,13 @@ +#include "tm1638_key.h" + +namespace esphome { +namespace tm1638 { + +void TM1638Key::keys_update(uint8_t keys) { + bool pressed = keys & (1 << key_code_); + if (pressed != this->state) + this->publish_state(pressed); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/binary_sensor/tm1638_key.h b/esphome/components/tm1638/binary_sensor/tm1638_key.h new file mode 100644 index 0000000000..0ea385f434 --- /dev/null +++ b/esphome/components/tm1638/binary_sensor/tm1638_key.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638Key : public binary_sensor::BinarySensor, public KeyListener { + public: + void set_keycode(uint8_t key_code) { key_code_ = key_code; }; + void keys_update(uint8_t keys) override; + + protected: + uint8_t key_code_{0}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/display.py b/esphome/components/tm1638/display.py new file mode 100644 index 0000000000..6339983674 --- /dev/null +++ b/esphome/components/tm1638/display.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_ID, + CONF_INTENSITY, + CONF_LAMBDA, + CONF_CLK_PIN, + CONF_DIO_PIN, + CONF_STB_PIN, +) + +CODEOWNERS = ["@skykingjwc"] + +CONF_TM1638_ID = "tm1638_id" + +tm1638_ns = cg.esphome_ns.namespace("tm1638") +TM1638Component = tm1638_ns.class_("TM1638Component", cg.PollingComponent) +TM1638ComponentRef = TM1638Component.operator("ref") + + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638Component), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_STB_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_INTENSITY, default=7): cv.int_range(min=0, max=8), + } +).extend(cv.polling_component_schema("1s")) + + +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]) + cg.add(var.set_clk_pin(clk)) + + dio = await cg.gpio_pin_expression(config[CONF_DIO_PIN]) + cg.add(var.set_dio_pin(dio)) + + stb = await cg.gpio_pin_expression(config[CONF_STB_PIN]) + cg.add(var.set_stb_pin(stb)) + + cg.add(var.set_intensity(config[CONF_INTENSITY])) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(TM1638ComponentRef, "it")], return_type=cg.void + ) + + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1638/output/__init__.py b/esphome/components/tm1638/output/__init__.py new file mode 100644 index 0000000000..2d982e409d --- /dev/null +++ b/esphome/components/tm1638/output/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_ID, CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638OutputLed = tm1638_ns.class_("TM1638OutputLed", output.BinaryOutput, cg.Component) + + +CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638OutputLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).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_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/output/tm1638_output_led.cpp b/esphome/components/tm1638/output/tm1638_output_led.cpp new file mode 100644 index 0000000000..ea1c84e64b --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.cpp @@ -0,0 +1,17 @@ +#include "tm1638_output_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638OutputLed::write_state(bool state) { tm1638_->set_led(led_, state); } + +void TM1638OutputLed::dump_config() { + LOG_BINARY_OUTPUT(this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/output/tm1638_output_led.h b/esphome/components/tm1638/output/tm1638_output_led.h new file mode 100644 index 0000000000..6aa1015aae --- /dev/null +++ b/esphome/components/tm1638/output/tm1638_output_led.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/binary_output.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638OutputLed : public output::BinaryOutput, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + + TM1638Component *tm1638_; + int led_; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/sevenseg.h b/esphome/components/tm1638/sevenseg.h new file mode 100644 index 0000000000..e20a55a69f --- /dev/null +++ b/esphome/components/tm1638/sevenseg.h @@ -0,0 +1,107 @@ +#pragma once + +namespace esphome { +namespace tm1638 { +namespace TM1638Translation { + +const unsigned char SEVEN_SEG[] PROGMEM = { + 0x00, /* (space) */ + 0x86, /* ! */ + 0x22, /* " */ + 0x7E, /* # */ + 0x6D, /* $ */ + 0xD2, /* % */ + 0x46, /* & */ + 0x20, /* ' */ + 0x29, /* ( */ + 0x0B, /* ) */ + 0x21, /* * */ + 0x70, /* + */ + 0x10, /* , */ + 0x40, /* - */ + 0x80, /* . */ + 0x52, /* / */ + 0x3F, /* 0 */ + 0x06, /* 1 */ + 0x5B, /* 2 */ + 0x4F, /* 3 */ + 0x66, /* 4 */ + 0x6D, /* 5 */ + 0x7D, /* 6 */ + 0x07, /* 7 */ + 0x7F, /* 8 */ + 0x6F, /* 9 */ + 0x09, /* : */ + 0x0D, /* ; */ + 0x61, /* < */ + 0x48, /* = */ + 0x43, /* > */ + 0xD3, /* ? */ + 0x5F, /* @ */ + 0x77, /* A */ + 0x7C, /* B */ + 0x39, /* C */ + 0x5E, /* D */ + 0x79, /* E */ + 0x71, /* F */ + 0x3D, /* G */ + 0x76, /* H */ + 0x30, /* I */ + 0x1E, /* J */ + 0x75, /* K */ + 0x38, /* L */ + 0x15, /* M */ + 0x37, /* N */ + 0x3F, /* O */ + 0x73, /* P */ + 0x6B, /* Q */ + 0x33, /* R */ + 0x6D, /* S */ + 0x78, /* T */ + 0x3E, /* U */ + 0x3E, /* V */ + 0x2A, /* W */ + 0x76, /* X */ + 0x6E, /* Y */ + 0x5B, /* Z */ + 0x39, /* [ */ + 0x64, /* \ */ + 0x0F, /* ] */ + 0x23, /* ^ */ + 0x08, /* _ */ + 0x02, /* ` */ + 0x5F, /* a */ + 0x7C, /* b */ + 0x58, /* c */ + 0x5E, /* d */ + 0x7B, /* e */ + 0x71, /* f */ + 0x6F, /* g */ + 0x74, /* h */ + 0x10, /* i */ + 0x0C, /* j */ + 0x75, /* k */ + 0x30, /* l */ + 0x14, /* m */ + 0x54, /* n */ + 0x5C, /* o */ + 0x73, /* p */ + 0x67, /* q */ + 0x50, /* r */ + 0x6D, /* s */ + 0x78, /* t */ + 0x1C, /* u */ + 0x1C, /* v */ + 0x14, /* w */ + 0x76, /* x */ + 0x6E, /* y */ + 0x5B, /* z */ + 0x46, /* { */ + 0x30, /* | */ + 0x70, /* } */ + 0x01, /* ~ */ +}; + +}; // namespace TM1638Translation +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/__init__.py b/esphome/components/tm1638/switch/__init__.py new file mode 100644 index 0000000000..ed6aa91d03 --- /dev/null +++ b/esphome/components/tm1638/switch/__init__.py @@ -0,0 +1,24 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_LED +from ..display import tm1638_ns, TM1638Component, CONF_TM1638_ID + +TM1638SwitchLed = tm1638_ns.class_("TM1638SwitchLed", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1638SwitchLed), + cv.GenerateID(CONF_TM1638_ID): cv.use_id(TM1638Component), + cv.Required(CONF_LED): cv.int_range(min=0, max=7), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + cg.add(var.set_lednum(config[CONF_LED])) + hub = await cg.get_variable(config[CONF_TM1638_ID]) + cg.add(var.set_tm1638(hub)) diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.cpp b/esphome/components/tm1638/switch/tm1638_switch_led.cpp new file mode 100644 index 0000000000..60c9e8b4a9 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.cpp @@ -0,0 +1,20 @@ +#include "tm1638_switch_led.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "tm1638.led"; + +void TM1638SwitchLed::write_state(bool state) { + tm1638_->set_led(led_, state); + publish_state(state); +} + +void TM1638SwitchLed::dump_config() { + LOG_SWITCH("", "TM1638 LED", this); + ESP_LOGCONFIG(TAG, " LED: %d", led_); +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.h b/esphome/components/tm1638/switch/tm1638_switch_led.h new file mode 100644 index 0000000000..10516e0079 --- /dev/null +++ b/esphome/components/tm1638/switch/tm1638_switch_led.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../tm1638.h" + +namespace esphome { +namespace tm1638 { + +class TM1638SwitchLed : public switch_::Switch, public Component { + public: + void dump_config() override; + + void set_tm1638(TM1638Component *tm1638) { tm1638_ = tm1638; } + void set_lednum(int led) { led_ = led; } + + protected: + void write_state(bool state) override; + TM1638Component *tm1638_; + int led_; +}; +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp new file mode 100644 index 0000000000..526b53f601 --- /dev/null +++ b/esphome/components/tm1638/tm1638.cpp @@ -0,0 +1,288 @@ +#include "tm1638.h" +#include "sevenseg.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tm1638 { + +static const char *const TAG = "display.tm1638"; +static const uint8_t TM1638_REGISTER_FIXEDADDRESS = 0x44; +static const uint8_t TM1638_REGISTER_AUTOADDRESS = 0x40; +static const uint8_t TM1638_REGISTER_READBUTTONS = 0x42; +static const uint8_t TM1638_REGISTER_DISPLAYOFF = 0x80; +static const uint8_t TM1638_REGISTER_DISPLAYON = 0x88; +static const uint8_t TM1638_REGISTER_7SEG_0 = 0xC0; +static const uint8_t TM1638_REGISTER_LED_0 = 0xC1; +static const uint8_t TM1638_UNKNOWN_CHAR = 0b11111111; + +static const uint8_t TM1638_SHIFT_DELAY = 4; // clock pause between commands, default 4ms + +void TM1638Component::setup() { + ESP_LOGD(TAG, "Setting up TM1638..."); + + this->clk_pin_->setup(); // OUTPUT + this->dio_pin_->setup(); // OUTPUT + this->stb_pin_->setup(); // OUTPUT + + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->clk_pin_->digital_write(false); + this->dio_pin_->digital_write(false); + this->stb_pin_->digital_write(false); + + this->set_intensity(intensity_); + + this->reset_(); // all LEDs off + + for (uint8_t i = 0; i < 8; i++) // zero fill print buffer + this->buffer_[i] = 0; +} + +void TM1638Component::dump_config() { + ESP_LOGCONFIG(TAG, "TM1638:"); + ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); + LOG_PIN(" CLK Pin: ", this->clk_pin_); + LOG_PIN(" DIO Pin: ", this->dio_pin_); + LOG_PIN(" STB Pin: ", this->stb_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void TM1638Component::loop() { + if (this->listeners_.empty()) + return; + + uint8_t keys = this->get_keys(); + for (auto &listener : this->listeners_) + listener->keys_update(keys); +} + +uint8_t TM1638Component::get_keys() { + uint8_t buttons = 0; + + this->stb_pin_->digital_write(false); + + this->shift_out_(TM1638_REGISTER_READBUTTONS); + + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); + + delayMicroseconds(10); + + for (uint8_t i = 0; i < 4; i++) { // read the 4 button registers + uint8_t v = this->shift_in_(); + buttons |= v << i; // shift bits to correct slots in the byte + } + + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + + this->stb_pin_->digital_write(true); + + return buttons; +} + +void TM1638Component::update() { // this is called at the interval specified in the config.yaml + if (this->writer_.has_value()) { + (*this->writer_)(*this); + } + + this->display(); +} + +float TM1638Component::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void TM1638Component::display() { + for (uint8_t i = 0; i < 8; i++) { + this->set_7seg_(i, buffer_[i]); + } +} + +void TM1638Component::reset_() { + uint8_t num_commands = 16; // 16 addresses, 8 for 7seg and 8 for LEDs + uint8_t commands[num_commands]; + + for (uint8_t i = 0; i < num_commands; i++) { + commands[i] = 0; + } + + this->send_command_sequence_(commands, num_commands, TM1638_REGISTER_7SEG_0); +} + +/////////////// LEDs ///////////////// + +void TM1638Component::set_led(int led_pos, bool led_on_off) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2]; + + commands[0] = TM1638_REGISTER_LED_0 + (led_pos << 1); + commands[1] = led_on_off; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_7seg_(int seg_pos, uint8_t seg_bits) { + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + uint8_t commands[2] = {}; + + commands[0] = TM1638_REGISTER_7SEG_0 + (seg_pos << 1); + commands[1] = seg_bits; + + this->send_commands_(commands, 2); +} + +void TM1638Component::set_intensity(uint8_t brightness_level) { + this->intensity_ = brightness_level; + + this->send_command_(TM1638_REGISTER_FIXEDADDRESS); + + if (brightness_level > 0) { + this->send_command_((uint8_t)(TM1638_REGISTER_DISPLAYON | intensity_)); + } else { + this->send_command_(TM1638_REGISTER_DISPLAYOFF); + } +} + +/////////////// DISPLAY PRINT ///////////////// + +uint8_t TM1638Component::print(uint8_t start_pos, const char *str) { + uint8_t pos = start_pos; + + bool last_was_dot = false; + + for (; *str != '\0'; str++) { + uint8_t data = TM1638_UNKNOWN_CHAR; + + if (*str >= ' ' && *str <= '~') { + data = progmem_read_byte(&TM1638Translation::SEVEN_SEG[*str - 32]); // subract 32 to account for ASCII offset + } else if (data == TM1638_UNKNOWN_CHAR) { + ESP_LOGW(TAG, "Encountered character '%c' with no TM1638 representation while translating string!", *str); + } + + if (*str == '.') // handle dots + { + if (pos != start_pos && + !last_was_dot) // if we are not at the first position, backup by one unless last char was a dot + { + pos--; + } + this->buffer_[pos] |= 0b10000000; // turn on the dot on the previous position + last_was_dot = true; // set a bit in case the next chracter is also a dot + } else // if not a dot, then just write the character to display + { + if (pos >= 8) { + ESP_LOGI(TAG, "TM1638 String is too long for the display!"); + break; + } + this->buffer_[pos] = data; + last_was_dot = false; // clear dot tracking bit + } + + pos++; + } + return pos - start_pos; +} + +/////////////// PRINT ///////////////// + +uint8_t TM1638Component::print(const char *str) { return this->print(0, str); } + +uint8_t TM1638Component::printf(uint8_t pos, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[64]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + return this->print(buffer); + return 0; +} + +#ifdef USE_TIME +uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::ESPTime time) { + char buffer[64]; + size_t ret = time.strftime(buffer, sizeof(buffer), format); + if (ret > 0) + return this->print(pos, buffer); + return 0; +} +uint8_t TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } +#endif + +//////////////// SPI //////////////// + +void TM1638Component::send_command_(uint8_t value) { + this->stb_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->stb_pin_->digital_write(false); + this->shift_out_(value); + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_commands_(uint8_t const commands[], uint8_t num_commands) { + this->stb_pin_->digital_write(false); + + for (uint8_t i = 0; i < num_commands; i++) { + uint8_t command = commands[i]; + this->shift_out_(command); + } + this->stb_pin_->digital_write(true); +} + +void TM1638Component::send_command_leave_open_(uint8_t value) { + this->stb_pin_->digital_write(false); + this->shift_out_(value); +} + +void TM1638Component::send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address) { + this->send_command_(TM1638_REGISTER_AUTOADDRESS); + this->send_command_leave_open_(starting_address); + + for (uint8_t i = 0; i < num_commands; i++) { + this->shift_out_(commands[i]); + } + + this->stb_pin_->digital_write(true); +} + +uint8_t TM1638Component::shift_in_() { + uint8_t value = 0; + + for (int i = 0; i < 8; ++i) { + value |= dio_pin_->digital_read() << i; + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } + return value; +} + +void TM1638Component::shift_out_(uint8_t val) { + for (int i = 0; i < 8; i++) { + this->dio_pin_->digital_write((val & (1 << i))); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(true); + delayMicroseconds(TM1638_SHIFT_DELAY); + + this->clk_pin_->digital_write(false); + delayMicroseconds(TM1638_SHIFT_DELAY); + } +} + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/components/tm1638/tm1638.h b/esphome/components/tm1638/tm1638.h new file mode 100644 index 0000000000..44160ad227 --- /dev/null +++ b/esphome/components/tm1638/tm1638.h @@ -0,0 +1,81 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + +namespace esphome { +namespace tm1638 { + +class KeyListener { + public: + virtual void keys_update(uint8_t keys){}; +}; + +class TM1638Component; + +using tm1638_writer_t = std::function; + +class TM1638Component : public PollingComponent { + public: + void set_writer(tm1638_writer_t &&writer) { this->writer_ = writer; } + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + void set_intensity(uint8_t brightness_level); + void display(); + + void set_clk_pin(GPIOPin *pin) { this->clk_pin_ = pin; } + void set_dio_pin(GPIOPin *pin) { this->dio_pin_ = pin; } + void set_stb_pin(GPIOPin *pin) { this->stb_pin_ = pin; } + + void register_listener(KeyListener *listener) { this->listeners_.push_back(listener); } + + /// Evaluate the printf-format and print the result at the given position. + uint8_t printf(uint8_t pos, const char *format, ...) __attribute__((format(printf, 3, 4))); + /// Evaluate the printf-format and print the result at position 0. + uint8_t printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + + /// Print `str` at the given position. + uint8_t print(uint8_t pos, const char *str); + /// Print `str` at position 0. + uint8_t print(const char *str); + + void loop() override; + uint8_t get_keys(); + +#ifdef USE_TIME + /// Evaluate the strftime-format and print the result at the given position. + uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); + /// Evaluate the strftime-format and print the result at position 0. + uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); +#endif + + void set_led(int led_pos, bool led_on_off); + + protected: + void set_7seg_(int seg_pos, uint8_t seg_bits); + void send_command_(uint8_t value); + void send_command_leave_open_(uint8_t value); + void send_commands_(uint8_t const commands[], uint8_t num_commands); + void send_command_sequence_(uint8_t commands[], uint8_t num_commands, uint8_t starting_address); + void shift_out_(uint8_t value); + void reset_(); + uint8_t shift_in_(); + uint8_t intensity_{}; /// brghtness of the display 0 through 7 + GPIOPin *clk_pin_; + GPIOPin *stb_pin_; + GPIOPin *dio_pin_; + uint8_t *buffer_ = new uint8_t[8]; + optional writer_{}; + std::vector listeners_{}; +}; + +} // namespace tm1638 +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 51c07ab666..5153efbbe0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -334,6 +334,7 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LED = "led" CONF_LEGEND = "legend" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -653,6 +654,7 @@ CONF_STATE_CLASS = "state_class" CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" +CONF_STB_PIN = "stb_pin" CONF_STEP = "step" CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" diff --git a/tests/test5.yaml b/tests/test5.yaml index 3b6862a4d4..7fc20c452f 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -80,6 +80,91 @@ binary_sensor: 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 + + - 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 + + + + tlc5947: data_pin: GPIO12 clock_pin: GPIO14 @@ -106,6 +191,22 @@ output: 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 + demo: esp32_ble: @@ -354,3 +455,35 @@ switch: 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: 5 #TM1638 STB + clk_pin: 18 #TM1638 CLK + dio_pin: 23 #TM1638 DIO + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); + From ed8f343aadef51328a26404bf8d006597cf1ff00 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 13 Sep 2022 01:17:33 +0200 Subject: [PATCH 055/838] Remove status_set_error from ufire_ec (#3792) --- esphome/components/ufire_ec/ufire_ec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp index d7ed890e21..7af4fadf75 100644 --- a/esphome/components/ufire_ec/ufire_ec.cpp +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -12,7 +12,6 @@ void UFireECComponent::setup() { uint8_t version; if (!this->read_byte(REGISTER_VERSION, &version) && version != 0xFF) { this->mark_failed(); - this->status_set_error(); return; } ESP_LOGI(TAG, "Found ufire_ec board version 0x%02X", version); From 15f0e54cbfadf65a065797796db4dcfeef6d94c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 09:57:43 +1200 Subject: [PATCH 056/838] Bump frenck/action-yamllint from 1.2.0 to 1.3.0 (#3798) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7496ccf388..b5b7521b3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,7 +173,7 @@ jobs: - name: Run yamllint if: matrix.id == 'yamllint' - uses: frenck/action-yamllint@v1.2.0 + uses: frenck/action-yamllint@v1.3.0 - name: Suggested changes run: script/ci-suggest-changes From 49465223a48a49434434d9a59206c15fea1b1229 Mon Sep 17 00:00:00 2001 From: Jonathan V <11986470+jonofmac@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:10:12 -0500 Subject: [PATCH 057/838] esp32_ble_tracker continuous and one shot scanning modes (#3649) Co-authored-by: Jonathan Valdez <@jonofmac> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/esp32_ble_tracker/__init__.py | 39 +++++++++++++++ .../components/esp32_ble_tracker/automation.h | 8 ++++ .../esp32_ble_tracker/esp32_ble_tracker.cpp | 47 +++++++++++++++++-- .../esp32_ble_tracker/esp32_ble_tracker.h | 21 +++++++++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e647b74a8f..db7fcc68ae 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -23,6 +23,8 @@ CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" CONF_ACTIVE = "active" +CONF_CONTINUOUS = "continuous" +CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") @@ -42,6 +44,13 @@ BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( "BLEManufacturerDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref), ) +BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_( + "BLEEndOfScanTrigger", automation.Trigger.template() +) +# Actions +ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_( + "ESP32BLEStartScanAction", automation.Action +) def validate_scan_parameters(config): @@ -138,6 +147,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_WINDOW, default="30ms" ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ACTIVE, default=True): cv.boolean, + cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, } ), validate_scan_parameters, @@ -168,6 +178,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_MANUFACTURER_ID): bt_uuid, } ), + cv.Optional(CONF_ON_SCAN_END): automation.validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)} + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -186,6 +199,7 @@ async def to_code(config): cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) cg.add(var.set_scan_active(params[CONF_ACTIVE])) + cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) for conf in config.get(CONF_ON_BLE_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: @@ -215,11 +229,36 @@ async def to_code(config): if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) + for conf in config.get(CONF_ON_SCAN_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) +ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(ESP32BLETracker), + cv.Optional(CONF_CONTINUOUS, default=False): cv.templatable(cv.boolean), + } +) + + +@automation.register_action( + "esp32_ble_tracker.start_scan", + ESP32BLEStartScanAction, + ESP32_BLE_START_SCAN_ACTION_SCHEMA, +) +async def esp32_ble_tracker_start_scan_action_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) + cg.add(var.set_continuous(config[CONF_CONTINUOUS])) + return var + + async def register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_listener(var)) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 3505e9c26d..d9e3b820af 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -76,6 +76,14 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger, ESPBTUUID uuid_; }; +class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { + public: + explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } + + bool parse_device(const ESPBTDevice &device) override { return false; } + void on_scan_end() override { this->trigger(); } +}; + } // namespace esp32_ble_tracker } // namespace esphome diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 82945dc771..743f246d3a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -46,13 +46,15 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex(); - + this->scanner_idle_ = true; if (!ESP32BLETracker::ble_setup()) { this->mark_failed(); return; } - global_esp32_ble_tracker->start_scan_(true); + if (this->scan_continuous_) { + global_esp32_ble_tracker->start_scan_(true); + } } void ESP32BLETracker::loop() { @@ -68,14 +70,25 @@ void ESP32BLETracker::loop() { ble_event = this->ble_events_.pop(); } + if (this->scanner_idle_) { + return; + } + bool connecting = false; for (auto *client : this->clients_) { if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) connecting = true; } + if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan_(false); + if (this->scan_continuous_) { + global_esp32_ble_tracker->start_scan_(false); + } else if (xSemaphoreTake(this->scan_end_lock_, 0L) && !this->scanner_idle_) { + xSemaphoreGive(this->scan_end_lock_); + global_esp32_ble_tracker->end_of_scan_(); + return; + } } if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { @@ -134,6 +147,15 @@ void ESP32BLETracker::loop() { } } +void ESP32BLETracker::start_scan() { + if (xSemaphoreTake(this->scan_end_lock_, 0L)) { + xSemaphoreGive(this->scan_end_lock_); + global_esp32_ble_tracker->start_scan_(true); + } else { + ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring."); + } +} + bool ESP32BLETracker::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); @@ -225,6 +247,7 @@ void ESP32BLETracker::start_scan_(bool first) { listener->on_scan_end(); } this->already_discovered_.clear(); + this->scanner_idle_ = false; this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; @@ -240,6 +263,22 @@ void ESP32BLETracker::start_scan_(bool first) { }); } +void ESP32BLETracker::end_of_scan_() { + if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { + ESP_LOGW(TAG, "Cannot clean up end of scan!"); + return; + } + + ESP_LOGD(TAG, "End of scan."); + this->scanner_idle_ = true; + this->already_discovered_.clear(); + xSemaphoreGive(this->scan_end_lock_); + this->cancel_timeout("scan"); + + for (auto *listener : this->listeners_) + listener->on_scan_end(); +} + void ESP32BLETracker::register_client(ESPBTClient *client) { client->app_id = ++this->app_id_; this->clients_.push_back(client); @@ -719,7 +758,9 @@ void ESP32BLETracker::dump_config() { ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); + ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); } + void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { const uint64_t address = device.address_uint64(); for (auto &disc : this->already_discovered_) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 62fff30a20..1d2900af69 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/automation.h" #include "esphome/core/helpers.h" #include "queue.h" @@ -171,6 +172,7 @@ class ESP32BLETracker : public Component { void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } void set_scan_active(bool scan_active) { scan_active_ = scan_active; } + void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; } /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; @@ -188,11 +190,15 @@ class ESP32BLETracker : public Component { void print_bt_device_info(const ESPBTDevice &device); + void start_scan(); + protected: /// The FreeRTOS task managing the bluetooth interface. static bool ble_setup(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. void start_scan_(bool first); + /// Called when a scan ends + void end_of_scan_(); /// Callback that will handle all GAP events and redistribute them to other callbacks. static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -221,7 +227,9 @@ class ESP32BLETracker : public Component { uint32_t scan_duration_; uint32_t scan_interval_; uint32_t scan_window_; + bool scan_continuous_; bool scan_active_; + bool scanner_idle_; SemaphoreHandle_t scan_result_lock_; SemaphoreHandle_t scan_end_lock_; size_t scan_result_index_{0}; @@ -235,6 +243,19 @@ class ESP32BLETracker : public Component { // NOLINTNEXTLINE extern ESP32BLETracker *global_esp32_ble_tracker; +template class ESP32BLEStartScanAction : public Action { + public: + ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(bool, continuous) + void play(Ts... x) override { + this->parent_->set_scan_continuous(this->continuous_.value(x...)); + this->parent_->start_scan(); + } + + protected: + ESP32BLETracker *parent_; +}; + } // namespace esp32_ble_tracker } // namespace esphome From b3cca5dcb63a2060921801d77854048f7ba1a844 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:57:45 +1200 Subject: [PATCH 058/838] Add stop action for ble scanning (#3799) --- .../components/esp32_ble_tracker/__init__.py | 25 +++++++++++++++++++ .../components/esp32_ble_tracker/automation.h | 18 +++++++++++++ .../esp32_ble_tracker/esp32_ble_tracker.cpp | 7 ++++++ .../esp32_ble_tracker/esp32_ble_tracker.h | 14 +---------- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index db7fcc68ae..f70fea0639 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -51,6 +51,9 @@ BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_( ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_( "ESP32BLEStartScanAction", automation.Action ) +ESP32BLEStopScanAction = esp32_ble_tracker_ns.class_( + "ESP32BLEStopScanAction", automation.Action +) def validate_scan_parameters(config): @@ -259,6 +262,28 @@ async def esp32_ble_tracker_start_scan_action_to_code( return var +ESP32_BLE_STOP_SCAN_ACTION_SCHEMA = automation.maybe_simple_id( + cv.Schema( + { + cv.GenerateID(): cv.use_id(ESP32BLETracker), + } + ) +) + + +@automation.register_action( + "esp32_ble_tracker.stop_scan", + ESP32BLEStopScanAction, + ESP32_BLE_STOP_SCAN_ACTION_SCHEMA, +) +async def esp32_ble_tracker_stop_scan_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 register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) cg.add(paren.register_listener(var)) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index d9e3b820af..6131d6ddf7 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -84,6 +84,24 @@ class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { void on_scan_end() override { this->trigger(); } }; +template class ESP32BLEStartScanAction : public Action { + public: + ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(bool, continuous) + void play(Ts... x) override { + this->parent_->set_scan_continuous(this->continuous_.value(x...)); + this->parent_->start_scan(); + } + + protected: + ESP32BLETracker *parent_; +}; + +template class ESP32BLEStopScanAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->stop_scan(); } +}; + } // namespace esp32_ble_tracker } // namespace esphome diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 743f246d3a..68d88eb1e8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -156,6 +156,13 @@ void ESP32BLETracker::start_scan() { } } +void ESP32BLETracker::stop_scan() { + ESP_LOGD(TAG, "Stopping scan."); + this->scan_continuous_ = false; + esp_ble_gap_stop_scanning(); + this->cancel_timeout("scan"); +} + bool ESP32BLETracker::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 1d2900af69..29d0c81542 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -191,6 +191,7 @@ class ESP32BLETracker : public Component { void print_bt_device_info(const ESPBTDevice &device); void start_scan(); + void stop_scan(); protected: /// The FreeRTOS task managing the bluetooth interface. @@ -243,19 +244,6 @@ class ESP32BLETracker : public Component { // NOLINTNEXTLINE extern ESP32BLETracker *global_esp32_ble_tracker; -template class ESP32BLEStartScanAction : public Action { - public: - ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} - TEMPLATABLE_VALUE(bool, continuous) - void play(Ts... x) override { - this->parent_->set_scan_continuous(this->continuous_.value(x...)); - this->parent_->start_scan(); - } - - protected: - ESP32BLETracker *parent_; -}; - } // namespace esp32_ble_tracker } // namespace esphome From a5e3cd1a42a1068bc3fc915d8589a4e2a477d2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Kom=C3=A1rek?= Date: Wed, 14 Sep 2022 05:22:59 +0200 Subject: [PATCH 059/838] Add Prometheus Service Discovery for online devices (#3788) --- esphome/dashboard/dashboard.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 889f5cc0bf..9a8f072237 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -644,6 +644,33 @@ def _ping_func(filename, address): 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 MDNSStatusThread(threading.Thread): def run(self): global IMPORT_RESULT @@ -993,6 +1020,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}import", ImportRequestHandler), (f"{rel}secret_keys", SecretKeysRequestHandler), (f"{rel}rename", EsphomeRenameHandler), + (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), ], **app_settings, ) From f4b0917239b91059a5680450e6805ff6fed53a3a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 Sep 2022 16:49:20 +1200 Subject: [PATCH 060/838] Allow ble tracker to subscribe to ota start and stop the scanning (#3800) --- .../components/esp32_ble_tracker/__init__.py | 2 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 37 +++++++++++++------ esphome/components/ota/__init__.py | 1 + esphome/components/ota/ota_component.cpp | 4 ++ esphome/components/ota/ota_component.h | 3 ++ esphome/core/defines.h | 1 + 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index f70fea0639..6d7868d4c5 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -239,6 +239,8 @@ async def to_code(config): if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts + ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 68d88eb1e8..f7e51a8ab3 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -1,10 +1,11 @@ #ifdef USE_ESP32 #include "esp32_ble_tracker.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" -#include "esphome/core/helpers.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include #include @@ -15,6 +16,10 @@ #include #include +#ifdef USE_OTA +#include "esphome/components/ota/ota_component.h" +#endif + #ifdef USE_ARDUINO #include #endif @@ -52,8 +57,16 @@ void ESP32BLETracker::setup() { return; } +#ifdef USE_OTA + ota::global_ota_component->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t error) { + if (state == ota::OTA_STARTED) { + this->stop_scan(); + } + }); +#endif + if (this->scan_continuous_) { - global_esp32_ble_tracker->start_scan_(true); + this->start_scan_(true); } } @@ -83,10 +96,10 @@ void ESP32BLETracker::loop() { if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); if (this->scan_continuous_) { - global_esp32_ble_tracker->start_scan_(false); + this->start_scan_(false); } else if (xSemaphoreTake(this->scan_end_lock_, 0L) && !this->scanner_idle_) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->end_of_scan_(); + this->end_of_scan_(); return; } } @@ -150,7 +163,7 @@ void ESP32BLETracker::loop() { void ESP32BLETracker::start_scan() { if (xSemaphoreTake(this->scan_end_lock_, 0L)) { xSemaphoreGive(this->scan_end_lock_); - global_esp32_ble_tracker->start_scan_(true); + this->start_scan_(true); } else { ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring."); } @@ -299,21 +312,21 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: - global_esp32_ble_tracker->gap_scan_result_(param->scan_rst); + this->gap_scan_result_(param->scan_rst); break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl); + this->gap_scan_set_param_complete_(param->scan_param_cmpl); break; case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl); + this->gap_scan_start_complete_(param->scan_start_cmpl); break; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl); + this->gap_scan_stop_complete_(param->scan_stop_cmpl); break; default: break; } - for (auto *client : global_esp32_ble_tracker->clients_) { + for (auto *client : this->clients_) { client->gap_event_handler(event, param); } } @@ -351,7 +364,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - for (auto *client : global_esp32_ble_tracker->clients_) { + for (auto *client : this->clients_) { client->gattc_event_handler(event, gattc_if, param); } } diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index b3d3b7ad23..1bc4012ce2 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -78,6 +78,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) + cg.add_define("USE_OTA") if CONF_PASSWORD in config: cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 25c9f2e912..a02d64cd08 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -21,6 +21,8 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; +OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + std::unique_ptr make_ota_backend() { #ifdef USE_ARDUINO #ifdef USE_ESP8266 @@ -35,6 +37,8 @@ std::unique_ptr make_ota_backend() { #endif // USE_ESP_IDF } +OTAComponent::OTAComponent() { global_ota_component = this; } + void OTAComponent::setup() { server_ = socket::socket_ip(SOCK_STREAM, 0); if (server_ == nullptr) { diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index d178805168..9a1c92f727 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -41,6 +41,7 @@ enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; /// OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. class OTAComponent : public Component { public: + OTAComponent(); #ifdef USE_OTA_PASSWORD void set_auth_password(const std::string &password) { password_ = password; } #endif // USE_OTA_PASSWORD @@ -103,5 +104,7 @@ class OTAComponent : public Component { #endif }; +extern OTAComponent *global_ota_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace ota } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 90676c421e..cea53b8545 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -32,6 +32,7 @@ #define USE_MEDIA_PLAYER #define USE_MQTT #define USE_NUMBER +#define USE_OTA #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY From 6236db1a27d17ea1f0481a51fad13ebaf1528601 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 14 Sep 2022 06:51:20 +0200 Subject: [PATCH 061/838] Add uFire ISE sensor (#3789) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ufire_ise/__init__.py | 1 + esphome/components/ufire_ise/sensor.py | 127 +++++++++++++++++ esphome/components/ufire_ise/ufire_ise.cpp | 153 +++++++++++++++++++++ esphome/components/ufire_ise/ufire_ise.h | 95 +++++++++++++ esphome/const.py | 2 + tests/test2.yaml | 5 + tests/test4.yaml | 6 + 8 files changed, 390 insertions(+) create mode 100644 esphome/components/ufire_ise/__init__.py create mode 100644 esphome/components/ufire_ise/sensor.py create mode 100644 esphome/components/ufire_ise/ufire_ise.cpp create mode 100644 esphome/components/ufire_ise/ufire_ise.h diff --git a/CODEOWNERS b/CODEOWNERS index 3a1087191c..69e30027a9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -246,6 +246,7 @@ esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ufire_ec/* @pvizeli +esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core esphome/components/wake_on_lan/* @willwill2will54 diff --git a/esphome/components/ufire_ise/__init__.py b/esphome/components/ufire_ise/__init__.py new file mode 100644 index 0000000000..08f36c7934 --- /dev/null +++ b/esphome/components/ufire_ise/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@pvizeli"] diff --git a/esphome/components/ufire_ise/sensor.py b/esphome/components/ufire_ise/sensor.py new file mode 100644 index 0000000000..8f4359d6af --- /dev/null +++ b/esphome/components/ufire_ise/sensor.py @@ -0,0 +1,127 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PH, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PH, +) + +DEPENDENCIES = ["i2c"] + +CONF_SOLUTION = "solution" +CONF_TEMPERATURE_SENSOR = "temperature_sensor" + +ufire_ise_ns = cg.esphome_ns.namespace("ufire_ise") +UFireISEComponent = ufire_ise_ns.class_( + "UFireISEComponent", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +UFireISECalibrateProbeLowAction = ufire_ise_ns.class_( + "UFireISECalibrateProbeLowAction", automation.Action +) +UFireISECalibrateProbeHighAction = ufire_ise_ns.class_( + "UFireISECalibrateProbeHighAction", automation.Action +) +UFireISEResetAction = ufire_ise_ns.class_("UFireISEResetAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UFireISEComponent), + cv.Exclusive(CONF_TEMPERATURE, "temperature"): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Optional(CONF_PH): sensor.sensor_schema( + unit_of_measurement=UNIT_PH, + icon=ICON_EMPTY, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + cv.Exclusive(CONF_TEMPERATURE_SENSOR, "temperature"): cv.use_id( + sensor.Sensor + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x3F)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_PH in config: + sens = await sensor.new_sensor(config[CONF_PH]) + cg.add(var.set_ph_sensor(sens)) + + if CONF_TEMPERATURE_SENSOR in config: + sens = await cg.get_variable(config[CONF_TEMPERATURE_SENSOR]) + cg.add(var.set_temperature_sensor_external(sens)) + + await i2c.register_i2c_device(var, config) + + +UFIRE_ISE_CALIBRATE_PROBE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(UFireISEComponent), + cv.Required(CONF_SOLUTION): cv.templatable(float), + } +) + + +@automation.register_action( + "ufire_ise.calibrate_probe_low", + UFireISECalibrateProbeLowAction, + UFIRE_ISE_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ise_calibrate_probe_low_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_SOLUTION], args, float) + cg.add(var.set_solution(template_)) + return var + + +@automation.register_action( + "ufire_ise.calibrate_probe_high", + UFireISECalibrateProbeHighAction, + UFIRE_ISE_CALIBRATE_PROBE_SCHEMA, +) +async def ufire_ise_calibrate_probe_high_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_SOLUTION], args, float) + cg.add(var.set_solution(template_)) + return var + + +UFIRE_ISE_RESET_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(UFireISEComponent)}) + + +@automation.register_action( + "ufire_ise.reset", + UFireISEResetAction, + UFIRE_ISE_RESET_SCHEMA, +) +async def ufire_ise_reset_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) + return var diff --git a/esphome/components/ufire_ise/ufire_ise.cpp b/esphome/components/ufire_ise/ufire_ise.cpp new file mode 100644 index 0000000000..957e6f3299 --- /dev/null +++ b/esphome/components/ufire_ise/ufire_ise.cpp @@ -0,0 +1,153 @@ +#include "esphome/core/log.h" +#include "ufire_ise.h" + +#include + +namespace esphome { +namespace ufire_ise { + +static const char *const TAG = "ufire_ise"; + +void UFireISEComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up uFire_ise..."); + + uint8_t version; + if (!this->read_byte(REGISTER_VERSION, &version) && version != 0xFF) { + this->mark_failed(); + return; + } + ESP_LOGI(TAG, "Found uFire_ise board version 0x%02X", version); + + // Write option for temperature adjustments + uint8_t config; + this->read_byte(REGISTER_CONFIG, &config); + if (this->temperature_sensor_ == nullptr && this->temperature_sensor_external_ == nullptr) { + config &= ~CONFIG_TEMP_COMPENSATION; + } else { + config |= CONFIG_TEMP_COMPENSATION; + } + this->write_byte(REGISTER_CONFIG, config); +} + +void UFireISEComponent::update() { + int wait = 0; + if (this->temperature_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_TEMP); + wait += 750; + } + if (this->ph_sensor_ != nullptr) { + this->write_byte(REGISTER_TASK, COMMAND_MEASURE_MV); + wait += 750; + } + + // Wait until measurement are taken + this->set_timeout("data", wait, [this]() { this->update_internal_(); }); +} + +void UFireISEComponent::update_internal_() { + float temperature = 0; + + // Read temperature internal and populate it + if (this->temperature_sensor_ != nullptr) { + temperature = this->measure_temperature_(); + this->temperature_sensor_->publish_state(temperature); + } + // Get temperature from external only for adjustments + else if (this->temperature_sensor_external_ != nullptr) { + temperature = this->temperature_sensor_external_->state; + } + + if (this->ph_sensor_ != nullptr) { + this->ph_sensor_->publish_state(this->measure_ph_(temperature)); + } +} + +float UFireISEComponent::measure_temperature_() { return this->read_data_(REGISTER_TEMP); } + +float UFireISEComponent::measure_mv_() { return this->read_data_(REGISTER_MV); } + +float UFireISEComponent::measure_ph_(float temperature) { + float mv, ph; + + mv = this->measure_mv_(); + if (mv == -1) + return -1; + + ph = fabs(7.0 - (mv / PROBE_MV_TO_PH)); + + // Determine the temperature correction + float distance_from_7 = std::abs(7 - roundf(ph)); + float distance_from_25 = std::floor(std::abs(25 - roundf(temperature)) / 10); + float temp_multiplier = (distance_from_25 * distance_from_7) * PROBE_TMP_CORRECTION; + if ((ph >= 8.0) && (temperature >= 35)) + temp_multiplier *= -1; + if ((ph <= 6.0) && (temperature <= 15)) + temp_multiplier *= -1; + + ph += temp_multiplier; + if ((ph <= 0.0) || (ph > 14.0)) + ph = -1; + if (std::isinf(ph)) + ph = -1; + if (std::isnan(ph)) + ph = -1; + + return ph; +} + +void UFireISEComponent::set_solution_(float solution) { + solution = (7 - solution) * PROBE_MV_TO_PH; + this->write_data_(REGISTER_SOLUTION, solution); +} + +void UFireISEComponent::calibrate_probe_low(float solution) { + this->set_solution_(solution); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_LOW); +} + +void UFireISEComponent::calibrate_probe_high(float solution) { + this->set_solution_(solution); + this->write_byte(REGISTER_TASK, COMMAND_CALIBRATE_HIGH); +} + +void UFireISEComponent::reset_board() { + this->write_data_(REGISTER_REFHIGH, NAN); + this->write_data_(REGISTER_REFLOW, NAN); + this->write_data_(REGISTER_READHIGH, NAN); + this->write_data_(REGISTER_READLOW, NAN); +} + +float UFireISEComponent::read_data_(uint8_t reg) { + float f; + uint8_t temp[4]; + + this->write(®, 1); + delay(10); + + for (uint8_t i = 0; i < 4; i++) { + this->read_bytes_raw(temp + i, 1); + } + memcpy(&f, temp, sizeof(f)); + + return f; +} + +void UFireISEComponent::write_data_(uint8_t reg, float data) { + uint8_t temp[4]; + + memcpy(temp, &data, sizeof(data)); + this->write_bytes(reg, temp, 4); + delay(10); +} + +void UFireISEComponent::dump_config() { + ESP_LOGCONFIG(TAG, "uFire-ISE"); + LOG_I2C_DEVICE(this) + LOG_UPDATE_INTERVAL(this) + LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_) + LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_) + LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_) +} + +} // namespace ufire_ise +} // namespace esphome diff --git a/esphome/components/ufire_ise/ufire_ise.h b/esphome/components/ufire_ise/ufire_ise.h new file mode 100644 index 0000000000..01efdcdb55 --- /dev/null +++ b/esphome/components/ufire_ise/ufire_ise.h @@ -0,0 +1,95 @@ +#pragma once + +#include "esphome/core/automation.h" +#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 ufire_ise { + +static const float PROBE_MV_TO_PH = 59.2; +static const float PROBE_TMP_CORRECTION = 0.03; + +static const uint8_t CONFIG_TEMP_COMPENSATION = 0x02; + +static const uint8_t REGISTER_VERSION = 0; +static const uint8_t REGISTER_MV = 1; +static const uint8_t REGISTER_TEMP = 5; +static const uint8_t REGISTER_REFHIGH = 13; +static const uint8_t REGISTER_REFLOW = 17; +static const uint8_t REGISTER_READHIGH = 21; +static const uint8_t REGISTER_READLOW = 25; +static const uint8_t REGISTER_SOLUTION = 29; +static const uint8_t REGISTER_CONFIG = 38; +static const uint8_t REGISTER_TASK = 39; + +static const uint8_t COMMAND_CALIBRATE_HIGH = 8; +static const uint8_t COMMAND_CALIBRATE_LOW = 10; +static const uint8_t COMMAND_MEASURE_TEMP = 40; +static const uint8_t COMMAND_MEASURE_MV = 80; + +class UFireISEComponent : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } + void set_temperature_sensor_external(sensor::Sensor *temperature_sensor) { + this->temperature_sensor_external_ = temperature_sensor; + } + void set_ph_sensor(sensor::Sensor *ph_sensor) { this->ph_sensor_ = ph_sensor; } + void calibrate_probe_low(float solution); + void calibrate_probe_high(float solution); + void reset_board(); + + protected: + float measure_temperature_(); + float measure_mv_(); + float measure_ph_(float temperature); + void set_solution_(float solution); + float read_data_(uint8_t reg); + void write_data_(uint8_t reg, float data); + void update_internal_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_external_{nullptr}; + sensor::Sensor *ph_sensor_{nullptr}; +}; + +template class UFireISECalibrateProbeLowAction : public Action { + public: + UFireISECalibrateProbeLowAction(UFireISEComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + + void play(Ts... x) override { this->parent_->calibrate_probe_low(this->solution_.value(x...)); } + + protected: + UFireISEComponent *parent_; +}; + +template class UFireISECalibrateProbeHighAction : public Action { + public: + UFireISECalibrateProbeHighAction(UFireISEComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, solution) + + void play(Ts... x) override { this->parent_->calibrate_probe_high(this->solution_.value(x...)); } + + protected: + UFireISEComponent *parent_; +}; + +template class UFireISEResetAction : public Action { + public: + UFireISEResetAction(UFireISEComponent *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->reset_board(); } + + protected: + UFireISEComponent *parent_; +}; + +} // namespace ufire_ise +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 5153efbbe0..c9d46fc82c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -493,6 +493,7 @@ CONF_PAYLOAD = "payload" CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PERIOD = "period" +CONF_PH = "ph" CONF_PHASE_ANGLE = "phase_angle" CONF_PHASE_BALANCER = "phase_balancer" CONF_PIN = "pin" @@ -881,6 +882,7 @@ UNIT_PARTS_PER_BILLION = "ppb" UNIT_PARTS_PER_MILLION = "ppm" UNIT_PASCAL = "Pa" UNIT_PERCENT = "%" +UNIT_PH = "pH" UNIT_PULSES = "pulses" UNIT_PULSES_PER_MINUTE = "pulses/min" UNIT_SECOND = "s" diff --git a/tests/test2.yaml b/tests/test2.yaml index f8ed04d389..5507fa0631 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -376,6 +376,11 @@ sensor: temperature_sensor: ha_hello_world_temperature temperature_compensation: 20.0 temperature_coefficient: 0.019 + - platform: ufire_ise + id: ufire_ise_board + temperature_sensor: ha_hello_world_temperature + ph: + name: Ufire pH time: - platform: homeassistant diff --git a/tests/test4.yaml b/tests/test4.yaml index d79b421cf8..6293e0f7b7 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -251,6 +251,12 @@ sensor: name: Ufire EC temperature_compensation: 20.0 temperature_coefficient: 0.019 + - platform: ufire_ise + id: ufire_ise_board + temperature: + name: Ufire Temperature + ph: + name: Ufire pH # # platform sensor.apds9960 requires component apds9960 From f1e8cc2cf091fe6a3c857d802a3d5d391c1d4886 Mon Sep 17 00:00:00 2001 From: Ignacio Hernandez-Ros Date: Wed, 14 Sep 2022 06:53:51 +0200 Subject: [PATCH 062/838] fix spi timing issues (#3763) --- esphome/components/spi/spi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 7f0b0f481a..6c92321ac8 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,6 +195,11 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { + if (cs != nullptr) { + this->active_cs_ = cs; + this->active_cs_->digital_write(false); + } + #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = SPI_MODE0; @@ -215,11 +220,6 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND } #endif // USE_SPI_ARDUINO_BACKEND - - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } } void disable(); From e557dc7208abda7e1e23ec23c743637491f8fcae Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 Sep 2022 17:02:22 +1200 Subject: [PATCH 063/838] Bump version to 2022.9.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c9d46fc82c..608a61466b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0-dev" +__version__ = "2022.9.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 88943103a2bd8008dc0978465c541c57a8b84c29 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 Sep 2022 17:02:22 +1200 Subject: [PATCH 064/838] Bump version to 2022.10.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c9d46fc82c..22f030e84e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0-dev" +__version__ = "2022.10.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 6a8f4e92df580f5e726003e2a9fd04a3123f435b Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Wed, 14 Sep 2022 15:01:28 +0200 Subject: [PATCH 065/838] null initialize total sensor for pulse counter (#3803) * null initialize total sensor. * pedantic styling fix Co-authored-by: Guillermo Ruffino Co-authored-by: Guillermo Ruffino --- esphome/components/pulse_counter/pulse_counter_sensor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index d9be79e403..ef944f106f 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -81,7 +81,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { PulseCounterStorageBase &storage_; uint32_t last_time_{0}; uint32_t current_total_{0}; - sensor::Sensor *total_sensor_; + sensor::Sensor *total_sensor_{nullptr}; }; } // namespace pulse_counter From aaf50fc2e67c493a8208c4b73d3a00516c023d9e Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 14 Sep 2022 16:43:03 -0300 Subject: [PATCH 066/838] Sim800l add calls, multiline sms and ussd (#3630) Co-authored-by: Matus Ivanecky Co-authored-by: Matus Ivanecky --- esphome/components/sim800l/__init__.py | 109 ++++++++++ esphome/components/sim800l/sim800l.cpp | 282 ++++++++++++++++++++----- esphome/components/sim800l/sim800l.h | 121 +++++++++-- 3 files changed, 442 insertions(+), 70 deletions(-) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 564b685b37..698e3cda9e 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -18,15 +18,42 @@ Sim800LReceivedMessageTrigger = sim800l_ns.class_( "Sim800LReceivedMessageTrigger", automation.Trigger.template(cg.std_string, cg.std_string), ) +Sim800LIncomingCallTrigger = sim800l_ns.class_( + "Sim800LIncomingCallTrigger", + automation.Trigger.template(cg.std_string), +) +Sim800LCallConnectedTrigger = sim800l_ns.class_( + "Sim800LCallConnectedTrigger", + automation.Trigger.template(), +) +Sim800LCallDisconnectedTrigger = sim800l_ns.class_( + "Sim800LCallDisconnectedTrigger", + automation.Trigger.template(), +) + +Sim800LReceivedUssdTrigger = sim800l_ns.class_( + "Sim800LReceivedUssdTrigger", + automation.Trigger.template(cg.std_string), +) # Actions Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) +Sim800LSendUssdAction = sim800l_ns.class_("Sim800LSendUssdAction", automation.Action) Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) +Sim800LConnectAction = sim800l_ns.class_("Sim800LConnectAction", automation.Action) +Sim800LDisconnectAction = sim800l_ns.class_( + "Sim800LDisconnectAction", automation.Action +) CONF_SIM800L_ID = "sim800l_id" CONF_ON_SMS_RECEIVED = "on_sms_received" +CONF_ON_USSD_RECEIVED = "on_ussd_received" +CONF_ON_INCOMING_CALL = "on_incoming_call" +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( cv.Schema( @@ -39,6 +66,34 @@ CONFIG_SCHEMA = cv.All( ), } ), + cv.Optional(CONF_ON_INCOMING_CALL): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LIncomingCallTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_CONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallConnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_DISCONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallDisconnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_USSD_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LReceivedUssdTrigger + ), + } + ), } ) .extend(cv.polling_component_schema("5s")) @@ -59,6 +114,19 @@ async def to_code(config): await automation.build_automation( trigger, [(cg.std_string, "message"), (cg.std_string, "sender")], conf ) + for conf in config.get(CONF_ON_INCOMING_CALL, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "caller_id")], conf) + for conf in config.get(CONF_ON_CALL_CONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CALL_DISCONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_USSD_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "ussd")], conf) SIM800L_SEND_SMS_SCHEMA = cv.Schema( @@ -98,3 +166,44 @@ async def sim800l_dial_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) cg.add(var.set_recipient(template_)) return var + + +@automation.register_action( + "sim800l.connect", + Sim800LConnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_connect_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) + return var + + +SIM800L_SEND_USSD_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_USSD): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action( + "sim800l.send_ussd", Sim800LSendUssdAction, SIM800L_SEND_USSD_SCHEMA +) +async def sim800l_send_ussd_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_USSD], args, cg.std_string) + cg.add(var.set_ussd(template_)) + return var + + +@automation.register_action( + "sim800l.disconnect", + Sim800LDisconnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_disconnect_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) + return var diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index a935978747..3ae5de491a 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -16,20 +16,38 @@ void Sim800LComponent::update() { this->write(26); } + if (this->expect_ack_) + return; + if (state_ == STATE_INIT) { if (this->registered_ && this->send_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); - this->state_ = STATE_SENDINGSMS1; + this->state_ = STATE_SENDING_SMS_1; } else if (this->registered_ && this->dial_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_DIALING1; + } else if (this->registered_ && this->connect_pending_) { + this->connect_pending_ = false; + ESP_LOGI(TAG, "Connecting..."); + this->send_cmd_("ATA"); + this->state_ = STATE_ATA_SENT; + } else if (this->registered_ && this->send_ussd_pending_) { + this->send_cmd_("AT+CSCS=\"GSM\""); + this->state_ = STATE_SEND_USSD1; + } else if (this->registered_ && this->disconnect_pending_) { + this->disconnect_pending_ = false; + ESP_LOGI(TAG, "Disconnecting..."); + this->send_cmd_("ATH"); + } else if (this->registered_ && this->call_state_ != 6) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; + return; } else { this->send_cmd_("AT"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } this->expect_ack_ = true; - } - if (state_ == STATE_RECEIVEDSMS) { + } else if (state_ == STATE_RECEIVED_SMS) { // Serial Buffer should have flushed. // Send cmd to delete received sms char delete_cmd[20]; @@ -49,16 +67,29 @@ void Sim800LComponent::send_cmd_(const std::string &message) { } void Sim800LComponent::parse_cmd_(std::string message) { - ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); - if (message.empty()) return; + ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); + + if (this->state_ != STATE_RECEIVE_SMS) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + this->expect_ack_ = false; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } + } + + bool ok = message == "OK"; if (this->expect_ack_) { - bool ok = message == "OK"; this->expect_ack_ = false; if (!ok) { - if (this->state_ == STATE_CHECK_AT && message == "AT") { + if (this->state_ == STATE_SETUP_CMGF && message == "AT") { // Expected ack but AT echo received this->state_ = STATE_DISABLE_ECHO; this->expect_ack_ = true; @@ -68,6 +99,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { return; } } + } else if (ok && (this->state_ != STATE_PARSE_SMS_RESPONSE && this->state_ != STATE_CHECK_CALL && + this->state_ != STATE_RECEIVE_SMS && this->state_ != STATE_DIALING2)) { + ESP_LOGW(TAG, "Received unexpected OK. Ignoring"); + return; } switch (this->state_) { @@ -75,30 +110,88 @@ void Sim800LComponent::parse_cmd_(std::string message) { // While we were waiting for update to check for messages, this notifies a message // is available. bool message_available = message.compare(0, 6, "+CMTI:") == 0; - if (!message_available) + if (!message_available) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } else if (message.compare(0, 6, "+CUSD:") == 0) { + // Incoming USSD MESSAGE + this->state_ = STATE_CHECK_USSD; + } break; + } + // Else fall thru ... } case STATE_CHECK_SMS: send_cmd_("AT+CMGL=\"ALL\""); - this->state_ = STATE_PARSE_SMS; + this->state_ = STATE_PARSE_SMS_RESPONSE; this->parse_index_ = 0; break; case STATE_DISABLE_ECHO: send_cmd_("ATE0"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; this->expect_ack_ = true; break; - case STATE_CHECK_AT: + case STATE_SETUP_CMGF: send_cmd_("AT+CMGF=1"); + this->state_ = STATE_SETUP_CLIP; + this->expect_ack_ = true; + break; + case STATE_SETUP_CLIP: + send_cmd_("AT+CLIP=1"); this->state_ = STATE_CREG; this->expect_ack_ = true; break; + case STATE_SETUP_USSD: + send_cmd_("AT+CUSD=1"); + this->state_ = STATE_CREG; + this->expect_ack_ = true; + break; + case STATE_SEND_USSD1: + this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\""); + this->state_ = STATE_SEND_USSD2; + break; + case STATE_SEND_USSD2: + ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str()); + if (message == "OK") { + // Dialing + ESP_LOGD(TAG, "Dialing ussd code: '%s' done.", this->ussd_.c_str()); + this->state_ = STATE_CHECK_USSD; + this->send_ussd_pending_ = false; + } else { + this->set_registered_(false); + this->state_ = STATE_INIT; + this->send_cmd_("AT+CMEE=2"); + this->write(26); + } + break; + case STATE_CHECK_USSD: + ESP_LOGD(TAG, "Check ussd code: '%s'", message.c_str()); + if (message.compare(0, 6, "+CUSD:") == 0) { + this->state_ = STATE_RECEIVED_USSD; + this->ussd_ = ""; + size_t start = 10; + size_t end = message.find_last_of(','); + if (end > start) { + this->ussd_ = message.substr(start + 1, end - start - 2); + this->ussd_received_callback_.call(this->ussd_); + } + } + // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS + if (message == "OK") + this->state_ = STATE_INIT; + break; case STATE_CREG: send_cmd_("AT+CREG?"); - this->state_ = STATE_CREGWAIT; + this->state_ = STATE_CREG_WAIT; break; - case STATE_CREGWAIT: { + case STATE_CREG_WAIT: { // Response: "+CREG: 0,1" -- the one there means registered ok // "+CREG: -,-" means not registered ok bool registered = message.compare(0, 6, "+CREG:") == 0 && (message[9] == '1' || message[9] == '5'); @@ -112,10 +205,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message[7] == '0') { // Network registration is disable, enable it send_cmd_("AT+CREG=1"); this->expect_ack_ = true; - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } else { // Keep waiting registration - this->state_ = STATE_CREG; + this->state_ = STATE_INIT; } } set_registered_(registered); @@ -145,9 +238,6 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->expect_ack_ = true; this->state_ = STATE_CHECK_SMS; break; - case STATE_PARSE_SMS: - this->state_ = STATE_PARSE_SMS_RESPONSE; - break; case STATE_PARSE_SMS_RESPONSE: if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) { size_t start = 7; @@ -158,10 +248,11 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (item == 1) { // Slot Index this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } - // item 2 = STATUS, usually "REC UNERAD" + // item 2 = STATUS, usually "REC UNREAD" if (item == 3) { // recipient // Add 1 and remove 2 from substring to get rid of "quotes" this->sender_ = message.substr(start + 1, end - start - 2); + this->message_.clear(); break; } // item 4 = "" @@ -174,42 +265,83 @@ void Sim800LComponent::parse_cmd_(std::string message) { ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); return; } - this->state_ = STATE_RECEIVESMS; + this->state_ = STATE_RECEIVE_SMS; + } + // Otherwise we receive another OK + if (ok) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; } - // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS - if (message == "OK") - this->state_ = STATE_INIT; break; - case STATE_RECEIVESMS: + case STATE_CHECK_CALL: + if (message.compare(0, 6, "+CLCC:") == 0 && this->parse_index_ == 0) { + this->expect_ack_ = true; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + // item 1 call index for +CHLD + // item 2 dir 0 Mobile originated; 1 Mobile terminated + if (item == 3) { // stat + uint8_t current_call_state = parse_number(message.substr(start, end - start)).value_or(6); + if (current_call_state != this->call_state_) { + ESP_LOGD(TAG, "Call state is now: %d", current_call_state); + if (current_call_state == 0) + this->call_connected_callback_.call(); + } + this->call_state_ = current_call_state; + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + + if (item < 2) { + ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); + return; + } + } else if (ok) { + if (this->call_state_ != 6) { + // no call in progress + this->call_state_ = 6; // Disconnect + this->call_disconnected_callback_.call(); + } + } + this->state_ = STATE_INIT; + break; + case STATE_RECEIVE_SMS: /* Our recipient is set and the message body is in message kick ESPHome callback now */ - ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); - ESP_LOGD(TAG, "%s", message.c_str()); - this->callback_.call(message, this->sender_); - /* If the message is multiline, next lines will contain message data. - If there were other messages in the list, next line will be +CMGL: ... - At the end of the list the new line and the OK should be received. - To keep this simple just first line of message if considered, then - the next state will swallow all received data and in next poll event - this message index is marked for deletion. - */ - this->state_ = STATE_RECEIVEDSMS; + if (ok || message.compare(0, 6, "+CMGL:") == 0) { + ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); + ESP_LOGD(TAG, "%s", this->message_.c_str()); + this->sms_received_callback_.call(this->message_, this->sender_); + this->state_ = STATE_RECEIVED_SMS; + } else { + if (this->message_.length() > 0) + this->message_ += "\n"; + this->message_ += message; + } break; - case STATE_RECEIVEDSMS: + case STATE_RECEIVED_SMS: + case STATE_RECEIVED_USSD: // Let the buffer flush. Next poll will request to delete the parsed index message. break; - case STATE_SENDINGSMS1: + case STATE_SENDING_SMS_1: this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\""); - this->state_ = STATE_SENDINGSMS2; + this->state_ = STATE_SENDING_SMS_2; break; - case STATE_SENDINGSMS2: + case STATE_SENDING_SMS_2: if (message == ">") { // Send sms body - ESP_LOGD(TAG, "Sending message: '%s'", this->outgoing_message_.c_str()); + ESP_LOGI(TAG, "Sending to %s message: '%s'", this->recipient_.c_str(), this->outgoing_message_.c_str()); this->write_str(this->outgoing_message_.c_str()); this->write(26); - this->state_ = STATE_SENDINGSMS3; + this->state_ = STATE_SENDING_SMS_3; } else { set_registered_(false); this->state_ = STATE_INIT; @@ -217,7 +349,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->write(26); } break; - case STATE_SENDINGSMS3: + case STATE_SENDING_SMS_3: if (message.compare(0, 6, "+CMGS:") == 0) { ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str()); this->send_pending_ = false; @@ -230,23 +362,55 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_DIALING2; break; case STATE_DIALING2: - if (message == "OK") { - // Dialing - ESP_LOGD(TAG, "Dialing: '%s'", this->recipient_.c_str()); - this->state_ = STATE_INIT; + if (ok) { + ESP_LOGI(TAG, "Dialing: '%s'", this->recipient_.c_str()); this->dial_pending_ = false; } else { this->set_registered_(false); - this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); } + this->state_ = STATE_INIT; + break; + case STATE_PARSE_CLIP: + if (message.compare(0, 6, "+CLIP:") == 0) { + std::string caller_id; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + if (item == 1) { // Slot Index + // Add 1 and remove 2 from substring to get rid of "quotes" + caller_id = message.substr(start + 1, end - start - 2); + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + if (this->call_state_ != 4) { + this->call_state_ = 4; + ESP_LOGI(TAG, "Incoming call from %s", caller_id.c_str()); + incoming_call_callback_.call(caller_id); + } + this->state_ = STATE_INIT; + } + break; + case STATE_ATA_SENT: + ESP_LOGI(TAG, "Call connected"); + if (this->call_state_ != 0) { + this->call_state_ = 0; + this->call_connected_callback_.call(); + } + this->state_ = STATE_INIT; break; default: - ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); + ESP_LOGW(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); break; } -} +} // namespace sim800l void Sim800LComponent::loop() { // Read message @@ -265,7 +429,7 @@ void Sim800LComponent::loop() { byte = '?'; // need to be valid utf8 string for log functions. this->read_buffer_[this->read_pos_] = byte; - if (this->state_ == STATE_SENDINGSMS2 && this->read_pos_ == 0 && byte == '>') + if (this->state_ == STATE_SENDING_SMS_2 && this->read_pos_ == 0 && byte == '>') this->read_buffer_[++this->read_pos_] = ASCII_LF; if (this->read_buffer_[this->read_pos_] == ASCII_LF) { @@ -276,13 +440,23 @@ void Sim800LComponent::loop() { this->read_pos_++; } } + if (state_ == STATE_INIT && this->registered_ && + (this->call_state_ != 6 // A call is in progress + || this->send_pending_ || this->dial_pending_ || this->connect_pending_ || this->disconnect_pending_)) { + this->update(); + } } void Sim800LComponent::send_sms(const std::string &recipient, const std::string &message) { - ESP_LOGD(TAG, "Sending to %s: %s", recipient.c_str(), message.c_str()); this->recipient_ = recipient; this->outgoing_message_ = message; this->send_pending_ = true; +} + +void Sim800LComponent::send_ussd(const std::string &ussd_code) { + ESP_LOGD(TAG, "Sending USSD code: %s", ussd_code.c_str()); + this->ussd_ = ussd_code; + this->send_ussd_pending_ = true; this->update(); } void Sim800LComponent::dump_config() { @@ -295,11 +469,11 @@ void Sim800LComponent::dump_config() { #endif } void Sim800LComponent::dial(const std::string &recipient) { - ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); this->recipient_ = recipient; this->dial_pending_ = true; - this->update(); } +void Sim800LComponent::connect() { this->connect_pending_ = true; } +void Sim800LComponent::disconnect() { this->disconnect_pending_ = true; } void Sim800LComponent::set_registered_(bool registered) { this->registered_ = registered; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 3535b96283..bf7efd6915 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -16,31 +16,35 @@ namespace esphome { namespace sim800l { -const uint8_t SIM800L_READ_BUFFER_LENGTH = 255; +const uint16_t SIM800L_READ_BUFFER_LENGTH = 1024; enum State { STATE_IDLE = 0, STATE_INIT, - STATE_CHECK_AT, + STATE_SETUP_CMGF, + STATE_SETUP_CLIP, STATE_CREG, - STATE_CREGWAIT, + STATE_CREG_WAIT, STATE_CSQ, STATE_CSQ_RESPONSE, - STATE_IDLEWAIT, - STATE_SENDINGSMS1, - STATE_SENDINGSMS2, - STATE_SENDINGSMS3, + STATE_SENDING_SMS_1, + STATE_SENDING_SMS_2, + STATE_SENDING_SMS_3, STATE_CHECK_SMS, - STATE_PARSE_SMS, STATE_PARSE_SMS_RESPONSE, - STATE_RECEIVESMS, - STATE_READSMS, - STATE_RECEIVEDSMS, - STATE_DELETEDSMS, + STATE_RECEIVE_SMS, + STATE_RECEIVED_SMS, STATE_DISABLE_ECHO, - STATE_PARSE_SMS_OK, STATE_DIALING1, - STATE_DIALING2 + STATE_DIALING2, + STATE_PARSE_CLIP, + STATE_ATA_SENT, + STATE_CHECK_CALL, + STATE_SETUP_USSD, + STATE_SEND_USSD1, + STATE_SEND_USSD2, + STATE_CHECK_USSD, + STATE_RECEIVED_USSD }; class Sim800LComponent : public uart::UARTDevice, public PollingComponent { @@ -58,10 +62,25 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } #endif void add_on_sms_received_callback(std::function callback) { - this->callback_.add(std::move(callback)); + this->sms_received_callback_.add(std::move(callback)); + } + void add_on_incoming_call_callback(std::function callback) { + this->incoming_call_callback_.add(std::move(callback)); + } + void add_on_call_connected_callback(std::function callback) { + this->call_connected_callback_.add(std::move(callback)); + } + void add_on_call_disconnected_callback(std::function callback) { + this->call_disconnected_callback_.add(std::move(callback)); + } + void add_on_ussd_received_callback(std::function callback) { + this->ussd_received_callback_.add(std::move(callback)); } void send_sms(const std::string &recipient, const std::string &message); + void send_ussd(const std::string &ussd_code); void dial(const std::string &recipient); + void connect(); + void disconnect(); protected: void send_cmd_(const std::string &message); @@ -76,6 +95,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { sensor::Sensor *rssi_sensor_{nullptr}; #endif std::string sender_; + std::string message_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; size_t read_pos_{0}; uint8_t parse_index_{0}; @@ -86,10 +106,19 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { std::string recipient_; std::string outgoing_message_; + std::string ussd_; bool send_pending_; bool dial_pending_; + bool connect_pending_; + bool disconnect_pending_; + bool send_ussd_pending_; + uint8_t call_state_{6}; - CallbackManager callback_; + CallbackManager sms_received_callback_; + CallbackManager incoming_call_callback_; + CallbackManager call_connected_callback_; + CallbackManager call_disconnected_callback_; + CallbackManager ussd_received_callback_; }; class Sim800LReceivedMessageTrigger : public Trigger { @@ -100,6 +129,33 @@ class Sim800LReceivedMessageTrigger : public Trigger { } }; +class Sim800LIncomingCallTrigger : public Trigger { + public: + explicit Sim800LIncomingCallTrigger(Sim800LComponent *parent) { + parent->add_on_incoming_call_callback([this](const std::string &caller_id) { this->trigger(caller_id); }); + } +}; + +class Sim800LCallConnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallConnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_connected_callback([this]() { this->trigger(); }); + } +}; + +class Sim800LCallDisconnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallDisconnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_disconnected_callback([this]() { this->trigger(); }); + } +}; +class Sim800LReceivedUssdTrigger : public Trigger { + public: + explicit Sim800LReceivedUssdTrigger(Sim800LComponent *parent) { + parent->add_on_ussd_received_callback([this](const std::string &ussd) { this->trigger(ussd); }); + } +}; + template class Sim800LSendSmsAction : public Action { public: Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} @@ -116,6 +172,20 @@ template class Sim800LSendSmsAction : public Action { Sim800LComponent *parent_; }; +template class Sim800LSendUssdAction : public Action { + public: + Sim800LSendUssdAction(Sim800LComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, ussd) + + void play(Ts... x) { + auto ussd_code = this->ussd_.value(x...); + this->parent_->send_ussd(ussd_code); + } + + protected: + Sim800LComponent *parent_; +}; + template class Sim800LDialAction : public Action { public: Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {} @@ -129,6 +199,25 @@ template class Sim800LDialAction : public Action { protected: Sim800LComponent *parent_; }; +template class Sim800LConnectAction : public Action { + public: + Sim800LConnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->connect(); } + + protected: + Sim800LComponent *parent_; +}; + +template class Sim800LDisconnectAction : public Action { + public: + Sim800LDisconnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->disconnect(); } + + protected: + Sim800LComponent *parent_; +}; } // namespace sim800l } // namespace esphome From 78b55d86e96ba72848379f75e889bc268122d41b Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Thu, 15 Sep 2022 01:53:02 +0200 Subject: [PATCH 067/838] Unify 'nullptr' initalization of class members; (#3805) --- esphome/components/ade7953/ade7953.h | 2 +- esphome/components/api/api_frame_helper.h | 6 +++--- esphome/components/hydreon_rgxx/hydreon_rgxx.h | 6 +++--- esphome/components/mlx90393/sensor_mlx90393.h | 2 +- esphome/components/pid/pid_climate.h | 4 ++-- esphome/components/pulse_meter/pulse_meter_sensor.h | 4 ++-- .../components/pvvx_mithermometer/display/pvvx_display.h | 2 +- esphome/core/automation.h | 2 +- esphome/core/component.h | 2 +- esphome/core/gpio.h | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index bb160cd8eb..418ad1c9e7 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -82,7 +82,7 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { return i2c::ERROR_OK; } - InternalGPIOPin *irq_pin_ = nullptr; + InternalGPIOPin *irq_pin_{nullptr}; bool is_setup_{false}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_a_sensor_{nullptr}; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 57e3c961d5..348a9b574f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -116,9 +116,9 @@ class APINoiseFrameHelper : public APIFrameHelper { std::vector prologue_; std::shared_ptr ctx_; - NoiseHandshakeState *handshake_ = nullptr; - NoiseCipherState *send_cipher_ = nullptr; - NoiseCipherState *recv_cipher_ = nullptr; + NoiseHandshakeState *handshake_{nullptr}; + NoiseCipherState *send_cipher_{nullptr}; + NoiseCipherState *recv_cipher_{nullptr}; NoiseProtocolId nid_; enum class State { diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index 1697060d28..34b9bd8d5e 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -58,9 +58,9 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; #ifdef USE_BINARY_SENSOR - binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; - binary_sensor::BinarySensor *lens_bad_sensor_ = nullptr; - binary_sensor::BinarySensor *em_sat_sensor_ = nullptr; + binary_sensor::BinarySensor *too_cold_sensor_{nullptr}; + binary_sensor::BinarySensor *lens_bad_sensor_{nullptr}; + binary_sensor::BinarySensor *em_sat_sensor_{nullptr}; #endif int16_t boot_count_ = 0; diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h index fc33ad1aa8..8dfb7e6a13 100644 --- a/esphome/components/mlx90393/sensor_mlx90393.h +++ b/esphome/components/mlx90393/sensor_mlx90393.h @@ -52,7 +52,7 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90 uint8_t temperature_oversampling_ = 0; uint8_t filter_; uint8_t resolutions_[3] = {0}; - GPIOPin *drdy_pin_ = nullptr; + GPIOPin *drdy_pin_{nullptr}; }; } // namespace mlx90393 diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index ff301386b6..095c00eb49 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -59,8 +59,8 @@ class PIDClimate : public climate::Climate, public Component { /// The sensor used for getting the current temperature sensor::Sensor *sensor_; - output::FloatOutput *cool_output_ = nullptr; - output::FloatOutput *heat_output_ = nullptr; + output::FloatOutput *cool_output_{nullptr}; + output::FloatOutput *heat_output_{nullptr}; PIDController controller_; /// Output value as reported by the PID controller, for PIDClimateSensor float output_value_; diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index cf08f8c92d..bf50eab6ff 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -31,11 +31,11 @@ class PulseMeterSensor : public sensor::Sensor, public Component { protected: static void gpio_intr(PulseMeterSensor *sensor); - InternalGPIOPin *pin_ = nullptr; + InternalGPIOPin *pin_{nullptr}; ISRInternalGPIOPin isr_pin_; uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; - sensor::Sensor *total_sensor_ = nullptr; + sensor::Sensor *total_sensor_{nullptr}; InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index 4de1ab7ba6..c7e7cc04fb 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -114,7 +114,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { void delayed_disconnect_(); #ifdef USE_TIME void sync_time_(); - time::RealTimeClock *time_ = nullptr; + time::RealTimeClock *time_{nullptr}; #endif uint16_t char_handle_ = 0; bool connection_established_ = false; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 92bc32247b..84c754e081 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -176,7 +176,7 @@ template class Action { return this->next_->is_running(); } - Action *next_ = nullptr; + Action *next_{nullptr}; /// The number of instances of this sequence in the list of actions /// that is currently being executed. diff --git a/esphome/core/component.h b/esphome/core/component.h index e394736653..cb97a93d21 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -254,7 +254,7 @@ class Component { uint32_t component_state_{0x0000}; ///< State of this component. float setup_priority_override_{NAN}; - const char *component_source_ = nullptr; + const char *component_source_{nullptr}; }; /** This class simplifies creating components that periodically check a state. diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index b953a95664..1b6f2ba1e6 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -73,7 +73,7 @@ class ISRInternalGPIOPin { void pin_mode(gpio::Flags flags); protected: - void *arg_ = nullptr; + void *arg_{nullptr}; }; class InternalGPIOPin : public GPIOPin { From 0ac4c055de90030d35d9fab704db06ee473da30c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:53:22 +1200 Subject: [PATCH 068/838] Initialize all child sensors to nullptr (#3808) --- esphome/components/aht10/aht10.h | 4 ++-- esphome/components/am2320/am2320.h | 4 ++-- esphome/components/as3935/as3935.h | 6 +++--- esphome/components/bl0939/bl0939.h | 16 ++++++++-------- esphome/components/bl0940/bl0940.h | 12 ++++++------ esphome/components/bl0942/bl0942.h | 10 +++++----- esphome/components/bme280/bme280.h | 6 +++--- esphome/components/bme680/bme680.h | 8 ++++---- esphome/components/bme680_bsec/bme680_bsec.h | 18 +++++++++--------- esphome/components/bmp280/bmp280.h | 4 ++-- esphome/components/bmp3xx/bmp3xx.h | 4 ++-- esphome/components/dht12/dht12.h | 4 ++-- esphome/components/dps310/dps310.h | 4 ++-- esphome/components/ens210/ens210.h | 4 ++-- esphome/components/hdc1080/hdc1080.h | 4 ++-- esphome/components/hmc5883l/hmc5883l.h | 8 ++++---- esphome/components/honeywellabp/honeywellabp.h | 4 ++-- esphome/components/ms5611/ms5611.h | 4 ++-- esphome/components/pzem004t/pzem004t.h | 8 ++++---- esphome/components/pzemac/pzemac.h | 12 ++++++------ esphome/components/pzemdc/pzemdc.h | 10 +++++----- esphome/components/qmc5883l/qmc5883l.h | 8 ++++---- esphome/components/qmp6988/qmp6988.h | 4 ++-- esphome/components/sht3xd/sht3xd.h | 4 ++-- esphome/components/shtcx/shtcx.h | 4 ++-- esphome/components/tsl2591/tsl2591.h | 8 ++++---- esphome/components/tx20/tx20.h | 4 ++-- 27 files changed, 93 insertions(+), 93 deletions(-) diff --git a/esphome/components/aht10/aht10.h b/esphome/components/aht10/aht10.h index bfb6b07a7a..4d0eaa5919 100644 --- a/esphome/components/aht10/aht10.h +++ b/esphome/components/aht10/aht10.h @@ -18,8 +18,8 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice { void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace aht10 diff --git a/esphome/components/am2320/am2320.h b/esphome/components/am2320/am2320.h index 33e1d30aa0..da1e87cf65 100644 --- a/esphome/components/am2320/am2320.h +++ b/esphome/components/am2320/am2320.h @@ -21,8 +21,8 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint8_t *data); bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace am2320 diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 2e65aab4d1..2cba9b11a0 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -92,9 +92,9 @@ class AS3935Component : public Component { virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; - sensor::Sensor *distance_sensor_; - sensor::Sensor *energy_sensor_; - binary_sensor::BinarySensor *thunder_alert_binary_sensor_; + sensor::Sensor *distance_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; GPIOPin *irq_pin_; bool indoor_; diff --git a/esphome/components/bl0939/bl0939.h b/esphome/components/bl0939/bl0939.h index d3dab67cc1..673d4ff351 100644 --- a/esphome/components/bl0939/bl0939.h +++ b/esphome/components/bl0939/bl0939.h @@ -75,16 +75,16 @@ class BL0939 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_1_; - sensor::Sensor *current_sensor_2_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_1_; - sensor::Sensor *power_sensor_2_; - sensor::Sensor *energy_sensor_1_; - sensor::Sensor *energy_sensor_2_; - sensor::Sensor *energy_sensor_sum_; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_1_{nullptr}; + sensor::Sensor *energy_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_sum_{nullptr}; // Divide by this to turn into Watt float power_reference_ = BL0939_PREF; diff --git a/esphome/components/bl0940/bl0940.h b/esphome/components/bl0940/bl0940.h index 49c8e50595..2d4e7ccaac 100644 --- a/esphome/components/bl0940/bl0940.h +++ b/esphome/components/bl0940/bl0940.h @@ -75,14 +75,14 @@ class BL0940 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *internal_temperature_sensor_; - sensor::Sensor *external_temperature_sensor_; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *internal_temperature_sensor_{nullptr}; + sensor::Sensor *external_temperature_sensor_{nullptr}; // Max difference between two measurements of the temperature. Used to avoid noise. float max_temperature_diff_{0}; diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h index 8149b7493b..12489915e1 100644 --- a/esphome/components/bl0942/bl0942.h +++ b/esphome/components/bl0942/bl0942.h @@ -43,13 +43,13 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *frequency_sensor_; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; // Divide by this to turn into Watt float power_reference_ = BL0942_PREF; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 8511f73382..50d398c40f 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -96,9 +96,9 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X}; BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X}; BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bme680/bme680.h b/esphome/components/bme680/bme680.h index 0671cd990e..6446449742 100644 --- a/esphome/components/bme680/bme680.h +++ b/esphome/components/bme680/bme680.h @@ -129,10 +129,10 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice { uint16_t heater_temperature_{320}; uint16_t heater_duration_{150}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; }; } // namespace bme680 diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 650b4d2413..6fe8f8fef7 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -100,15 +100,15 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; - sensor::Sensor *iaq_sensor_; - text_sensor::TextSensor *iaq_accuracy_text_sensor_; - sensor::Sensor *iaq_accuracy_sensor_; - sensor::Sensor *co2_equivalent_sensor_; - sensor::Sensor *breath_voc_equivalent_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; + sensor::Sensor *iaq_sensor_{nullptr}; + text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr}; + sensor::Sensor *iaq_accuracy_sensor_{nullptr}; + sensor::Sensor *co2_equivalent_sensor_{nullptr}; + sensor::Sensor *breath_voc_equivalent_sensor_{nullptr}; }; #endif } // namespace bme680_bsec diff --git a/esphome/components/bmp280/bmp280.h b/esphome/components/bmp280/bmp280.h index f8646fb547..96eb470155 100644 --- a/esphome/components/bmp280/bmp280.h +++ b/esphome/components/bmp280/bmp280.h @@ -81,8 +81,8 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { BMP280Oversampling temperature_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280Oversampling pressure_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280IIRFilter iir_filter_{BMP280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bmp3xx/bmp3xx.h b/esphome/components/bmp3xx/bmp3xx.h index ab20abfe9b..d3b15f601d 100644 --- a/esphome/components/bmp3xx/bmp3xx.h +++ b/esphome/components/bmp3xx/bmp3xx.h @@ -125,8 +125,8 @@ class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { Oversampling pressure_oversampling_{OVERSAMPLING_X16}; IIRFilter iir_filter_{IIR_FILTER_OFF}; OperationMode operation_mode_{FORCED_MODE}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, ERROR_COMMUNICATION_FAILED, diff --git a/esphome/components/dht12/dht12.h b/esphome/components/dht12/dht12.h index ae4d4fd607..2a706039ba 100644 --- a/esphome/components/dht12/dht12.h +++ b/esphome/components/dht12/dht12.h @@ -20,8 +20,8 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice { protected: bool read_data_(uint8_t *data); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace dht12 diff --git a/esphome/components/dps310/dps310.h b/esphome/components/dps310/dps310.h index 7aca2c3d10..50e7d93c8a 100644 --- a/esphome/components/dps310/dps310.h +++ b/esphome/components/dps310/dps310.h @@ -53,8 +53,8 @@ class DPS310Component : public PollingComponent, public i2c::I2CDevice { void calculate_values_(int32_t raw_temperature, int32_t raw_pressure); static int32_t twos_complement(int32_t val, uint8_t bits); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; int32_t raw_pressure_, raw_temperature_, c00_, c10_; int16_t c0_, c1_, c01_, c11_, c20_, c21_, c30_; uint8_t prod_rev_id_; diff --git a/esphome/components/ens210/ens210.h b/esphome/components/ens210/ens210.h index 342be04799..0fb6ff634d 100644 --- a/esphome/components/ens210/ens210.h +++ b/esphome/components/ens210/ens210.h @@ -31,8 +31,8 @@ class ENS210Component : public PollingComponent, public i2c::I2CDevice { bool set_low_power_(bool enable); void extract_measurement_(uint32_t val, int *data, int *status); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace ens210 diff --git a/esphome/components/hdc1080/hdc1080.h b/esphome/components/hdc1080/hdc1080.h index 9cb87cdb8b..2ff7b6dc33 100644 --- a/esphome/components/hdc1080/hdc1080.h +++ b/esphome/components/hdc1080/hdc1080.h @@ -21,8 +21,8 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - sensor::Sensor *temperature_; - sensor::Sensor *humidity_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; }; } // namespace hdc1080 diff --git a/esphome/components/hmc5883l/hmc5883l.h b/esphome/components/hmc5883l/hmc5883l.h index 41d41baa22..3481f45dc8 100644 --- a/esphome/components/hmc5883l/hmc5883l.h +++ b/esphome/components/hmc5883l/hmc5883l.h @@ -54,10 +54,10 @@ class HMC5883LComponent : public PollingComponent, public i2c::I2CDevice { HMC5883LOversampling oversampling_{HMC5883L_OVERSAMPLING_1}; HMC5883LDatarate datarate_{HMC5883L_DATARATE_15_0_HZ}; HMC5883LRange range_{HMC5883L_RANGE_130_UT}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/honeywellabp/honeywellabp.h b/esphome/components/honeywellabp/honeywellabp.h index 44d5952ca6..98f6f08c4a 100644 --- a/esphome/components/honeywellabp/honeywellabp.h +++ b/esphome/components/honeywellabp/honeywellabp.h @@ -29,8 +29,8 @@ class HONEYWELLABPSensor : public PollingComponent, uint8_t status_ = 0; // byte to hold status information. int pressure_count_ = 0; // hold raw pressure data (14 - bit, 0 - 16384) int temperature_count_ = 0; // hold raw temperature data (11 - bit, 0 - 2048) - sensor::Sensor *pressure_sensor_; - sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; uint8_t readsensor_(); uint8_t readstatus_(); int rawpressure_(); diff --git a/esphome/components/ms5611/ms5611.h b/esphome/components/ms5611/ms5611.h index b5663ad736..476db79612 100644 --- a/esphome/components/ms5611/ms5611.h +++ b/esphome/components/ms5611/ms5611.h @@ -22,8 +22,8 @@ class MS5611Component : public PollingComponent, public i2c::I2CDevice { void read_pressure_(uint32_t raw_temperature); void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; uint16_t prom_[6]; }; diff --git a/esphome/components/pzem004t/pzem004t.h b/esphome/components/pzem004t/pzem004t.h index f4f9f29b4d..e18413f35c 100644 --- a/esphome/components/pzem004t/pzem004t.h +++ b/esphome/components/pzem004t/pzem004t.h @@ -23,10 +23,10 @@ class PZEM004T : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; enum PZEM004TReadState { SET_ADDRESS = 0xB4, diff --git a/esphome/components/pzemac/pzemac.h b/esphome/components/pzemac/pzemac.h index e9f76972a3..8f2cf1460d 100644 --- a/esphome/components/pzemac/pzemac.h +++ b/esphome/components/pzemac/pzemac.h @@ -27,12 +27,12 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice { protected: template friend class ResetEnergyAction; - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; void reset_energy_(); }; diff --git a/esphome/components/pzemdc/pzemdc.h b/esphome/components/pzemdc/pzemdc.h index d838eb4167..a78a48a6fb 100644 --- a/esphome/components/pzemdc/pzemdc.h +++ b/esphome/components/pzemdc/pzemdc.h @@ -22,11 +22,11 @@ class PZEMDC : public PollingComponent, public modbus::ModbusDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; }; } // namespace pzemdc diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index 01697ecbd0..15ef435ce5 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -45,10 +45,10 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; QMC5883LRange range_{QMC5883L_RANGE_200_UT}; QMC5883LOversampling oversampling_{QMC5883L_SAMPLING_512}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/qmp6988/qmp6988.h b/esphome/components/qmp6988/qmp6988.h index ef944ba4ff..f0c11adf43 100644 --- a/esphome/components/qmp6988/qmp6988.h +++ b/esphome/components/qmp6988/qmp6988.h @@ -91,8 +91,8 @@ class QMP6988Component : public PollingComponent, public i2c::I2CDevice { protected: qmp6988_data_t qmp6988_data_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X}; QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X}; diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 3164aa0687..41ca3c5d6e 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -19,8 +19,8 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir void update() override; protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace sht3xd diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index c44fb9d9c1..084d3bfc35 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -26,8 +26,8 @@ class SHTCXComponent : public PollingComponent, public sensirion_common::Sensiri protected: SHTCXType type_; uint16_t sensor_id_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace shtcx diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d82dbc395f..5b7eea35ec 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -245,10 +245,10 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { protected: const char *name_; - sensor::Sensor *full_spectrum_sensor_; - sensor::Sensor *infrared_sensor_; - sensor::Sensor *visible_sensor_; - sensor::Sensor *calculated_lux_sensor_; + sensor::Sensor *full_spectrum_sensor_{nullptr}; + sensor::Sensor *infrared_sensor_{nullptr}; + sensor::Sensor *visible_sensor_{nullptr}; + sensor::Sensor *calculated_lux_sensor_{nullptr}; TSL2591IntegrationTime integration_time_; TSL2591ComponentGain component_gain_; TSL2591Gain gain_; diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 1c617d0674..95a9517227 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -43,8 +43,8 @@ class Tx20Component : public Component { std::string wind_cardinal_direction_; InternalGPIOPin *pin_; - sensor::Sensor *wind_speed_sensor_; - sensor::Sensor *wind_direction_degrees_sensor_; + sensor::Sensor *wind_speed_sensor_{nullptr}; + sensor::Sensor *wind_direction_degrees_sensor_{nullptr}; Tx20ComponentStore store_; }; From 917bbc669c517070b5f8cf4fba9335755165ca74 Mon Sep 17 00:00:00 2001 From: Azimath Date: Wed, 14 Sep 2022 19:54:33 -0400 Subject: [PATCH 069/838] Remove floating point calculation from ac_dimmer ISR (#3770) --- esphome/components/ac_dimmer/ac_dimmer.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 1d0cd8d0ab..16101a1c2c 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -121,11 +121,8 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - // calculate required value to provide a true RMS voltage output - this->enable_time_us = - std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) * - (this->cycle_time_us - min_us)) / - 65535); + this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% @@ -206,6 +203,7 @@ void AcDimmer::setup() { #endif } void AcDimmer::write_state(float state) { + state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation auto new_value = static_cast(roundf(state * 65535)); if (new_value != 0 && this->store_.value == 0) this->store_.init_cycle = this->init_with_half_cycle_; From c61abf6aca6be646320bd39d1b8fd7cdf348e339 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Wed, 14 Sep 2022 15:01:28 +0200 Subject: [PATCH 070/838] null initialize total sensor for pulse counter (#3803) * null initialize total sensor. * pedantic styling fix Co-authored-by: Guillermo Ruffino Co-authored-by: Guillermo Ruffino --- esphome/components/pulse_counter/pulse_counter_sensor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index d9be79e403..ef944f106f 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -81,7 +81,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { PulseCounterStorageBase &storage_; uint32_t last_time_{0}; uint32_t current_total_{0}; - sensor::Sensor *total_sensor_; + sensor::Sensor *total_sensor_{nullptr}; }; } // namespace pulse_counter From cea7deab912f664635cb28678690c72fc452c80d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 14 Sep 2022 16:43:03 -0300 Subject: [PATCH 071/838] Sim800l add calls, multiline sms and ussd (#3630) Co-authored-by: Matus Ivanecky Co-authored-by: Matus Ivanecky --- esphome/components/sim800l/__init__.py | 109 ++++++++++ esphome/components/sim800l/sim800l.cpp | 282 ++++++++++++++++++++----- esphome/components/sim800l/sim800l.h | 121 +++++++++-- 3 files changed, 442 insertions(+), 70 deletions(-) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 564b685b37..698e3cda9e 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -18,15 +18,42 @@ Sim800LReceivedMessageTrigger = sim800l_ns.class_( "Sim800LReceivedMessageTrigger", automation.Trigger.template(cg.std_string, cg.std_string), ) +Sim800LIncomingCallTrigger = sim800l_ns.class_( + "Sim800LIncomingCallTrigger", + automation.Trigger.template(cg.std_string), +) +Sim800LCallConnectedTrigger = sim800l_ns.class_( + "Sim800LCallConnectedTrigger", + automation.Trigger.template(), +) +Sim800LCallDisconnectedTrigger = sim800l_ns.class_( + "Sim800LCallDisconnectedTrigger", + automation.Trigger.template(), +) + +Sim800LReceivedUssdTrigger = sim800l_ns.class_( + "Sim800LReceivedUssdTrigger", + automation.Trigger.template(cg.std_string), +) # Actions Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) +Sim800LSendUssdAction = sim800l_ns.class_("Sim800LSendUssdAction", automation.Action) Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) +Sim800LConnectAction = sim800l_ns.class_("Sim800LConnectAction", automation.Action) +Sim800LDisconnectAction = sim800l_ns.class_( + "Sim800LDisconnectAction", automation.Action +) CONF_SIM800L_ID = "sim800l_id" CONF_ON_SMS_RECEIVED = "on_sms_received" +CONF_ON_USSD_RECEIVED = "on_ussd_received" +CONF_ON_INCOMING_CALL = "on_incoming_call" +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( cv.Schema( @@ -39,6 +66,34 @@ CONFIG_SCHEMA = cv.All( ), } ), + cv.Optional(CONF_ON_INCOMING_CALL): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LIncomingCallTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_CONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallConnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_CALL_DISCONNECTED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LCallDisconnectedTrigger + ), + } + ), + cv.Optional(CONF_ON_USSD_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LReceivedUssdTrigger + ), + } + ), } ) .extend(cv.polling_component_schema("5s")) @@ -59,6 +114,19 @@ async def to_code(config): await automation.build_automation( trigger, [(cg.std_string, "message"), (cg.std_string, "sender")], conf ) + for conf in config.get(CONF_ON_INCOMING_CALL, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "caller_id")], conf) + for conf in config.get(CONF_ON_CALL_CONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CALL_DISCONNECTED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_USSD_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.std_string, "ussd")], conf) SIM800L_SEND_SMS_SCHEMA = cv.Schema( @@ -98,3 +166,44 @@ async def sim800l_dial_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) cg.add(var.set_recipient(template_)) return var + + +@automation.register_action( + "sim800l.connect", + Sim800LConnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_connect_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) + return var + + +SIM800L_SEND_USSD_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_USSD): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action( + "sim800l.send_ussd", Sim800LSendUssdAction, SIM800L_SEND_USSD_SCHEMA +) +async def sim800l_send_ussd_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_USSD], args, cg.std_string) + cg.add(var.set_ussd(template_)) + return var + + +@automation.register_action( + "sim800l.disconnect", + Sim800LDisconnectAction, + cv.Schema({cv.GenerateID(): cv.use_id(Sim800LComponent)}), +) +async def sim800l_disconnect_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) + return var diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index a935978747..3ae5de491a 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -16,20 +16,38 @@ void Sim800LComponent::update() { this->write(26); } + if (this->expect_ack_) + return; + if (state_ == STATE_INIT) { if (this->registered_ && this->send_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); - this->state_ = STATE_SENDINGSMS1; + this->state_ = STATE_SENDING_SMS_1; } else if (this->registered_ && this->dial_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_DIALING1; + } else if (this->registered_ && this->connect_pending_) { + this->connect_pending_ = false; + ESP_LOGI(TAG, "Connecting..."); + this->send_cmd_("ATA"); + this->state_ = STATE_ATA_SENT; + } else if (this->registered_ && this->send_ussd_pending_) { + this->send_cmd_("AT+CSCS=\"GSM\""); + this->state_ = STATE_SEND_USSD1; + } else if (this->registered_ && this->disconnect_pending_) { + this->disconnect_pending_ = false; + ESP_LOGI(TAG, "Disconnecting..."); + this->send_cmd_("ATH"); + } else if (this->registered_ && this->call_state_ != 6) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; + return; } else { this->send_cmd_("AT"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } this->expect_ack_ = true; - } - if (state_ == STATE_RECEIVEDSMS) { + } else if (state_ == STATE_RECEIVED_SMS) { // Serial Buffer should have flushed. // Send cmd to delete received sms char delete_cmd[20]; @@ -49,16 +67,29 @@ void Sim800LComponent::send_cmd_(const std::string &message) { } void Sim800LComponent::parse_cmd_(std::string message) { - ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); - if (message.empty()) return; + ESP_LOGV(TAG, "R: %s - %d", message.c_str(), this->state_); + + if (this->state_ != STATE_RECEIVE_SMS) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + this->expect_ack_ = false; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } + } + + bool ok = message == "OK"; if (this->expect_ack_) { - bool ok = message == "OK"; this->expect_ack_ = false; if (!ok) { - if (this->state_ == STATE_CHECK_AT && message == "AT") { + if (this->state_ == STATE_SETUP_CMGF && message == "AT") { // Expected ack but AT echo received this->state_ = STATE_DISABLE_ECHO; this->expect_ack_ = true; @@ -68,6 +99,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { return; } } + } else if (ok && (this->state_ != STATE_PARSE_SMS_RESPONSE && this->state_ != STATE_CHECK_CALL && + this->state_ != STATE_RECEIVE_SMS && this->state_ != STATE_DIALING2)) { + ESP_LOGW(TAG, "Received unexpected OK. Ignoring"); + return; } switch (this->state_) { @@ -75,30 +110,88 @@ void Sim800LComponent::parse_cmd_(std::string message) { // While we were waiting for update to check for messages, this notifies a message // is available. bool message_available = message.compare(0, 6, "+CMTI:") == 0; - if (!message_available) + if (!message_available) { + if (message == "RING") { + // Incoming call... + this->state_ = STATE_PARSE_CLIP; + } else if (message == "NO CARRIER") { + if (this->call_state_ != 6) { + this->call_state_ = 6; + this->call_disconnected_callback_.call(); + } + } else if (message.compare(0, 6, "+CUSD:") == 0) { + // Incoming USSD MESSAGE + this->state_ = STATE_CHECK_USSD; + } break; + } + // Else fall thru ... } case STATE_CHECK_SMS: send_cmd_("AT+CMGL=\"ALL\""); - this->state_ = STATE_PARSE_SMS; + this->state_ = STATE_PARSE_SMS_RESPONSE; this->parse_index_ = 0; break; case STATE_DISABLE_ECHO: send_cmd_("ATE0"); - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; this->expect_ack_ = true; break; - case STATE_CHECK_AT: + case STATE_SETUP_CMGF: send_cmd_("AT+CMGF=1"); + this->state_ = STATE_SETUP_CLIP; + this->expect_ack_ = true; + break; + case STATE_SETUP_CLIP: + send_cmd_("AT+CLIP=1"); this->state_ = STATE_CREG; this->expect_ack_ = true; break; + case STATE_SETUP_USSD: + send_cmd_("AT+CUSD=1"); + this->state_ = STATE_CREG; + this->expect_ack_ = true; + break; + case STATE_SEND_USSD1: + this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\""); + this->state_ = STATE_SEND_USSD2; + break; + case STATE_SEND_USSD2: + ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str()); + if (message == "OK") { + // Dialing + ESP_LOGD(TAG, "Dialing ussd code: '%s' done.", this->ussd_.c_str()); + this->state_ = STATE_CHECK_USSD; + this->send_ussd_pending_ = false; + } else { + this->set_registered_(false); + this->state_ = STATE_INIT; + this->send_cmd_("AT+CMEE=2"); + this->write(26); + } + break; + case STATE_CHECK_USSD: + ESP_LOGD(TAG, "Check ussd code: '%s'", message.c_str()); + if (message.compare(0, 6, "+CUSD:") == 0) { + this->state_ = STATE_RECEIVED_USSD; + this->ussd_ = ""; + size_t start = 10; + size_t end = message.find_last_of(','); + if (end > start) { + this->ussd_ = message.substr(start + 1, end - start - 2); + this->ussd_received_callback_.call(this->ussd_); + } + } + // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS + if (message == "OK") + this->state_ = STATE_INIT; + break; case STATE_CREG: send_cmd_("AT+CREG?"); - this->state_ = STATE_CREGWAIT; + this->state_ = STATE_CREG_WAIT; break; - case STATE_CREGWAIT: { + case STATE_CREG_WAIT: { // Response: "+CREG: 0,1" -- the one there means registered ok // "+CREG: -,-" means not registered ok bool registered = message.compare(0, 6, "+CREG:") == 0 && (message[9] == '1' || message[9] == '5'); @@ -112,10 +205,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message[7] == '0') { // Network registration is disable, enable it send_cmd_("AT+CREG=1"); this->expect_ack_ = true; - this->state_ = STATE_CHECK_AT; + this->state_ = STATE_SETUP_CMGF; } else { // Keep waiting registration - this->state_ = STATE_CREG; + this->state_ = STATE_INIT; } } set_registered_(registered); @@ -145,9 +238,6 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->expect_ack_ = true; this->state_ = STATE_CHECK_SMS; break; - case STATE_PARSE_SMS: - this->state_ = STATE_PARSE_SMS_RESPONSE; - break; case STATE_PARSE_SMS_RESPONSE: if (message.compare(0, 6, "+CMGL:") == 0 && this->parse_index_ == 0) { size_t start = 7; @@ -158,10 +248,11 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (item == 1) { // Slot Index this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } - // item 2 = STATUS, usually "REC UNERAD" + // item 2 = STATUS, usually "REC UNREAD" if (item == 3) { // recipient // Add 1 and remove 2 from substring to get rid of "quotes" this->sender_ = message.substr(start + 1, end - start - 2); + this->message_.clear(); break; } // item 4 = "" @@ -174,42 +265,83 @@ void Sim800LComponent::parse_cmd_(std::string message) { ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); return; } - this->state_ = STATE_RECEIVESMS; + this->state_ = STATE_RECEIVE_SMS; + } + // Otherwise we receive another OK + if (ok) { + send_cmd_("AT+CLCC"); + this->state_ = STATE_CHECK_CALL; } - // Otherwise we receive another OK, we do nothing just wait polling to continuously check for SMS - if (message == "OK") - this->state_ = STATE_INIT; break; - case STATE_RECEIVESMS: + case STATE_CHECK_CALL: + if (message.compare(0, 6, "+CLCC:") == 0 && this->parse_index_ == 0) { + this->expect_ack_ = true; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + // item 1 call index for +CHLD + // item 2 dir 0 Mobile originated; 1 Mobile terminated + if (item == 3) { // stat + uint8_t current_call_state = parse_number(message.substr(start, end - start)).value_or(6); + if (current_call_state != this->call_state_) { + ESP_LOGD(TAG, "Call state is now: %d", current_call_state); + if (current_call_state == 0) + this->call_connected_callback_.call(); + } + this->call_state_ = current_call_state; + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + + if (item < 2) { + ESP_LOGD(TAG, "Invalid message %d %s", this->state_, message.c_str()); + return; + } + } else if (ok) { + if (this->call_state_ != 6) { + // no call in progress + this->call_state_ = 6; // Disconnect + this->call_disconnected_callback_.call(); + } + } + this->state_ = STATE_INIT; + break; + case STATE_RECEIVE_SMS: /* Our recipient is set and the message body is in message kick ESPHome callback now */ - ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); - ESP_LOGD(TAG, "%s", message.c_str()); - this->callback_.call(message, this->sender_); - /* If the message is multiline, next lines will contain message data. - If there were other messages in the list, next line will be +CMGL: ... - At the end of the list the new line and the OK should be received. - To keep this simple just first line of message if considered, then - the next state will swallow all received data and in next poll event - this message index is marked for deletion. - */ - this->state_ = STATE_RECEIVEDSMS; + if (ok || message.compare(0, 6, "+CMGL:") == 0) { + ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); + ESP_LOGD(TAG, "%s", this->message_.c_str()); + this->sms_received_callback_.call(this->message_, this->sender_); + this->state_ = STATE_RECEIVED_SMS; + } else { + if (this->message_.length() > 0) + this->message_ += "\n"; + this->message_ += message; + } break; - case STATE_RECEIVEDSMS: + case STATE_RECEIVED_SMS: + case STATE_RECEIVED_USSD: // Let the buffer flush. Next poll will request to delete the parsed index message. break; - case STATE_SENDINGSMS1: + case STATE_SENDING_SMS_1: this->send_cmd_("AT+CMGS=\"" + this->recipient_ + "\""); - this->state_ = STATE_SENDINGSMS2; + this->state_ = STATE_SENDING_SMS_2; break; - case STATE_SENDINGSMS2: + case STATE_SENDING_SMS_2: if (message == ">") { // Send sms body - ESP_LOGD(TAG, "Sending message: '%s'", this->outgoing_message_.c_str()); + ESP_LOGI(TAG, "Sending to %s message: '%s'", this->recipient_.c_str(), this->outgoing_message_.c_str()); this->write_str(this->outgoing_message_.c_str()); this->write(26); - this->state_ = STATE_SENDINGSMS3; + this->state_ = STATE_SENDING_SMS_3; } else { set_registered_(false); this->state_ = STATE_INIT; @@ -217,7 +349,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->write(26); } break; - case STATE_SENDINGSMS3: + case STATE_SENDING_SMS_3: if (message.compare(0, 6, "+CMGS:") == 0) { ESP_LOGD(TAG, "SMS Sent OK: %s", message.c_str()); this->send_pending_ = false; @@ -230,23 +362,55 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_DIALING2; break; case STATE_DIALING2: - if (message == "OK") { - // Dialing - ESP_LOGD(TAG, "Dialing: '%s'", this->recipient_.c_str()); - this->state_ = STATE_INIT; + if (ok) { + ESP_LOGI(TAG, "Dialing: '%s'", this->recipient_.c_str()); this->dial_pending_ = false; } else { this->set_registered_(false); - this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); } + this->state_ = STATE_INIT; + break; + case STATE_PARSE_CLIP: + if (message.compare(0, 6, "+CLIP:") == 0) { + std::string caller_id; + size_t start = 7; + size_t end = message.find(',', start); + uint8_t item = 0; + while (end != start) { + item++; + if (item == 1) { // Slot Index + // Add 1 and remove 2 from substring to get rid of "quotes" + caller_id = message.substr(start + 1, end - start - 2); + break; + } + // item 4 = "" + // item 5 = Received timestamp + start = end + 1; + end = message.find(',', start); + } + if (this->call_state_ != 4) { + this->call_state_ = 4; + ESP_LOGI(TAG, "Incoming call from %s", caller_id.c_str()); + incoming_call_callback_.call(caller_id); + } + this->state_ = STATE_INIT; + } + break; + case STATE_ATA_SENT: + ESP_LOGI(TAG, "Call connected"); + if (this->call_state_ != 0) { + this->call_state_ = 0; + this->call_connected_callback_.call(); + } + this->state_ = STATE_INIT; break; default: - ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); + ESP_LOGW(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); break; } -} +} // namespace sim800l void Sim800LComponent::loop() { // Read message @@ -265,7 +429,7 @@ void Sim800LComponent::loop() { byte = '?'; // need to be valid utf8 string for log functions. this->read_buffer_[this->read_pos_] = byte; - if (this->state_ == STATE_SENDINGSMS2 && this->read_pos_ == 0 && byte == '>') + if (this->state_ == STATE_SENDING_SMS_2 && this->read_pos_ == 0 && byte == '>') this->read_buffer_[++this->read_pos_] = ASCII_LF; if (this->read_buffer_[this->read_pos_] == ASCII_LF) { @@ -276,13 +440,23 @@ void Sim800LComponent::loop() { this->read_pos_++; } } + if (state_ == STATE_INIT && this->registered_ && + (this->call_state_ != 6 // A call is in progress + || this->send_pending_ || this->dial_pending_ || this->connect_pending_ || this->disconnect_pending_)) { + this->update(); + } } void Sim800LComponent::send_sms(const std::string &recipient, const std::string &message) { - ESP_LOGD(TAG, "Sending to %s: %s", recipient.c_str(), message.c_str()); this->recipient_ = recipient; this->outgoing_message_ = message; this->send_pending_ = true; +} + +void Sim800LComponent::send_ussd(const std::string &ussd_code) { + ESP_LOGD(TAG, "Sending USSD code: %s", ussd_code.c_str()); + this->ussd_ = ussd_code; + this->send_ussd_pending_ = true; this->update(); } void Sim800LComponent::dump_config() { @@ -295,11 +469,11 @@ void Sim800LComponent::dump_config() { #endif } void Sim800LComponent::dial(const std::string &recipient) { - ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); this->recipient_ = recipient; this->dial_pending_ = true; - this->update(); } +void Sim800LComponent::connect() { this->connect_pending_ = true; } +void Sim800LComponent::disconnect() { this->disconnect_pending_ = true; } void Sim800LComponent::set_registered_(bool registered) { this->registered_ = registered; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 3535b96283..bf7efd6915 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -16,31 +16,35 @@ namespace esphome { namespace sim800l { -const uint8_t SIM800L_READ_BUFFER_LENGTH = 255; +const uint16_t SIM800L_READ_BUFFER_LENGTH = 1024; enum State { STATE_IDLE = 0, STATE_INIT, - STATE_CHECK_AT, + STATE_SETUP_CMGF, + STATE_SETUP_CLIP, STATE_CREG, - STATE_CREGWAIT, + STATE_CREG_WAIT, STATE_CSQ, STATE_CSQ_RESPONSE, - STATE_IDLEWAIT, - STATE_SENDINGSMS1, - STATE_SENDINGSMS2, - STATE_SENDINGSMS3, + STATE_SENDING_SMS_1, + STATE_SENDING_SMS_2, + STATE_SENDING_SMS_3, STATE_CHECK_SMS, - STATE_PARSE_SMS, STATE_PARSE_SMS_RESPONSE, - STATE_RECEIVESMS, - STATE_READSMS, - STATE_RECEIVEDSMS, - STATE_DELETEDSMS, + STATE_RECEIVE_SMS, + STATE_RECEIVED_SMS, STATE_DISABLE_ECHO, - STATE_PARSE_SMS_OK, STATE_DIALING1, - STATE_DIALING2 + STATE_DIALING2, + STATE_PARSE_CLIP, + STATE_ATA_SENT, + STATE_CHECK_CALL, + STATE_SETUP_USSD, + STATE_SEND_USSD1, + STATE_SEND_USSD2, + STATE_CHECK_USSD, + STATE_RECEIVED_USSD }; class Sim800LComponent : public uart::UARTDevice, public PollingComponent { @@ -58,10 +62,25 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } #endif void add_on_sms_received_callback(std::function callback) { - this->callback_.add(std::move(callback)); + this->sms_received_callback_.add(std::move(callback)); + } + void add_on_incoming_call_callback(std::function callback) { + this->incoming_call_callback_.add(std::move(callback)); + } + void add_on_call_connected_callback(std::function callback) { + this->call_connected_callback_.add(std::move(callback)); + } + void add_on_call_disconnected_callback(std::function callback) { + this->call_disconnected_callback_.add(std::move(callback)); + } + void add_on_ussd_received_callback(std::function callback) { + this->ussd_received_callback_.add(std::move(callback)); } void send_sms(const std::string &recipient, const std::string &message); + void send_ussd(const std::string &ussd_code); void dial(const std::string &recipient); + void connect(); + void disconnect(); protected: void send_cmd_(const std::string &message); @@ -76,6 +95,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { sensor::Sensor *rssi_sensor_{nullptr}; #endif std::string sender_; + std::string message_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; size_t read_pos_{0}; uint8_t parse_index_{0}; @@ -86,10 +106,19 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { std::string recipient_; std::string outgoing_message_; + std::string ussd_; bool send_pending_; bool dial_pending_; + bool connect_pending_; + bool disconnect_pending_; + bool send_ussd_pending_; + uint8_t call_state_{6}; - CallbackManager callback_; + CallbackManager sms_received_callback_; + CallbackManager incoming_call_callback_; + CallbackManager call_connected_callback_; + CallbackManager call_disconnected_callback_; + CallbackManager ussd_received_callback_; }; class Sim800LReceivedMessageTrigger : public Trigger { @@ -100,6 +129,33 @@ class Sim800LReceivedMessageTrigger : public Trigger { } }; +class Sim800LIncomingCallTrigger : public Trigger { + public: + explicit Sim800LIncomingCallTrigger(Sim800LComponent *parent) { + parent->add_on_incoming_call_callback([this](const std::string &caller_id) { this->trigger(caller_id); }); + } +}; + +class Sim800LCallConnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallConnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_connected_callback([this]() { this->trigger(); }); + } +}; + +class Sim800LCallDisconnectedTrigger : public Trigger<> { + public: + explicit Sim800LCallDisconnectedTrigger(Sim800LComponent *parent) { + parent->add_on_call_disconnected_callback([this]() { this->trigger(); }); + } +}; +class Sim800LReceivedUssdTrigger : public Trigger { + public: + explicit Sim800LReceivedUssdTrigger(Sim800LComponent *parent) { + parent->add_on_ussd_received_callback([this](const std::string &ussd) { this->trigger(ussd); }); + } +}; + template class Sim800LSendSmsAction : public Action { public: Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} @@ -116,6 +172,20 @@ template class Sim800LSendSmsAction : public Action { Sim800LComponent *parent_; }; +template class Sim800LSendUssdAction : public Action { + public: + Sim800LSendUssdAction(Sim800LComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, ussd) + + void play(Ts... x) { + auto ussd_code = this->ussd_.value(x...); + this->parent_->send_ussd(ussd_code); + } + + protected: + Sim800LComponent *parent_; +}; + template class Sim800LDialAction : public Action { public: Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {} @@ -129,6 +199,25 @@ template class Sim800LDialAction : public Action { protected: Sim800LComponent *parent_; }; +template class Sim800LConnectAction : public Action { + public: + Sim800LConnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->connect(); } + + protected: + Sim800LComponent *parent_; +}; + +template class Sim800LDisconnectAction : public Action { + public: + Sim800LDisconnectAction(Sim800LComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->disconnect(); } + + protected: + Sim800LComponent *parent_; +}; } // namespace sim800l } // namespace esphome From 6b23b7cad7cecdd892dd36f8e9a5fce0e9d3cb29 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Thu, 15 Sep 2022 01:53:02 +0200 Subject: [PATCH 072/838] Unify 'nullptr' initalization of class members; (#3805) --- esphome/components/ade7953/ade7953.h | 2 +- esphome/components/api/api_frame_helper.h | 6 +++--- esphome/components/hydreon_rgxx/hydreon_rgxx.h | 6 +++--- esphome/components/mlx90393/sensor_mlx90393.h | 2 +- esphome/components/pid/pid_climate.h | 4 ++-- esphome/components/pulse_meter/pulse_meter_sensor.h | 4 ++-- .../components/pvvx_mithermometer/display/pvvx_display.h | 2 +- esphome/core/automation.h | 2 +- esphome/core/component.h | 2 +- esphome/core/gpio.h | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index bb160cd8eb..418ad1c9e7 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -82,7 +82,7 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { return i2c::ERROR_OK; } - InternalGPIOPin *irq_pin_ = nullptr; + InternalGPIOPin *irq_pin_{nullptr}; bool is_setup_{false}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_a_sensor_{nullptr}; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 57e3c961d5..348a9b574f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -116,9 +116,9 @@ class APINoiseFrameHelper : public APIFrameHelper { std::vector prologue_; std::shared_ptr ctx_; - NoiseHandshakeState *handshake_ = nullptr; - NoiseCipherState *send_cipher_ = nullptr; - NoiseCipherState *recv_cipher_ = nullptr; + NoiseHandshakeState *handshake_{nullptr}; + NoiseCipherState *send_cipher_{nullptr}; + NoiseCipherState *recv_cipher_{nullptr}; NoiseProtocolId nid_; enum class State { diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index 1697060d28..34b9bd8d5e 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -58,9 +58,9 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; #ifdef USE_BINARY_SENSOR - binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; - binary_sensor::BinarySensor *lens_bad_sensor_ = nullptr; - binary_sensor::BinarySensor *em_sat_sensor_ = nullptr; + binary_sensor::BinarySensor *too_cold_sensor_{nullptr}; + binary_sensor::BinarySensor *lens_bad_sensor_{nullptr}; + binary_sensor::BinarySensor *em_sat_sensor_{nullptr}; #endif int16_t boot_count_ = 0; diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h index fc33ad1aa8..8dfb7e6a13 100644 --- a/esphome/components/mlx90393/sensor_mlx90393.h +++ b/esphome/components/mlx90393/sensor_mlx90393.h @@ -52,7 +52,7 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90 uint8_t temperature_oversampling_ = 0; uint8_t filter_; uint8_t resolutions_[3] = {0}; - GPIOPin *drdy_pin_ = nullptr; + GPIOPin *drdy_pin_{nullptr}; }; } // namespace mlx90393 diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index ff301386b6..095c00eb49 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -59,8 +59,8 @@ class PIDClimate : public climate::Climate, public Component { /// The sensor used for getting the current temperature sensor::Sensor *sensor_; - output::FloatOutput *cool_output_ = nullptr; - output::FloatOutput *heat_output_ = nullptr; + output::FloatOutput *cool_output_{nullptr}; + output::FloatOutput *heat_output_{nullptr}; PIDController controller_; /// Output value as reported by the PID controller, for PIDClimateSensor float output_value_; diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index cf08f8c92d..bf50eab6ff 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -31,11 +31,11 @@ class PulseMeterSensor : public sensor::Sensor, public Component { protected: static void gpio_intr(PulseMeterSensor *sensor); - InternalGPIOPin *pin_ = nullptr; + InternalGPIOPin *pin_{nullptr}; ISRInternalGPIOPin isr_pin_; uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; - sensor::Sensor *total_sensor_ = nullptr; + sensor::Sensor *total_sensor_{nullptr}; InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index 4de1ab7ba6..c7e7cc04fb 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -114,7 +114,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { void delayed_disconnect_(); #ifdef USE_TIME void sync_time_(); - time::RealTimeClock *time_ = nullptr; + time::RealTimeClock *time_{nullptr}; #endif uint16_t char_handle_ = 0; bool connection_established_ = false; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 92bc32247b..84c754e081 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -176,7 +176,7 @@ template class Action { return this->next_->is_running(); } - Action *next_ = nullptr; + Action *next_{nullptr}; /// The number of instances of this sequence in the list of actions /// that is currently being executed. diff --git a/esphome/core/component.h b/esphome/core/component.h index e394736653..cb97a93d21 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -254,7 +254,7 @@ class Component { uint32_t component_state_{0x0000}; ///< State of this component. float setup_priority_override_{NAN}; - const char *component_source_ = nullptr; + const char *component_source_{nullptr}; }; /** This class simplifies creating components that periodically check a state. diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index b953a95664..1b6f2ba1e6 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -73,7 +73,7 @@ class ISRInternalGPIOPin { void pin_mode(gpio::Flags flags); protected: - void *arg_ = nullptr; + void *arg_{nullptr}; }; class InternalGPIOPin : public GPIOPin { From df4d0da221f8c1296ea3c53d3667d50c44a663df Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:53:22 +1200 Subject: [PATCH 073/838] Initialize all child sensors to nullptr (#3808) --- esphome/components/aht10/aht10.h | 4 ++-- esphome/components/am2320/am2320.h | 4 ++-- esphome/components/as3935/as3935.h | 6 +++--- esphome/components/bl0939/bl0939.h | 16 ++++++++-------- esphome/components/bl0940/bl0940.h | 12 ++++++------ esphome/components/bl0942/bl0942.h | 10 +++++----- esphome/components/bme280/bme280.h | 6 +++--- esphome/components/bme680/bme680.h | 8 ++++---- esphome/components/bme680_bsec/bme680_bsec.h | 18 +++++++++--------- esphome/components/bmp280/bmp280.h | 4 ++-- esphome/components/bmp3xx/bmp3xx.h | 4 ++-- esphome/components/dht12/dht12.h | 4 ++-- esphome/components/dps310/dps310.h | 4 ++-- esphome/components/ens210/ens210.h | 4 ++-- esphome/components/hdc1080/hdc1080.h | 4 ++-- esphome/components/hmc5883l/hmc5883l.h | 8 ++++---- esphome/components/honeywellabp/honeywellabp.h | 4 ++-- esphome/components/ms5611/ms5611.h | 4 ++-- esphome/components/pzem004t/pzem004t.h | 8 ++++---- esphome/components/pzemac/pzemac.h | 12 ++++++------ esphome/components/pzemdc/pzemdc.h | 10 +++++----- esphome/components/qmc5883l/qmc5883l.h | 8 ++++---- esphome/components/qmp6988/qmp6988.h | 4 ++-- esphome/components/sht3xd/sht3xd.h | 4 ++-- esphome/components/shtcx/shtcx.h | 4 ++-- esphome/components/tsl2591/tsl2591.h | 8 ++++---- esphome/components/tx20/tx20.h | 4 ++-- 27 files changed, 93 insertions(+), 93 deletions(-) diff --git a/esphome/components/aht10/aht10.h b/esphome/components/aht10/aht10.h index bfb6b07a7a..4d0eaa5919 100644 --- a/esphome/components/aht10/aht10.h +++ b/esphome/components/aht10/aht10.h @@ -18,8 +18,8 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice { void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace aht10 diff --git a/esphome/components/am2320/am2320.h b/esphome/components/am2320/am2320.h index 33e1d30aa0..da1e87cf65 100644 --- a/esphome/components/am2320/am2320.h +++ b/esphome/components/am2320/am2320.h @@ -21,8 +21,8 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint8_t *data); bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace am2320 diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 2e65aab4d1..2cba9b11a0 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -92,9 +92,9 @@ class AS3935Component : public Component { virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; - sensor::Sensor *distance_sensor_; - sensor::Sensor *energy_sensor_; - binary_sensor::BinarySensor *thunder_alert_binary_sensor_; + sensor::Sensor *distance_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; GPIOPin *irq_pin_; bool indoor_; diff --git a/esphome/components/bl0939/bl0939.h b/esphome/components/bl0939/bl0939.h index d3dab67cc1..673d4ff351 100644 --- a/esphome/components/bl0939/bl0939.h +++ b/esphome/components/bl0939/bl0939.h @@ -75,16 +75,16 @@ class BL0939 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_1_; - sensor::Sensor *current_sensor_2_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_1_; - sensor::Sensor *power_sensor_2_; - sensor::Sensor *energy_sensor_1_; - sensor::Sensor *energy_sensor_2_; - sensor::Sensor *energy_sensor_sum_; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_1_{nullptr}; + sensor::Sensor *energy_sensor_2_{nullptr}; + sensor::Sensor *energy_sensor_sum_{nullptr}; // Divide by this to turn into Watt float power_reference_ = BL0939_PREF; diff --git a/esphome/components/bl0940/bl0940.h b/esphome/components/bl0940/bl0940.h index 49c8e50595..2d4e7ccaac 100644 --- a/esphome/components/bl0940/bl0940.h +++ b/esphome/components/bl0940/bl0940.h @@ -75,14 +75,14 @@ class BL0940 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *internal_temperature_sensor_; - sensor::Sensor *external_temperature_sensor_; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *internal_temperature_sensor_{nullptr}; + sensor::Sensor *external_temperature_sensor_{nullptr}; // Max difference between two measurements of the temperature. Used to avoid noise. float max_temperature_diff_{0}; diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h index 8149b7493b..12489915e1 100644 --- a/esphome/components/bl0942/bl0942.h +++ b/esphome/components/bl0942/bl0942.h @@ -43,13 +43,13 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; // NB This may be negative as the circuits is seemingly able to measure // power in both directions - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *frequency_sensor_; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; // Divide by this to turn into Watt float power_reference_ = BL0942_PREF; diff --git a/esphome/components/bme280/bme280.h b/esphome/components/bme280/bme280.h index 8511f73382..50d398c40f 100644 --- a/esphome/components/bme280/bme280.h +++ b/esphome/components/bme280/bme280.h @@ -96,9 +96,9 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X}; BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X}; BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bme680/bme680.h b/esphome/components/bme680/bme680.h index 0671cd990e..6446449742 100644 --- a/esphome/components/bme680/bme680.h +++ b/esphome/components/bme680/bme680.h @@ -129,10 +129,10 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice { uint16_t heater_temperature_{320}; uint16_t heater_duration_{150}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; }; } // namespace bme680 diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 650b4d2413..6fe8f8fef7 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -100,15 +100,15 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; - sensor::Sensor *humidity_sensor_; - sensor::Sensor *gas_resistance_sensor_; - sensor::Sensor *iaq_sensor_; - text_sensor::TextSensor *iaq_accuracy_text_sensor_; - sensor::Sensor *iaq_accuracy_sensor_; - sensor::Sensor *co2_equivalent_sensor_; - sensor::Sensor *breath_voc_equivalent_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *gas_resistance_sensor_{nullptr}; + sensor::Sensor *iaq_sensor_{nullptr}; + text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr}; + sensor::Sensor *iaq_accuracy_sensor_{nullptr}; + sensor::Sensor *co2_equivalent_sensor_{nullptr}; + sensor::Sensor *breath_voc_equivalent_sensor_{nullptr}; }; #endif } // namespace bme680_bsec diff --git a/esphome/components/bmp280/bmp280.h b/esphome/components/bmp280/bmp280.h index f8646fb547..96eb470155 100644 --- a/esphome/components/bmp280/bmp280.h +++ b/esphome/components/bmp280/bmp280.h @@ -81,8 +81,8 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { BMP280Oversampling temperature_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280Oversampling pressure_oversampling_{BMP280_OVERSAMPLING_16X}; BMP280IIRFilter iir_filter_{BMP280_IIR_FILTER_OFF}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/bmp3xx/bmp3xx.h b/esphome/components/bmp3xx/bmp3xx.h index ab20abfe9b..d3b15f601d 100644 --- a/esphome/components/bmp3xx/bmp3xx.h +++ b/esphome/components/bmp3xx/bmp3xx.h @@ -125,8 +125,8 @@ class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { Oversampling pressure_oversampling_{OVERSAMPLING_X16}; IIRFilter iir_filter_{IIR_FILTER_OFF}; OperationMode operation_mode_{FORCED_MODE}; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; enum ErrorCode { NONE = 0, ERROR_COMMUNICATION_FAILED, diff --git a/esphome/components/dht12/dht12.h b/esphome/components/dht12/dht12.h index ae4d4fd607..2a706039ba 100644 --- a/esphome/components/dht12/dht12.h +++ b/esphome/components/dht12/dht12.h @@ -20,8 +20,8 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice { protected: bool read_data_(uint8_t *data); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace dht12 diff --git a/esphome/components/dps310/dps310.h b/esphome/components/dps310/dps310.h index 7aca2c3d10..50e7d93c8a 100644 --- a/esphome/components/dps310/dps310.h +++ b/esphome/components/dps310/dps310.h @@ -53,8 +53,8 @@ class DPS310Component : public PollingComponent, public i2c::I2CDevice { void calculate_values_(int32_t raw_temperature, int32_t raw_pressure); static int32_t twos_complement(int32_t val, uint8_t bits); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; int32_t raw_pressure_, raw_temperature_, c00_, c10_; int16_t c0_, c1_, c01_, c11_, c20_, c21_, c30_; uint8_t prod_rev_id_; diff --git a/esphome/components/ens210/ens210.h b/esphome/components/ens210/ens210.h index 342be04799..0fb6ff634d 100644 --- a/esphome/components/ens210/ens210.h +++ b/esphome/components/ens210/ens210.h @@ -31,8 +31,8 @@ class ENS210Component : public PollingComponent, public i2c::I2CDevice { bool set_low_power_(bool enable); void extract_measurement_(uint32_t val, int *data, int *status); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace ens210 diff --git a/esphome/components/hdc1080/hdc1080.h b/esphome/components/hdc1080/hdc1080.h index 9cb87cdb8b..2ff7b6dc33 100644 --- a/esphome/components/hdc1080/hdc1080.h +++ b/esphome/components/hdc1080/hdc1080.h @@ -21,8 +21,8 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; protected: - sensor::Sensor *temperature_; - sensor::Sensor *humidity_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; }; } // namespace hdc1080 diff --git a/esphome/components/hmc5883l/hmc5883l.h b/esphome/components/hmc5883l/hmc5883l.h index 41d41baa22..3481f45dc8 100644 --- a/esphome/components/hmc5883l/hmc5883l.h +++ b/esphome/components/hmc5883l/hmc5883l.h @@ -54,10 +54,10 @@ class HMC5883LComponent : public PollingComponent, public i2c::I2CDevice { HMC5883LOversampling oversampling_{HMC5883L_OVERSAMPLING_1}; HMC5883LDatarate datarate_{HMC5883L_DATARATE_15_0_HZ}; HMC5883LRange range_{HMC5883L_RANGE_130_UT}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/honeywellabp/honeywellabp.h b/esphome/components/honeywellabp/honeywellabp.h index 44d5952ca6..98f6f08c4a 100644 --- a/esphome/components/honeywellabp/honeywellabp.h +++ b/esphome/components/honeywellabp/honeywellabp.h @@ -29,8 +29,8 @@ class HONEYWELLABPSensor : public PollingComponent, uint8_t status_ = 0; // byte to hold status information. int pressure_count_ = 0; // hold raw pressure data (14 - bit, 0 - 16384) int temperature_count_ = 0; // hold raw temperature data (11 - bit, 0 - 2048) - sensor::Sensor *pressure_sensor_; - sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; uint8_t readsensor_(); uint8_t readstatus_(); int rawpressure_(); diff --git a/esphome/components/ms5611/ms5611.h b/esphome/components/ms5611/ms5611.h index b5663ad736..476db79612 100644 --- a/esphome/components/ms5611/ms5611.h +++ b/esphome/components/ms5611/ms5611.h @@ -22,8 +22,8 @@ class MS5611Component : public PollingComponent, public i2c::I2CDevice { void read_pressure_(uint32_t raw_temperature); void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; uint16_t prom_[6]; }; diff --git a/esphome/components/pzem004t/pzem004t.h b/esphome/components/pzem004t/pzem004t.h index f4f9f29b4d..e18413f35c 100644 --- a/esphome/components/pzem004t/pzem004t.h +++ b/esphome/components/pzem004t/pzem004t.h @@ -23,10 +23,10 @@ class PZEM004T : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; enum PZEM004TReadState { SET_ADDRESS = 0xB4, diff --git a/esphome/components/pzemac/pzemac.h b/esphome/components/pzemac/pzemac.h index e9f76972a3..8f2cf1460d 100644 --- a/esphome/components/pzemac/pzemac.h +++ b/esphome/components/pzemac/pzemac.h @@ -27,12 +27,12 @@ class PZEMAC : public PollingComponent, public modbus::ModbusDevice { protected: template friend class ResetEnergyAction; - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *energy_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *energy_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; void reset_energy_(); }; diff --git a/esphome/components/pzemdc/pzemdc.h b/esphome/components/pzemdc/pzemdc.h index d838eb4167..a78a48a6fb 100644 --- a/esphome/components/pzemdc/pzemdc.h +++ b/esphome/components/pzemdc/pzemdc.h @@ -22,11 +22,11 @@ class PZEMDC : public PollingComponent, public modbus::ModbusDevice { void dump_config() override; protected: - sensor::Sensor *voltage_sensor_; - sensor::Sensor *current_sensor_; - sensor::Sensor *power_sensor_; - sensor::Sensor *frequency_sensor_; - sensor::Sensor *power_factor_sensor_; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; }; } // namespace pzemdc diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index 01697ecbd0..15ef435ce5 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -45,10 +45,10 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; QMC5883LRange range_{QMC5883L_RANGE_200_UT}; QMC5883LOversampling oversampling_{QMC5883L_SAMPLING_512}; - sensor::Sensor *x_sensor_; - sensor::Sensor *y_sensor_; - sensor::Sensor *z_sensor_; - sensor::Sensor *heading_sensor_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/qmp6988/qmp6988.h b/esphome/components/qmp6988/qmp6988.h index ef944ba4ff..f0c11adf43 100644 --- a/esphome/components/qmp6988/qmp6988.h +++ b/esphome/components/qmp6988/qmp6988.h @@ -91,8 +91,8 @@ class QMP6988Component : public PollingComponent, public i2c::I2CDevice { protected: qmp6988_data_t qmp6988_data_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X}; QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X}; diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 3164aa0687..41ca3c5d6e 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -19,8 +19,8 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir void update() override; protected: - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace sht3xd diff --git a/esphome/components/shtcx/shtcx.h b/esphome/components/shtcx/shtcx.h index c44fb9d9c1..084d3bfc35 100644 --- a/esphome/components/shtcx/shtcx.h +++ b/esphome/components/shtcx/shtcx.h @@ -26,8 +26,8 @@ class SHTCXComponent : public PollingComponent, public sensirion_common::Sensiri protected: SHTCXType type_; uint16_t sensor_id_; - sensor::Sensor *temperature_sensor_; - sensor::Sensor *humidity_sensor_; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; }; } // namespace shtcx diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index d82dbc395f..5b7eea35ec 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -245,10 +245,10 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { protected: const char *name_; - sensor::Sensor *full_spectrum_sensor_; - sensor::Sensor *infrared_sensor_; - sensor::Sensor *visible_sensor_; - sensor::Sensor *calculated_lux_sensor_; + sensor::Sensor *full_spectrum_sensor_{nullptr}; + sensor::Sensor *infrared_sensor_{nullptr}; + sensor::Sensor *visible_sensor_{nullptr}; + sensor::Sensor *calculated_lux_sensor_{nullptr}; TSL2591IntegrationTime integration_time_; TSL2591ComponentGain component_gain_; TSL2591Gain gain_; diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 1c617d0674..95a9517227 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -43,8 +43,8 @@ class Tx20Component : public Component { std::string wind_cardinal_direction_; InternalGPIOPin *pin_; - sensor::Sensor *wind_speed_sensor_; - sensor::Sensor *wind_direction_degrees_sensor_; + sensor::Sensor *wind_speed_sensor_{nullptr}; + sensor::Sensor *wind_direction_degrees_sensor_{nullptr}; Tx20ComponentStore store_; }; From 625a575e49f362578a9ea6acef7d83538fa66b9d Mon Sep 17 00:00:00 2001 From: Azimath Date: Wed, 14 Sep 2022 19:54:33 -0400 Subject: [PATCH 074/838] Remove floating point calculation from ac_dimmer ISR (#3770) --- esphome/components/ac_dimmer/ac_dimmer.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 1d0cd8d0ab..16101a1c2c 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -121,11 +121,8 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - // calculate required value to provide a true RMS voltage output - this->enable_time_us = - std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) * - (this->cycle_time_us - min_us)) / - 65535); + this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% @@ -206,6 +203,7 @@ void AcDimmer::setup() { #endif } void AcDimmer::write_state(float state) { + state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation auto new_value = static_cast(roundf(state * 65535)); if (new_value != 0 && this->store_.value == 0) this->store_.init_cycle = this->init_with_half_cycle_; From 7deabbb51232da5d0ab85744cd6e61431e5c3666 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:02:40 +1200 Subject: [PATCH 075/838] Bump version to 2022.9.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 608a61466b..5ce1edb8cd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0b1" +__version__ = "2022.9.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 7a91ca98090a57c180a01750c388f496a47aee93 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 15 Sep 2022 12:27:50 -0700 Subject: [PATCH 076/838] split pronto codes if they are too long (#3812) Co-authored-by: Samuel Sieb --- esphome/components/remote_base/pronto_protocol.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index d434744e49..d8798d4ab9 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -227,7 +227,18 @@ optional ProntoProtocol::decode(RemoteReceiveData src) { return out; } -void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } +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_LOGD(TAG, "Received Pronto: data=%s", first.c_str()); + if (!rest.empty()) + ESP_LOGD(TAG, "%s", rest.c_str()); +} } // namespace remote_base } // namespace esphome From f6e5a8cb2a9840840239006fd7649f56cab02d02 Mon Sep 17 00:00:00 2001 From: pawel3410 Date: Fri, 16 Sep 2022 03:19:41 +0200 Subject: [PATCH 077/838] Fix mcp23s17 addressing beyond 3 (#3797) --- esphome/components/mcp23s17/mcp23s17.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 2fc9c74634..ca4c28e0b4 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -23,6 +23,13 @@ void MCP23S17::setup() { this->transfer_byte(0b00011000); // Enable HAEN pins for addressing this->disable(); + this->enable(); + cmd = 0b01001000; + this->transfer_byte(cmd); + this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA); + this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->disable(); + // Read current output register state this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); From 55ad45e3ee2384fa6b2146d0b0646566dd69a202 Mon Sep 17 00:00:00 2001 From: h3ndrik Date: Sun, 18 Sep 2022 21:25:59 +0200 Subject: [PATCH 078/838] [BME280] raise standby time (#3804) --- esphome/components/bme280/bme280.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 345b24a36e..d8124f5dc3 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -163,7 +163,7 @@ void BME280Component::setup() { return; } config_register &= ~0b11111100; - config_register |= 0b000 << 5; // 0.5 ms standby time + config_register |= 0b101 << 5; // 1000 ms standby time config_register |= (this->iir_filter_ & 0b111) << 2; if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) { this->mark_failed(); From d1c85fc3fa9b9705fbbc366c11f7d2846e36b192 Mon Sep 17 00:00:00 2001 From: Geek_cat <36782632+zhzhzhy@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:01:00 +0800 Subject: [PATCH 079/838] Allow CORS for web_server (#3819) --- esphome/components/web_server_base/web_server_base.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index bc37337ca5..cdabe66d44 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -81,6 +81,7 @@ class WebServerBase : public Component { return; } this->server_ = std::make_shared(this->port_); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); this->server_->begin(); for (auto *handler : this->handlers_) From ab8674a5c7993f6d10abbf9792adaba3c591154e Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 19 Sep 2022 20:02:55 -0500 Subject: [PATCH 080/838] Make sprinkler reset_resume() method public (#3824) --- esphome/components/sprinkler/sprinkler.cpp | 12 ++++++------ esphome/components/sprinkler/sprinkler.h | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index ab694c8412..2be71a08d0 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -769,7 +769,7 @@ void Sprinkler::resume() { ESP_LOGD(TAG, "Resuming valve %u with %u 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()); - this->reset_resume_(); + this->reset_resume(); } else { ESP_LOGD(TAG, "No valve to resume!"); } @@ -783,6 +783,11 @@ void Sprinkler::resume_or_start_full_cycle() { } } +void Sprinkler::reset_resume() { + this->paused_valve_.reset(); + this->resume_duration_.reset(); +} + const char *Sprinkler::valve_name(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { return this->valve_[valve_number].controller_switch->get_name().c_str(); @@ -1101,11 +1106,6 @@ void Sprinkler::reset_cycle_states_() { } } -void Sprinkler::reset_resume_() { - this->paused_valve_.reset(); - this->resume_duration_.reset(); -} - void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) { this->next_req_.set_valve(requested_valve); this->next_req_.set_run_duration(requested_run_duration); diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 1243a844fa..acd168d791 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -308,6 +308,9 @@ class Sprinkler : public Component, public EntityBase { /// if a cycle was suspended using pause(), resumes it. otherwise calls start_full_cycle() void resume_or_start_full_cycle(); + /// resets resume state + void reset_resume(); + /// returns a pointer to a valve's name string object; returns nullptr if valve_number is invalid const char *valve_name(size_t valve_number); @@ -401,9 +404,6 @@ class Sprinkler : public Component, public EntityBase { /// resets the cycle state for all valves void reset_cycle_states_(); - /// resets resume state - void reset_resume_(); - /// make a request of the state machine void fsm_request_(size_t requested_valve, uint32_t requested_run_duration = 0); From b2db524366e79bdb3f786840ef63fded3285805a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 20 Sep 2022 01:13:59 -0400 Subject: [PATCH 081/838] Bump dashboard to 20220919.1 (#3828) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd0c70bb72..a81c38c44f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220508.0 +esphome-dashboard==20220919.1 aioesphomeapi==10.13.0 zeroconf==0.39.1 From fb9984e21f54dabab4563b83c5f14dce4ac3128e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 15 Sep 2022 12:27:50 -0700 Subject: [PATCH 082/838] split pronto codes if they are too long (#3812) Co-authored-by: Samuel Sieb --- esphome/components/remote_base/pronto_protocol.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index d434744e49..d8798d4ab9 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -227,7 +227,18 @@ optional ProntoProtocol::decode(RemoteReceiveData src) { return out; } -void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } +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_LOGD(TAG, "Received Pronto: data=%s", first.c_str()); + if (!rest.empty()) + ESP_LOGD(TAG, "%s", rest.c_str()); +} } // namespace remote_base } // namespace esphome From 47a7a239aef236351ba547ab44d4f299f3c691ab Mon Sep 17 00:00:00 2001 From: h3ndrik Date: Sun, 18 Sep 2022 21:25:59 +0200 Subject: [PATCH 083/838] [BME280] raise standby time (#3804) --- esphome/components/bme280/bme280.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 345b24a36e..d8124f5dc3 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -163,7 +163,7 @@ void BME280Component::setup() { return; } config_register &= ~0b11111100; - config_register |= 0b000 << 5; // 0.5 ms standby time + config_register |= 0b101 << 5; // 1000 ms standby time config_register |= (this->iir_filter_ & 0b111) << 2; if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) { this->mark_failed(); From 91f1c25fccea0f7d120ead79dcdd609b2236616a Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 19 Sep 2022 20:02:55 -0500 Subject: [PATCH 084/838] Make sprinkler reset_resume() method public (#3824) --- esphome/components/sprinkler/sprinkler.cpp | 12 ++++++------ esphome/components/sprinkler/sprinkler.h | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index ab694c8412..2be71a08d0 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -769,7 +769,7 @@ void Sprinkler::resume() { ESP_LOGD(TAG, "Resuming valve %u with %u 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()); - this->reset_resume_(); + this->reset_resume(); } else { ESP_LOGD(TAG, "No valve to resume!"); } @@ -783,6 +783,11 @@ void Sprinkler::resume_or_start_full_cycle() { } } +void Sprinkler::reset_resume() { + this->paused_valve_.reset(); + this->resume_duration_.reset(); +} + const char *Sprinkler::valve_name(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { return this->valve_[valve_number].controller_switch->get_name().c_str(); @@ -1101,11 +1106,6 @@ void Sprinkler::reset_cycle_states_() { } } -void Sprinkler::reset_resume_() { - this->paused_valve_.reset(); - this->resume_duration_.reset(); -} - void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) { this->next_req_.set_valve(requested_valve); this->next_req_.set_run_duration(requested_run_duration); diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 1243a844fa..acd168d791 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -308,6 +308,9 @@ class Sprinkler : public Component, public EntityBase { /// if a cycle was suspended using pause(), resumes it. otherwise calls start_full_cycle() void resume_or_start_full_cycle(); + /// resets resume state + void reset_resume(); + /// returns a pointer to a valve's name string object; returns nullptr if valve_number is invalid const char *valve_name(size_t valve_number); @@ -401,9 +404,6 @@ class Sprinkler : public Component, public EntityBase { /// resets the cycle state for all valves void reset_cycle_states_(); - /// resets resume state - void reset_resume_(); - /// make a request of the state machine void fsm_request_(size_t requested_valve, uint32_t requested_run_duration = 0); From 5c4e83ebdc1f8685b0dc7a675416d75d4552f221 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 20 Sep 2022 01:13:59 -0400 Subject: [PATCH 085/838] Bump dashboard to 20220919.1 (#3828) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bd0c70bb72..a81c38c44f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220508.0 +esphome-dashboard==20220919.1 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 5f56cf312848c5e20f0c25463e768f90a3a372da Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:14:51 +1200 Subject: [PATCH 086/838] Bump version to 2022.9.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5ce1edb8cd..02cbe02ba2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0b2" +__version__ = "2022.9.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 1444cddda9393d5b21d2ca4e2c8bc23a79ea9179 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 20 Sep 2022 02:23:55 -0300 Subject: [PATCH 087/838] Fix-esphome-validation-line-number (#3815) --- esphome/config.py | 22 +++++++++++++++------- esphome/core/config.py | 6 +++++- esphome/vscode.py | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index 56fe75a4c7..04717be6f5 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -165,15 +165,19 @@ class Config(OrderedDict, fv.FinalValidateConfig): return err return None - def get_deepest_document_range_for_path(self, path): - # type: (ConfigPath) -> Optional[ESPHomeDataBase] + def get_deepest_document_range_for_path(self, path, get_key=False): + # type: (ConfigPath, bool) -> Optional[ESPHomeDataBase] data = self doc_range = None - for item_index in path: + for index, path_item in enumerate(path): try: - if item_index in data: - doc_range = [x for x in data.keys() if x == item_index][0].esp_range - data = data[item_index] + if path_item in data: + key_data = [x for x in data.keys() if x == path_item][0] + if isinstance(key_data, ESPHomeDataBase): + doc_range = key_data.esp_range + if get_key and index == len(path) - 1: + return doc_range + data = data[path_item] except (KeyError, IndexError, TypeError, AttributeError): return doc_range if isinstance(data, core.ID): @@ -281,7 +285,7 @@ class ConfigValidationStep(abc.ABC): class LoadValidationStep(ConfigValidationStep): """Load step, this step is called once for each domain config fragment. - Responsibilties: + Responsibilities: - Load component code - Ensure all AUTO_LOADs are added - Set output paths of result @@ -738,6 +742,10 @@ def validate_config(config, command_line_substitutions) -> Config: result.add_validation_step(LoadValidationStep(key, config[key])) result.run_validation_steps() + if result.errors: + # do not try to validate further as we don't know what the target is + return result + for domain, conf in config.items(): result.add_validation_step(LoadValidationStep(domain, conf)) result.add_validation_step(IDPassValidationStep()) diff --git a/esphome/core/config.py b/esphome/core/config.py index f1337be04b..82cf37d44d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -179,7 +179,11 @@ def preload_core_config(config, result): ] if not has_oldstyle and not newstyle_found: - raise cv.Invalid("Platform missing for core options!", [CONF_ESPHOME]) + raise cv.Invalid( + "Platform missing. You must include one of the available platform keys: " + + ", ".join(TARGET_PLATFORMS), + [CONF_ESPHOME], + ) if has_oldstyle and newstyle_found: raise cv.Invalid( f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", diff --git a/esphome/vscode.py b/esphome/vscode.py index 68d59abd02..6a43a654ed 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -12,7 +12,9 @@ from typing import Optional def _get_invalid_range(res, invalid): # type: (Config, cv.Invalid) -> Optional[DocumentRange] - return res.get_deepest_document_range_for_path(invalid.path) + return res.get_deepest_document_range_for_path( + invalid.path, invalid.error_message == "extra keys not allowed" + ) def _dump_range(range): From 3572c62315a1caf915e9b23e558ffba8aee3161b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 20 Sep 2022 15:35:46 -0400 Subject: [PATCH 088/838] Bump dashboard to 20220920.0 (#3831) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a81c38c44f..0a667bef10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220919.1 +esphome-dashboard==20220920.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 33f296e05b11786c48bfbe8cbb1bed9a6c559572 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 20 Sep 2022 02:23:55 -0300 Subject: [PATCH 089/838] Fix-esphome-validation-line-number (#3815) --- esphome/config.py | 22 +++++++++++++++------- esphome/core/config.py | 6 +++++- esphome/vscode.py | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/esphome/config.py b/esphome/config.py index 56fe75a4c7..04717be6f5 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -165,15 +165,19 @@ class Config(OrderedDict, fv.FinalValidateConfig): return err return None - def get_deepest_document_range_for_path(self, path): - # type: (ConfigPath) -> Optional[ESPHomeDataBase] + def get_deepest_document_range_for_path(self, path, get_key=False): + # type: (ConfigPath, bool) -> Optional[ESPHomeDataBase] data = self doc_range = None - for item_index in path: + for index, path_item in enumerate(path): try: - if item_index in data: - doc_range = [x for x in data.keys() if x == item_index][0].esp_range - data = data[item_index] + if path_item in data: + key_data = [x for x in data.keys() if x == path_item][0] + if isinstance(key_data, ESPHomeDataBase): + doc_range = key_data.esp_range + if get_key and index == len(path) - 1: + return doc_range + data = data[path_item] except (KeyError, IndexError, TypeError, AttributeError): return doc_range if isinstance(data, core.ID): @@ -281,7 +285,7 @@ class ConfigValidationStep(abc.ABC): class LoadValidationStep(ConfigValidationStep): """Load step, this step is called once for each domain config fragment. - Responsibilties: + Responsibilities: - Load component code - Ensure all AUTO_LOADs are added - Set output paths of result @@ -738,6 +742,10 @@ def validate_config(config, command_line_substitutions) -> Config: result.add_validation_step(LoadValidationStep(key, config[key])) result.run_validation_steps() + if result.errors: + # do not try to validate further as we don't know what the target is + return result + for domain, conf in config.items(): result.add_validation_step(LoadValidationStep(domain, conf)) result.add_validation_step(IDPassValidationStep()) diff --git a/esphome/core/config.py b/esphome/core/config.py index f1337be04b..82cf37d44d 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -179,7 +179,11 @@ def preload_core_config(config, result): ] if not has_oldstyle and not newstyle_found: - raise cv.Invalid("Platform missing for core options!", [CONF_ESPHOME]) + raise cv.Invalid( + "Platform missing. You must include one of the available platform keys: " + + ", ".join(TARGET_PLATFORMS), + [CONF_ESPHOME], + ) if has_oldstyle and newstyle_found: raise cv.Invalid( f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", diff --git a/esphome/vscode.py b/esphome/vscode.py index 68d59abd02..6a43a654ed 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -12,7 +12,9 @@ from typing import Optional def _get_invalid_range(res, invalid): # type: (Config, cv.Invalid) -> Optional[DocumentRange] - return res.get_deepest_document_range_for_path(invalid.path) + return res.get_deepest_document_range_for_path( + invalid.path, invalid.error_message == "extra keys not allowed" + ) def _dump_range(range): From d56107e97fe72de58c26b2667cc42e9d08bc8fbc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 20 Sep 2022 15:35:46 -0400 Subject: [PATCH 090/838] Bump dashboard to 20220920.0 (#3831) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a81c38c44f..0a667bef10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220919.1 +esphome-dashboard==20220920.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 63b42f36080c17ab51fc57325e2fd61dc71eac4a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:36:39 +1200 Subject: [PATCH 091/838] Bump version to 2022.9.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 02cbe02ba2..0fe271cd5a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0b3" +__version__ = "2022.9.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 9a69769a7e377634efc2d688fe5cde802383e0be Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Sep 2022 07:54:35 +1200 Subject: [PATCH 092/838] Dont fail fast on CI for docker (#3832) --- .github/workflows/ci-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 7eb4cd1f66..86035b9259 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -28,6 +28,7 @@ jobs: name: Build docker containers runs-on: ubuntu-latest strategy: + fail-fast: false matrix: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] From 68ea59f3ae19fafdb8ce59133e9fc8513ec2b910 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 20 Sep 2022 21:32:53 -0300 Subject: [PATCH 093/838] Bump dashboard to 20220920.1 (#3834) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0a667bef10..55544e442e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220920.0 +esphome-dashboard==20220920.1 aioesphomeapi==10.13.0 zeroconf==0.39.1 From fd6135aebb1199dfd4029bfe53f3343c7dcb14f7 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 20 Sep 2022 21:32:53 -0300 Subject: [PATCH 094/838] Bump dashboard to 20220920.1 (#3834) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0a667bef10..55544e442e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220920.0 +esphome-dashboard==20220920.1 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 91560ae4e9613e78c3265c6d637e6f7ee530f2af Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Sep 2022 12:38:55 +1200 Subject: [PATCH 095/838] Bump version to 2022.9.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 0fe271cd5a..290e5e2994 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0b4" +__version__ = "2022.9.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 9655362f238bacc6e24a4c5446fb345f4d8afe8d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:34:02 +1200 Subject: [PATCH 096/838] Bump version to 2022.9.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 290e5e2994..41057f3d29 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0b5" +__version__ = "2022.9.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 6ef93452f5fa707d1023e498b7d3ee6579850411 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Sep 2022 07:38:31 +1200 Subject: [PATCH 097/838] Revert "fix spi timing issues" (#3838) --- esphome/components/spi/spi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 6c92321ac8..7f0b0f481a 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,11 +195,6 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } - #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = SPI_MODE0; @@ -220,6 +215,11 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND } #endif // USE_SPI_ARDUINO_BACKEND + + if (cs != nullptr) { + this->active_cs_ = cs; + this->active_cs_->digital_write(false); + } } void disable(); From 34bef2f2ca73f5e73796ea62671dccb97de48e60 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Sep 2022 07:38:31 +1200 Subject: [PATCH 098/838] Revert "fix spi timing issues" (#3838) --- esphome/components/spi/spi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 6c92321ac8..7f0b0f481a 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -195,11 +195,6 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } - #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = SPI_MODE0; @@ -220,6 +215,11 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND } #endif // USE_SPI_ARDUINO_BACKEND + + if (cs != nullptr) { + this->active_cs_ = cs; + this->active_cs_->digital_write(false); + } } void disable(); From 66226abb4877de61261b4bc5553ea7caf5664a4f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Sep 2022 07:40:15 +1200 Subject: [PATCH 099/838] Bump version to 2022.9.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 41057f3d29..ce5a51aa71 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.0" +__version__ = "2022.9.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ce2e161b082be0bf44b2689e40d0c63dd69676fb Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 25 Sep 2022 18:16:30 -0300 Subject: [PATCH 100/838] Bump dashboard to 20220925.0 (#3846) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55544e442e..7f86acdbef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220920.1 +esphome-dashboard==20220925.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 8095db67152eecf8709d3c7893efdeddf027f52f Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Mon, 26 Sep 2022 12:59:04 +1000 Subject: [PATCH 101/838] Thermostat remove deprecated config (#3643) * Raise errors for all the now deprecated options * Fix CONF_DEFAULT_PRESET detection * Stop attempting to set the non-existent normal_config * Add support for default presets * Fix correct detection of Two Point temperature mode * Fix lint issues * Fix tests * Generate correct yaml for equivalent configurations * Remove debug code * Only set default preset if the thermostat does not have state to restore * Add restore_default_preset_on_boot option If set to True then the default_preset will be applied on every boot. If False (Default) state will be restored from memory as per prior versions * Apply lint suggestions * Switch from restore_default_preset_on_boot to an enum for startup_behavior This gives better self-documentation as well as the option for extending to other options down the track * Lint fixes * Rename startup_behavior to on_boot_restore_from This removes any issues with different English locales * Fix comparable_preset yaml output alignment * Add dump of on_boot_restore_from setting Co-authored-by: Keith Burzinski --- esphome/components/thermostat/climate.py | 147 ++++++++++++------ .../thermostat/thermostat_climate.cpp | 53 +++++-- .../thermostat/thermostat_climate.h | 18 ++- tests/test3.yaml | 12 +- 4 files changed, 161 insertions(+), 69 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 5e26e6d6de..8aa61dbb93 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -69,6 +69,8 @@ from esphome.const import ( ) CONF_PRESET_CHANGE = "preset_change" +CONF_DEFAULT_PRESET = "default_preset" +CONF_ON_BOOT_RESTORE_FROM = "on_boot_restore_from" CODEOWNERS = ["@kbx81"] @@ -80,6 +82,13 @@ ThermostatClimate = thermostat_ns.class_( ThermostatClimateTargetTempConfig = thermostat_ns.struct( "ThermostatClimateTargetTempConfig" ) +OnBootRestoreFrom = thermostat_ns.enum("OnBootRestoreFrom") +ON_BOOT_RESTORE_FROM = { + "MEMORY": OnBootRestoreFrom.MEMORY, + "DEFAULT_PRESET": OnBootRestoreFrom.DEFAULT_PRESET, +} +validate_on_boot_restore_from = cv.enum(ON_BOOT_RESTORE_FROM, upper=True) + ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { "OFF": ClimateMode.CLIMATE_MODE_OFF, @@ -125,6 +134,17 @@ def validate_temperature_preset(preset, root_config, name, requirements): ) +def generate_comparable_preset(config, name): + comparable_preset = f"{CONF_PRESET}:\n" f" - {CONF_NAME}: {name}\n" + + if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config: + comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n" + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config: + comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_HIGH}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]}\n" + + return comparable_preset + + def validate_thermostat(config): # verify corresponding action(s) exist(s) for any defined climate mode or action requirements = { @@ -277,13 +297,32 @@ def validate_thermostat(config): CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], } - # Validate temperature requirements for default configuraation - validate_temperature_preset(config, config, "default", requirements) + # Legacy high/low configs + if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config: + comparable_preset = generate_comparable_preset(config, "Your new preset") - # Validate temperature requirements for away configuration + raise cv.Invalid( + f"{CONF_DEFAULT_TARGET_TEMPERATURE_LOW} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n" + f"{comparable_preset}" + ) + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config: + comparable_preset = generate_comparable_preset(config, "Your new preset") + + raise cv.Invalid( + f"{CONF_DEFAULT_TARGET_TEMPERATURE_HIGH} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n" + f"{comparable_preset}" + ) + + # Legacy away mode - raise an error instructing the user to switch to presets if CONF_AWAY_CONFIG in config: - away = config[CONF_AWAY_CONFIG] - validate_temperature_preset(away, config, "away", requirements) + comparable_preset = generate_comparable_preset(config[CONF_AWAY_CONFIG], "Away") + + raise cv.Invalid( + f"{CONF_AWAY_CONFIG} is no longer valid. Please switch to using a preset named " + "Away" + " for an equivalent experience.\nEquivalent configuration:\n\n" + f"{comparable_preset}" + ) # Validate temperature requirements for presets if CONF_PRESET in config: @@ -292,7 +331,12 @@ def validate_thermostat(config): preset_config, config, preset_config[CONF_NAME], requirements ) - # Verify default climate mode is valid given above configuration + # Warn about using the removed CONF_DEFAULT_MODE and advise users + if CONF_DEFAULT_MODE in config and config[CONF_DEFAULT_MODE] is not None: + raise cv.Invalid( + f"{CONF_DEFAULT_MODE} is no longer valid. Please switch to using presets and specify a {CONF_DEFAULT_PRESET}." + ) + default_mode = config[CONF_DEFAULT_MODE] requirements = { "HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION], @@ -403,6 +447,38 @@ def validate_thermostat(config): f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration" ) + # If a default preset is requested then ensure that preset is defined + if CONF_DEFAULT_PRESET in config: + default_preset = config[CONF_DEFAULT_PRESET] + + if CONF_PRESET not in config: + raise cv.Invalid( + f"{CONF_DEFAULT_PRESET} is specified but no presets are defined" + ) + + presets = config[CONF_PRESET] + found_preset = False + + for preset in presets: + if preset[CONF_NAME] == default_preset: + found_preset = True + break + + if found_preset is False: + raise cv.Invalid( + f"{CONF_DEFAULT_PRESET} set to '{default_preset}' but no such preset has been defined. Available presets: {[preset[CONF_NAME] for preset in presets]}" + ) + + # If restoring default preset on boot is true then ensure we have a default preset + if ( + CONF_ON_BOOT_RESTORE_FROM in config + and config[CONF_ON_BOOT_RESTORE_FROM] is OnBootRestoreFrom.DEFAULT_PRESET + ): + if CONF_DEFAULT_PRESET not in config: + raise cv.Invalid( + f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode" + ) + if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config: raise cv.Invalid( f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}" @@ -502,9 +578,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_TARGET_TEMPERATURE_CHANGE_ACTION ): automation.validate_automation(single=True), - cv.Optional(CONF_DEFAULT_MODE, default="OFF"): cv.templatable( - validate_climate_mode - ), + cv.Optional(CONF_DEFAULT_MODE, default=None): cv.valid, + cv.Optional(CONF_DEFAULT_PRESET): cv.templatable(cv.string), cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Optional( @@ -542,6 +617,7 @@ CONFIG_SCHEMA = cv.All( } ), cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA), + cv.Optional(CONF_ON_BOOT_RESTORE_FROM): validate_on_boot_restore_from, cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation( single=True ), @@ -564,9 +640,10 @@ async def to_code(config): CONF_COOL_ACTION in config or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config) ) + if two_points_available: + cg.add(var.set_supports_two_points(True)) sens = await cg.get_variable(config[CONF_SENSOR]) - cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE])) cg.add( var.set_set_point_minimum_differential( config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL] @@ -579,23 +656,6 @@ async def to_code(config): cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND])) cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN])) - if two_points_available is True: - cg.add(var.set_supports_two_points(True)) - normal_config = ThermostatClimateTargetTempConfig( - config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], - ) - elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config: - cg.add(var.set_supports_two_points(False)) - normal_config = ThermostatClimateTargetTempConfig( - config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] - ) - elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config: - cg.add(var.set_supports_two_points(False)) - normal_config = ThermostatClimateTargetTempConfig( - config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] - ) - if CONF_MAX_COOLING_RUN_TIME in config: cg.add( var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME]) @@ -661,7 +721,6 @@ async def to_code(config): cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING])) cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY])) - cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_HOME, normal_config)) await automation.build_automation( var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] @@ -808,27 +867,8 @@ async def to_code(config): config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION], ) - if CONF_AWAY_CONFIG in config: - away = config[CONF_AWAY_CONFIG] - - if two_points_available is True: - away_config = ThermostatClimateTargetTempConfig( - away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], - ) - elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away: - away_config = ThermostatClimateTargetTempConfig( - away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] - ) - elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away: - away_config = ThermostatClimateTargetTempConfig( - away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] - ) - cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_AWAY, away_config)) - if CONF_PRESET in config: for preset_config in config[CONF_PRESET]: - name = preset_config[CONF_NAME] standard_preset = None if name.upper() in climate.CLIMATE_PRESETS: @@ -872,6 +912,19 @@ async def to_code(config): else: cg.add(var.set_custom_preset_config(name, preset_target_variable)) + if CONF_DEFAULT_PRESET in config: + default_preset_name = config[CONF_DEFAULT_PRESET] + + # if the name is a built in preset use the appropriate naming format + if default_preset_name.upper() in climate.CLIMATE_PRESETS: + climate_preset = climate.CLIMATE_PRESETS[default_preset_name.upper()] + cg.add(var.set_default_preset(climate_preset)) + else: + cg.add(var.set_default_preset(default_preset_name)) + + if CONF_ON_BOOT_RESTORE_FROM in config: + cg.add(var.set_on_boot_restore_from(config[CONF_ON_BOOT_RESTORE_FROM])) + if CONF_PRESET_CHANGE in config: await automation.build_automation( var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE] diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index dc4e1e437e..a9b03187d3 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -25,15 +25,27 @@ void ThermostatClimate::setup() { this->publish_state(); }); this->current_temperature = this->sensor_->state; - // restore all climate data, if possible - auto restore = this->restore_state_(); - if (restore.has_value()) { - restore->to_call(this).perform(); - } else { - // restore from defaults, change_away handles temps for us - this->mode = this->default_mode_; - this->change_preset_(climate::CLIMATE_PRESET_HOME); + + auto use_default_preset = true; + + if (this->on_boot_restore_from_ == thermostat::OnBootRestoreFrom::MEMORY) { + // restore all climate data, if possible + auto restore = this->restore_state_(); + if (restore.has_value()) { + use_default_preset = false; + restore->to_call(this).perform(); + } } + + // Either we failed to restore state or the user has requested we always apply the default preset + if (use_default_preset) { + if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) { + this->change_preset_(this->default_preset_); + } else if (!this->default_custom_preset_.empty()) { + this->change_custom_preset_(this->default_custom_preset_); + } + } + // refresh the climate action based on the restored settings, we'll publish_state() later this->switch_to_action_(this->compute_action_(), false); this->switch_to_supplemental_action_(this->compute_supplemental_action_()); @@ -923,10 +935,12 @@ bool ThermostatClimate::supplemental_heating_required_() { (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING)); } -void ThermostatClimate::dump_preset_config_(const std::string &preset, - const ThermostatClimateTargetTempConfig &config) { +void ThermostatClimate::dump_preset_config_(const std::string &preset, const ThermostatClimateTargetTempConfig &config, + bool is_default_preset) { const auto *preset_name = preset.c_str(); + ESP_LOGCONFIG(TAG, " %s Is Default: %s", preset_name, YESNO(is_default_preset)); + if (this->supports_heat_) { if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name, @@ -1061,7 +1075,15 @@ ThermostatClimate::ThermostatClimate() temperature_change_trigger_(new Trigger<>()), preset_change_trigger_(new Trigger<>()) {} -void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } +void ThermostatClimate::set_default_preset(const std::string &custom_preset) { + this->default_custom_preset_ = custom_preset; +} + +void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; } + +void ThermostatClimate::set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from) { + this->on_boot_restore_from_ = on_boot_restore_from; +} void ThermostatClimate::set_set_point_minimum_differential(float differential) { this->set_point_minimum_differential_ = differential; } @@ -1213,8 +1235,9 @@ Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->p void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); + } ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); if (this->supports_cool_) { ESP_LOGCONFIG(TAG, " Cooling Parameters:"); @@ -1284,7 +1307,7 @@ void ThermostatClimate::dump_config() { const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first)); ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); - this->dump_preset_config_(preset_name, it.second); + this->dump_preset_config_(preset_name, it.second, it.first == this->default_preset_); } ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: "); @@ -1292,8 +1315,10 @@ void ThermostatClimate::dump_config() { const auto *preset_name = it.first.c_str(); ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); - this->dump_preset_config_(preset_name, it.second); + this->dump_preset_config_(preset_name, it.second, it.first == this->default_custom_preset_); } + ESP_LOGCONFIG(TAG, " On boot, restore from: %s", + this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY"); } ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default; diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index a5498dc53d..aa7529cfb1 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -22,6 +22,7 @@ enum ThermostatClimateTimerIndex : size_t { TIMER_IDLE_ON = 9, }; +enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 }; struct ThermostatClimateTimer { const std::string name; bool active; @@ -57,7 +58,9 @@ class ThermostatClimate : public climate::Climate, public Component { void setup() override; void dump_config() override; - void set_default_mode(climate::ClimateMode default_mode); + void set_default_preset(const std::string &custom_preset); + void set_default_preset(climate::ClimatePreset preset); + void set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from); void set_set_point_minimum_differential(float differential); void set_cool_deadband(float deadband); void set_cool_overrun(float overrun); @@ -225,7 +228,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool supplemental_cooling_required_(); bool supplemental_heating_required_(); - void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config); + void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config, + bool is_default_preset); /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; @@ -397,7 +401,6 @@ class ThermostatClimate : public climate::Climate, public Component { /// These are used to determine when a trigger/action needs to be called climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF}; climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON}; - climate::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; @@ -441,6 +444,15 @@ class ThermostatClimate : public climate::Climate, public Component { std::map preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") std::map custom_preset_config_{}; + + /// Default standard preset to use on start up + climate::ClimatePreset default_preset_{}; + /// Default custom preset to use on start up + std::string default_custom_preset_{}; + + /// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior + /// state will attempt to be restored if possible + thermostat::OnBootRestoreFrom on_boot_restore_from_{thermostat::OnBootRestoreFrom::MEMORY}; }; } // namespace thermostat diff --git a/tests/test3.yaml b/tests/test3.yaml index 4eee0fd2c9..8fc66f4918 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1061,8 +1061,13 @@ climate: - platform: thermostat name: Thermostat Climate sensor: ha_hello_world - default_target_temperature_low: 18°C - default_target_temperature_high: 24°C + 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: - switch.turn_on: gpio_switch1 cool_action: @@ -1137,9 +1142,6 @@ climate: fan_only_cooling: true fan_with_cooling: true fan_with_heating: true - away_config: - default_target_temperature_low: 16°C - default_target_temperature_high: 20°C - platform: pid id: pid_climate name: PID Climate Controller From efdb3d1f40eb0750b15ff2e3894aea0ac8ba28d3 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 25 Sep 2022 18:16:30 -0300 Subject: [PATCH 102/838] Bump dashboard to 20220925.0 (#3846) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55544e442e..7f86acdbef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220920.1 +esphome-dashboard==20220925.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From d2c1c7507cbb42dd1bfa857006b3db9f6432f4d1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:00:55 +1300 Subject: [PATCH 103/838] Bump version to 2022.9.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ce5a51aa71..a272f518df 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.1" +__version__ = "2022.9.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 106de3530dac29a0d21803aa44a11121142d63a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Sep 2022 10:15:30 -1000 Subject: [PATCH 104/838] Add support for parsing the short local name in the tracker (#3854) --- .../components/esp32_ble_tracker/esp32_ble_tracker.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index f7e51a8ab3..47c1f6022e 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -637,11 +637,17 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p // (called CSS here) switch (record_type) { + case ESP_BLE_AD_TYPE_NAME_SHORT: case ESP_BLE_AD_TYPE_NAME_CMPL: { // CSS 1.2 LOCAL NAME // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the // device." CSS 1: Optional in this context; shall not appear more than once in a block. - this->name_ = std::string(reinterpret_cast(record), record_length); + // SHORTENED LOCAL NAME + // "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened + // Local Name data type shall not be used to advertise a name that is longer than the Local Name data type." + if (record_length > this->name_.length()) { + this->name_ = std::string(reinterpret_cast(record), record_length); + } break; } case ESP_BLE_AD_TYPE_TX_PWR: { From f4a84765cdc618d488e50b7a132c955b9c45448d Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 30 Sep 2022 01:10:53 -0500 Subject: [PATCH 105/838] Add display GPIO setup instruction for Aliexpress display (#3851) --- esphome/components/ssd1327_base/ssd1327_base.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index 4cb8d17a3d..4223a013a4 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -76,6 +76,8 @@ void SSD1327::setup() { this->command(0x55); this->command(SSD1327_SETVCOMHVOLTAGE); // Set High Voltage Level of COM Pin this->command(0x1C); + this->command(SSD1327_SETGPIO); // Switch voltage converter on (for Aliexpress display) + this->command(0x03); this->command(SSD1327_NORMALDISPLAY); // set display mode set_brightness(this->brightness_); this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on From ed443c6153c56eedd5671b7fa545806c5350ff1c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:45:06 +1300 Subject: [PATCH 106/838] Bluetooth Proxy active connections (#3817) --- CODEOWNERS | 1 + .../airthings_wave_mini.cpp | 6 +- .../airthings_wave_plus.cpp | 6 +- esphome/components/am43/am43.cpp | 10 +- esphome/components/am43/cover/am43_cover.cpp | 21 +- esphome/components/anova/anova.cpp | 22 +- esphome/components/api/api.proto | 177 +++- esphome/components/api/api_connection.cpp | 72 +- esphome/components/api/api_connection.h | 31 +- esphome/components/api/api_pb2.cpp | 764 +++++++++++++++++- esphome/components/api/api_pb2.h | 231 +++++- esphome/components/api/api_pb2_service.cpp | 285 ++++++- esphome/components/api/api_pb2_service.h | 95 ++- esphome/components/api/api_server.cpp | 45 ++ esphome/components/api/api_server.h | 6 + esphome/components/bedjet/bedjet_hub.cpp | 48 +- esphome/components/bedjet/bedjet_hub.h | 2 - esphome/components/ble_client/__init__.py | 7 +- esphome/components/ble_client/automation.cpp | 6 +- esphome/components/ble_client/automation.h | 3 +- esphome/components/ble_client/ble_client.cpp | 409 +--------- esphome/components/ble_client/ble_client.h | 86 +- .../components/ble_client/sensor/automation.h | 3 +- .../ble_client/sensor/ble_sensor.cpp | 12 +- .../ble_client/text_sensor/automation.h | 3 +- .../text_sensor/ble_text_sensor.cpp | 12 +- .../components/bluetooth_proxy/__init__.py | 16 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 323 +++++++- .../bluetooth_proxy/bluetooth_proxy.h | 39 +- .../components/esp32_ble_client/__init__.py | 12 + .../esp32_ble_client/ble_characteristic.cpp | 83 ++ .../esp32_ble_client/ble_characteristic.h | 35 + .../esp32_ble_client/ble_client_base.cpp | 324 ++++++++ .../esp32_ble_client/ble_client_base.h | 72 ++ .../esp32_ble_client/ble_descriptor.h | 25 + .../esp32_ble_client/ble_service.cpp | 66 ++ .../components/esp32_ble_client/ble_service.h | 32 + .../components/esp32_ble_tracker/__init__.py | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 37 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 5 +- esphome/components/esp32_ble_tracker/queue.h | 10 +- .../display/pvvx_display.cpp | 5 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 12 +- esphome/const.py | 1 + 44 files changed, 2818 insertions(+), 644 deletions(-) create mode 100644 esphome/components/esp32_ble_client/__init__.py create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.cpp create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.h create mode 100644 esphome/components/esp32_ble_client/ble_client_base.cpp create mode 100644 esphome/components/esp32_ble_client/ble_client_base.h create mode 100644 esphome/components/esp32_ble_client/ble_descriptor.h create mode 100644 esphome/components/esp32_ble_client/ble_service.cpp create mode 100644 esphome/components/esp32_ble_client/ble_service.h diff --git a/CODEOWNERS b/CODEOWNERS index 69e30027a9..d95ebcce59 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -70,6 +70,7 @@ esphome/components/ektf2232/* @jesserockz esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 2e7a1fb024..40873ec005 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -88,8 +88,8 @@ void AirthingsWaveMini::update() { } void AirthingsWaveMini::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 02ed33b87a..11f86307fe 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -109,8 +109,8 @@ void AirthingsWavePlus::update() { } void AirthingsWavePlus::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 1c4bad64c3..9a0e5999d2 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i if (this->current_sensor_ > 0) { if (this->illuminance_ != nullptr) { auto *packet = this->encoder_->get_light_level_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -102,8 +102,8 @@ void Am43::update() { if (this->battery_ != nullptr) { auto *packet = this->encoder_->get_battery_level_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 39089e73c0..ba8e350732 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -27,8 +27,8 @@ void Am43Component::loop() { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); if (status) { ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); @@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) { if (call.get_stop()) { auto *packet = this->encoder_->get_stop_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); } @@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) { pos = 1 - pos; auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); } @@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->decoder_->pin_ok_) { ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); auto *packet = this->encoder_->get_position_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); } else { diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 6c8316d338..cafd30149d 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) { ESP_LOGW(TAG, "Unsupported mode: %d", mode); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } if (call.get_target_temperature().has_value()) { auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } @@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } if (pkt != nullptr) { auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, - pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -137,8 +140,9 @@ void Anova::update() { auto *pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); this->current_request_++; diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c6cde8b038..9fa77d2daa 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -27,7 +27,6 @@ service APIConnection { rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} - rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc get_time (GetTimeRequest) returns (GetTimeResponse) { option (needs_authentication) = false; } @@ -44,6 +43,16 @@ service APIConnection { rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} + + rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} + rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} + rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {} + rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {} + rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {} + rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} + rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} } @@ -78,6 +87,8 @@ message HelloRequest { // Not strictly necessary to send but nice for debugging // purposes. string client_info = 1; + uint32 api_version_major = 2; + uint32 api_version_minor = 3; } // Confirmation of successful connection request. @@ -192,7 +203,7 @@ message DeviceInfoResponse { uint32 webserver_port = 10; - bool has_bluetooth_proxy = 11; + uint32 bluetooth_proxy_version = 11; } message ListEntitiesRequest { @@ -1111,7 +1122,8 @@ message SubscribeBluetoothLEAdvertisementsRequest { message BluetoothServiceData { string uuid = 1; - repeated uint32 data = 2 [packed=false]; + repeated uint32 legacy_data = 2 [deprecated = true]; + bytes data = 3; // Changed in proto version 1.7 } message BluetoothLEAdvertisementResponse { option (id) = 67; @@ -1127,3 +1139,162 @@ message BluetoothLEAdvertisementResponse { repeated BluetoothServiceData service_data = 5; repeated BluetoothServiceData manufacturer_data = 6; } + +enum BluetoothDeviceRequestType { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0; + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2; + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; +} + +message BluetoothDeviceRequest { + option (id) = 68; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + BluetoothDeviceRequestType request_type = 2; +} + +message BluetoothDeviceConnectionResponse { + option (id) = 69; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool connected = 2; + uint32 mtu = 3; + int32 error = 4; +} + +message BluetoothGATTGetServicesRequest { + option (id) = 70; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTDescriptor { + repeated uint64 uuid = 1; + uint32 handle = 2; +} + +message BluetoothGATTCharacteristic { + repeated uint64 uuid = 1; + uint32 handle = 2; + uint32 properties = 3; + repeated BluetoothGATTDescriptor descriptors = 4; +} + +message BluetoothGATTService { + repeated uint64 uuid = 1; + uint32 handle = 2; + repeated BluetoothGATTCharacteristic characteristics = 3; +} + +message BluetoothGATTGetServicesResponse { + option (id) = 71; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + repeated BluetoothGATTService services = 2; +} + +message BluetoothGATTGetServicesDoneResponse { + option (id) = 72; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTReadRequest { + option (id) = 73; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTReadResponse { + option (id) = 74; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; + +} + +message BluetoothGATTWriteRequest { + option (id) = 75; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool response = 3; + + bytes data = 4; +} + +message BluetoothGATTReadDescriptorRequest { + option (id) = 76; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTWriteDescriptorRequest { + option (id) = 77; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message BluetoothGATTNotifyRequest { + option (id) = 78; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool enable = 3; +} + +message BluetoothGATTNotifyDataResponse { + option (id) = 79; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message SubscribeBluetoothConnectionsFreeRequest { + option (id) = 80; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; +} + +message BluetoothConnectionsFreeResponse { + option (id) = 81; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint32 free = 1; + uint32 limit = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a3b000c778..1dbf0abec4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,10 +1,10 @@ #include "api_connection.h" -#include "esphome/core/entity_base.h" -#include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include "esphome/core/version.h" -#include "esphome/core/hal.h" #include +#include "esphome/components/network/util.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/version.h" #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -12,6 +12,9 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif +#ifdef USE_BLUETOOTH_PROXY +#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" +#endif namespace esphome { namespace api { @@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { } #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) { + if (!this->bluetooth_le_advertisement_subscription_) + return false; + if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) { + BluetoothLEAdvertisementResponse resp = msg; + for (auto &service : resp.service_data) { + service.legacy_data.assign(service.data.begin(), service.data.end()); + service.data.clear(); + } + for (auto &manufacturer_data : resp.manufacturer_data) { + manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end()); + manufacturer_data.data.clear(); + } + return this->send_bluetooth_le_advertisement_response(resp); + } + return this->send_bluetooth_le_advertisement_response(msg); +} +void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); +} +void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); +} +void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); +} +void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); +} +void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); +} +void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); +} + +void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); +} + +BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + BluetoothConnectionsFreeResponse resp; + resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free(); + resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit(); + return resp; +} +#endif + bool APIConnection::send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; @@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin HelloResponse APIConnection::hello(const HelloRequest &msg) { this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; this->helper_->set_log_info(client_info_); - ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); + this->client_api_version_major_ = msg.api_version_major; + this->client_api_version_minor_ = msg.api_version_minor; + ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), + this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 6; + resp.api_version_minor = 7; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); @@ -888,7 +944,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.webserver_port = USE_WEBSERVER_PORT; #endif #ifdef USE_BLUETOOTH_PROXY - resp.has_bluetooth_proxy = true; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1; #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index dcf8bacad2..028fb80175 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -1,15 +1,11 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/application.h" +#include "api_frame_helper.h" #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" -#include "api_frame_helper.h" - -#ifdef USE_BLUETOOTH_PROXY -#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" -#endif +#include "esphome/core/application.h" +#include "esphome/core/component.h" namespace esphome { namespace api { @@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection { this->send_homeassistant_service_response(call); } #ifdef USE_BLUETOOTH_PROXY - bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { - if (!this->bluetooth_le_advertisement_subscription_) - return false; - return this->send_bluetooth_le_advertisement_response(call); - } + bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); + + void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; + void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; + void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; + void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; + void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; + void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; + void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; + BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) override; + #endif #ifdef USE_HOMEASSISTANT_TIME void send_time_request() { @@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection { std::unique_ptr helper_; std::string client_info_; + uint32_t client_api_version_major_{0}; + uint32_t client_api_version_minor_{0}; #ifdef USE_ESP32_CAMERA esp32_camera::CameraImageReader image_reader_; #endif @@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; - bool bluetooth_le_advertisement_subscription_{true}; + bool bluetooth_le_advertisement_subscription_{false}; bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 13969fce76..73d8044cde 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string(enums::Me return "UNKNOWN"; } } +template<> +const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) { + switch (value) { + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"; + default: + return "UNKNOWN"; + } +} +bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->api_version_major = value.as_uint32(); + return true; + } + case 3: { + this->api_version_minor = value.as_uint32(); + return true; + } + default: + return false; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) return false; } } -void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } +void HelloRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->client_info); + buffer.encode_uint32(2, this->api_version_major); + buffer.encode_uint32(3, this->api_version_minor); +} #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const { out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); out.append("\n"); + + out.append(" api_version_major: "); + sprintf(buffer, "%u", this->api_version_major); + out.append(buffer); + out.append("\n"); + + out.append(" api_version_minor: "); + sprintf(buffer, "%u", this->api_version_minor); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 11: { - this->has_bluetooth_proxy = value.as_bool(); + this->bluetooth_proxy_version = value.as_uint32(); return true; } default: @@ -548,7 +591,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); buffer.encode_uint32(10, this->webserver_port); - buffer.encode_bool(11, this->has_bluetooth_proxy); + buffer.encode_uint32(11, this->bluetooth_proxy_version); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -595,8 +638,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_bluetooth_proxy: "); - out.append(YESNO(this->has_bluetooth_proxy)); + out.append(" bluetooth_proxy_version: "); + sprintf(buffer, "%u", this->bluetooth_proxy_version); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -4872,7 +4916,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->data.push_back(value.as_uint32()); + this->legacy_data.push_back(value.as_uint32()); return true; } default: @@ -4885,15 +4929,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited this->uuid = value.as_string(); return true; } + case 3: { + this->data = value.as_string(); + return true; + } default: return false; } } void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->uuid); - for (auto &it : this->data) { + for (auto &it : this->legacy_data) { buffer.encode_uint32(2, it, true); } + buffer.encode_string(3, this->data); } #ifdef HAS_PROTO_MESSAGE_DUMP void BluetoothServiceData::dump_to(std::string &out) const { @@ -4903,12 +4952,16 @@ void BluetoothServiceData::dump_to(std::string &out) const { out.append("'").append(this->uuid).append("'"); out.append("\n"); - for (const auto &it : this->data) { - out.append(" data: "); + for (const auto &it : this->legacy_data) { + out.append(" legacy_data: "); sprintf(buffer, "%u", it); out.append(buffer); out.append("\n"); } + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -5000,6 +5053,699 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->request_type = value.as_enum(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_enum(2, this->request_type); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" request_type: "); + out.append(proto_enum_to_string(this->request_type)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->connected = value.as_bool(); + return true; + } + case 3: { + this->mtu = value.as_uint32(); + return true; + } + case 4: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->connected); + buffer.encode_uint32(3, this->mtu); + buffer.encode_int32(4, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceConnectionResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" connected: "); + out.append(YESNO(this->connected)); + out.append("\n"); + + out.append(" mtu: "); + sprintf(buffer, "%u", this->mtu); + out.append(buffer); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTDescriptor::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTDescriptor {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->properties = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->descriptors.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + buffer.encode_uint32(3, this->properties); + for (auto &it : this->descriptors) { + buffer.encode_message(4, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTCharacteristic::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTCharacteristic {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" properties: "); + sprintf(buffer, "%u", this->properties); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->descriptors) { + out.append(" descriptors: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->characteristics.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + for (auto &it : this->characteristics) { + buffer.encode_message(3, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTService::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTService {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->characteristics) { + out.append(" characteristics: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->services.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + for (auto &it : this->services) { + buffer.encode_message(2, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->services) { + out.append(" services: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesDoneResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->response = value.as_bool(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->response); + buffer.encode_string(4, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" response: "); + out.append(YESNO(this->response)); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->enable = value.as_bool(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->enable); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" enable: "); + out.append(YESNO(this->enable)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyDataResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { + out.append("SubscribeBluetoothConnectionsFreeRequest {}"); +} +#endif +bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->free = value.as_uint32(); + return true; + } + case 2: { + this->limit = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->free); + buffer.encode_uint32(2, this->limit); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothConnectionsFreeResponse {\n"); + out.append(" free: "); + sprintf(buffer, "%u", this->free); + out.append(buffer); + out.append("\n"); + + out.append(" limit: "); + sprintf(buffer, "%u", this->limit); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2093f93ee7..325e9a23c3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_UNMUTE = 4, }; +enum BluetoothDeviceRequestType : uint32_t { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2, + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, +}; } // namespace enums class HelloRequest : public ProtoMessage { public: std::string client_info{}; + uint32_t api_version_major{0}; + uint32_t api_version_minor{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class HelloResponse : public ProtoMessage { public: @@ -263,7 +272,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; uint32_t webserver_port{0}; - bool has_bluetooth_proxy{false}; + uint32_t bluetooth_proxy_version{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1227,7 +1236,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { class BluetoothServiceData : public ProtoMessage { public: std::string uuid{}; - std::vector data{}; + std::vector legacy_data{}; + std::string data{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1254,6 +1264,223 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothDeviceRequest : public ProtoMessage { + public: + uint64_t address{0}; + enums::BluetoothDeviceRequestType request_type{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothDeviceConnectionResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool connected{false}; + uint32_t mtu{0}; + int32_t error{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesRequest : public ProtoMessage { + public: + uint64_t address{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTDescriptor : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTCharacteristic : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + uint32_t properties{0}; + std::vector descriptors{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTService : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + std::vector characteristics{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesResponse : public ProtoMessage { + public: + uint64_t address{0}; + std::vector services{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesDoneResponse : public ProtoMessage { + public: + uint64_t address{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTWriteRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool response{false}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTWriteDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTNotifyRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool enable{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTNotifyDataResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class BluetoothConnectionsFreeResponse : public ProtoMessage { + public: + uint32_t free{0}; + uint32_t limit{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 6932675c41..7bfe1acf48 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -336,6 +336,71 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu return this->send_message_(msg, 67); } #endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 69); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 71); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response( + const BluetoothGATTGetServicesDoneResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 72); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 74); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 79); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 81); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -612,6 +677,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_subscribe_bluetooth_le_advertisements_request(msg); break; } + case 68: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothDeviceRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_device_request(msg); +#endif + break; + } + case 70: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTGetServicesRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_get_services_request(msg); +#endif + break; + } + case 73: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_request(msg); +#endif + break; + } + case 75: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_request(msg); +#endif + break; + } + case 76: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_descriptor_request(msg); +#endif + break; + } + case 77: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_descriptor_request(msg); +#endif + break; + } + case 78: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTNotifyRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_notify_request(msg); +#endif + break; + } + case 80: { +#ifdef USE_BLUETOOTH_PROXY + SubscribeBluetoothConnectionsFreeRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); +#endif + this->on_subscribe_bluetooth_connections_free_request(msg); +#endif + break; + } default: return false; } @@ -708,18 +861,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc } this->subscribe_home_assistant_states(msg); } -void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_bluetooth_le_advertisements(msg); -} void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { if (!this->is_connection_setup()) { this->on_no_setup_connection(); @@ -884,6 +1025,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma this->media_player_command(msg); } #endif +void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->subscribe_bluetooth_le_advertisements(msg); +} +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_device_request(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_get_services(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_notify(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_subscribe_bluetooth_connections_free_request( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); + if (!this->send_bluetooth_connections_free_response(ret)) { + this->on_fatal_error(); + } +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 49426d1bbc..c7ef1468d8 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -158,6 +158,48 @@ class APIServerConnectionBase : public ProtoService { const SubscribeBluetoothLEAdvertisementsRequest &value){}; #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -175,7 +217,6 @@ class APIServerConnection : public APIServerConnectionBase { virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; - virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0; #ifdef USE_COVER @@ -210,6 +251,32 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; +#endif + virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -222,7 +289,6 @@ class APIServerConnection : public APIServerConnectionBase { void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override; #ifdef USE_COVER @@ -257,6 +323,31 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; #endif }; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 5851594955..965f08aa15 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -297,6 +297,51 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe client->send_bluetooth_le_advertisement(call); } } +void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { + BluetoothDeviceConnectionResponse call; + call.address = address; + call.connected = connected; + call.mtu = mtu; + call.error = error; + + for (auto &client : this->clients_) { + client->send_bluetooth_device_connection_response(call); + } +} + +void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { + BluetoothConnectionsFreeResponse call; + call.free = free; + call.limit = limit; + + for (auto &client : this->clients_) { + client->send_bluetooth_connections_free_response(call); + } +} + +void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_read_response(call); + } +} +void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_notify_data_response(call); + } +} +void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_get_services_response(call); + } +} +void APIServer::send_bluetooth_gatt_services_done(uint64_t address) { + BluetoothGATTGetServicesDoneResponse call; + call.address = address; + + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_get_services_done_response(call); + } +} #endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index dc892b2088..72ab55432c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -75,6 +75,12 @@ class APIServer : public Component, public Controller { void send_homeassistant_service_call(const HomeassistantServiceResponse &call); #ifdef USE_BLUETOOTH_PROXY void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); + void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK); + void send_bluetooth_connections_free(uint8_t free, uint8_t limit); + void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); + void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call); + void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call); + void send_bluetooth_gatt_services_done(uint64_t address); #endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index fd383eb6be..f90ca5cf54 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { } return -1; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_, - pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); return status; } @@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { uint8_t BedJetHub::set_notify_(const bool enable) { uint8_t status; if (enable) { - status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } } else { - status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status); @@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() { result = false; } else { this->char_handle_name_ = chr->handle; - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status); } @@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->dispatch_state_(false); break; } - case ESP_GATTC_OPEN_EVT: { - // FIXME: bug in BLEClient - this->parent_->conn_id = param->open.conn_id; - this->open_conn_id_ = param->open.conn_id; - break; - } - - case ESP_GATTC_CONNECT_EVT: { - if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) { - // FIXME: bug in BLEClient - ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(), - this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id); - this->parent_->conn_id = this->open_conn_id_; - } - break; - } case ESP_GATTC_SEARCH_CMPL_EVT: { auto result = this->discover_characteristics_(); @@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent_->conn_id) + if (param->read.conn_id != this->parent_->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (this->processing_) break; - if (param->notify.conn_id != this->parent_->conn_id) { + if (param->notify.conn_id != this->parent_->get_conn_id()) { ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x", - this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id); + this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id); // FIXME: bug in BLEClient holding wrong conn_id. } @@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (needs_extra) { // This means the packet was partial, so read the status characteristic to get the second part. // Ideally this will complete quickly. We won't process additional notification events until it does. - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str()); @@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) { // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits. uint16_t notify_en = enable ? 1 : 0; - auto status = - esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle, + sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); return status; } ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(), - enable ? "true" : "false", handle, this->parent_->conn_id); + enable ? "true" : "false", handle, this->parent_->get_conn_id()); return ESP_GATT_OK; } @@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); } void BedJetHub::dump_config() { ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str()); ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id); - ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->conn_id); + ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id()); LOG_UPDATE_INTERVAL(this) ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); for (auto *child : this->children_) { diff --git a/esphome/components/bedjet/bedjet_hub.h b/esphome/components/bedjet/bedjet_hub.h index c7583b78e9..f1479710a7 100644 --- a/esphome/components/bedjet/bedjet_hub.h +++ b/esphome/components/bedjet/bedjet_hub.h @@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo uint16_t char_handle_status_; uint16_t config_descr_status_; - uint8_t open_conn_id_ = -1; - uint8_t write_notify_config_descriptor_(bool enable); }; diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 4b7d5f5b8a..0f1f60e62b 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_ID, @@ -14,13 +14,12 @@ from esphome.const import ( ) from esphome import automation +AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["esp32_ble_tracker"] ble_client_ns = cg.esphome_ns.namespace("ble_client") -BLEClient = ble_client_ns.class_( - "BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient -) +BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase) BLEClientNode = ble_client_ns.class_("BLEClientNode") BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const") # Triggers diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 6918ab31b4..395950dd26 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -1,8 +1,8 @@ #include "automation.h" +#include #include #include -#include #include "esphome/core/log.h" @@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector &value) { } ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); esp_err_t err = - esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(), - const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_, + value.size(), const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_OK) { ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 38e64ebd76..ba5f78109e 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { void loop() override {} void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { - if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) + if (event == ESP_GATTC_DISCONNECT_EVT && + memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) this->node_state = espbt::ClientState::ESTABLISHED; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 5f58d8273f..3f86df32f5 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -1,8 +1,9 @@ -#include "esphome/core/log.h" +#include "ble_client.h" +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include "ble_client.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 @@ -11,22 +12,13 @@ namespace ble_client { static const char *const TAG = "ble_client"; -float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } - void BLEClient::setup() { - auto ret = esp_ble_gattc_app_register(this->app_id); - if (ret) { - ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); - this->mark_failed(); - } - this->set_states_(espbt::ClientState::IDLE); + BLEClientBase::setup(); this->enabled = true; } void BLEClient::loop() { - if (this->state() == espbt::ClientState::DISCOVERED) { - this->connect(); - } + BLEClientBase::loop(); for (auto *node : this->nodes_) node->loop(); } @@ -39,34 +31,7 @@ void BLEClient::dump_config() { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { if (!this->enabled) return false; - if (device.address_uint64() != this->address) - return false; - if (this->state() != espbt::ClientState::IDLE) - return false; - - ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); - this->set_states_(espbt::ClientState::DISCOVERED); - - auto addr = device.address_uint64(); - this->remote_bda[0] = (addr >> 40) & 0xFF; - this->remote_bda[1] = (addr >> 32) & 0xFF; - this->remote_bda[2] = (addr >> 24) & 0xFF; - this->remote_bda[3] = (addr >> 16) & 0xFF; - this->remote_bda[4] = (addr >> 8) & 0xFF; - this->remote_bda[5] = (addr >> 0) & 0xFF; - this->remote_addr_type = device.get_address_type(); - return true; -} - -std::string BLEClient::address_str() const { - char buf[20]; - sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff, - (uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff, - (uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff, - (uint8_t)(this->address >> 0) & 0xff); - std::string ret; - ret = buf; - return ret; + return BLEClientBase::parse_device(device); } void BLEClient::set_enabled(bool enabled) { @@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) { return; if (!enabled && this->state() != espbt::ClientState::IDLE) { ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); - auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); + auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); } @@ -82,125 +47,12 @@ void BLEClient::set_enabled(bool enabled) { this->enabled = enabled; } -void BLEClient::connect() { - ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); - auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); - this->set_states_(espbt::ClientState::IDLE); - } else { - this->set_states_(espbt::ClientState::CONNECTING); - } -} - void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, esp_ble_gattc_cb_param_t *param) { - if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) - return; - if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if) - return; - bool all_established = this->all_nodes_established_(); - switch (event) { - case ESP_GATTC_REG_EVT: { - if (param->reg.status == ESP_GATT_OK) { - ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); - this->gattc_if = esp_gattc_if; - } else { - ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); - } - break; - } - case ESP_GATTC_OPEN_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); - this->conn_id = param->open.conn_id; - if (param->open.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - break; - } - case ESP_GATTC_CONNECT_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); - if (this->conn_id != param->connect.conn_id) { - ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", - this->address_str().c_str(), param->connect.conn_id, this->conn_id); - } - auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); - } - break; - } - case ESP_GATTC_CFG_MTU_EVT: { - if (param->cfg_mtu.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, - param->cfg_mtu.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); - break; - } - case ESP_GATTC_DISCONNECT_EVT: { - if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { - return; - } - ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); - for (auto &svc : this->services_) - delete svc; // NOLINT(cppcoreguidelines-owning-memory) - this->services_.clear(); - this->set_states_(espbt::ClientState::IDLE); - break; - } - case ESP_GATTC_SEARCH_RES_EVT: { - BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) - ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); - ble_service->start_handle = param->search_res.start_handle; - ble_service->end_handle = param->search_res.end_handle; - ble_service->client = this; - this->services_.push_back(ble_service); - break; - } - case ESP_GATTC_SEARCH_CMPL_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); - for (auto &svc : this->services_) { - ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); - ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); - svc->parse_characteristics(); - } - this->set_states_(espbt::ClientState::CONNECTED); - this->set_state(espbt::ClientState::ESTABLISHED); - break; - } - case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); - if (descr == nullptr) { - ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); - break; - } - if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || - descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { - ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, - descr->uuid.to_string().c_str()); - break; - } - uint16_t notify_en = 1; - auto status = - esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); - } - break; - } + BLEClientBase::gattc_event_handler(event, esp_gattc_if, param); - default: - break; - } for (auto *node : this->nodes_) node->gattc_event_handler(event, esp_gattc_if, param); @@ -212,237 +64,20 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } -void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - switch (event) { - // This event is sent by the server when it requests security - case ESP_GAP_BLE_SEC_REQ_EVT: - ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - break; - // This event is sent once authentication has completed - case ESP_GAP_BLE_AUTH_CMPL_EVT: - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); - if (!param->ble_security.auth_cmpl.success) { - ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); - } else { - ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, - param->ble_security.auth_cmpl.auth_mode); - } - break; - // There are other events we'll want to implement at some point to support things like pass key - // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md - default: - break; +void BLEClient::set_state(espbt::ClientState state) { + BLEClientBase::set_state(state); + for (auto &node : nodes_) + node->node_state = state; +} + +bool BLEClient::all_nodes_established_() { + if (this->state() != espbt::ClientState::ESTABLISHED) + return false; + for (auto &node : nodes_) { + if (node->node_state != espbt::ClientState::ESTABLISHED) + return false; } -} - -// Parse GATT values into a float for a sensor. -// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ -float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { - // A length of one means a single octet value. - if (length == 0) - return 0; - if (length == 1) - return (float) ((uint8_t) value[0]); - - switch (value[0]) { - case 0x1: // boolean. - case 0x2: // 2bit. - case 0x3: // nibble. - case 0x4: // uint8. - return (float) ((uint8_t) value[1]); - case 0x5: // uint12. - case 0x6: // uint16. - if (length > 2) { - return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]); - } - case 0x7: // uint24. - if (length > 3) { - return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3])); - } - case 0x8: // uint32. - if (length > 4) { - return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) + - (uint32_t)(value[4])); - } - case 0xC: // int8. - return (float) ((int8_t) value[1]); - case 0xD: // int12. - case 0xE: // int16. - if (length > 2) { - return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); - } - case 0xF: // int24. - if (length > 3) { - return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); - } - case 0x10: // int32. - if (length > 4) { - return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + - (int32_t)(value[4])); - } - } - ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); - return NAN; -} - -BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto *svc : this->services_) { - if (svc->uuid == uuid) - return svc; - } - return nullptr; -} - -BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } - -BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - return svc->get_characteristic(chr); -} - -BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); -} - -BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) { - for (auto &svc : this->services_) { - for (auto &chr : svc->characteristics) { - if (chr->handle == handle) { - for (auto &desc : chr->descriptors) { - if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) - return desc; - } - } - } - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { - for (auto &chr : this->characteristics) { - if (chr->uuid == uuid) - return chr; - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); -} - -BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - auto *ch = svc->get_characteristic(chr); - if (ch == nullptr) - return nullptr; - return ch->get_descriptor(descr); -} - -BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), - espbt::ESPBTUUID::from_uint16(descr)); -} - -BLEService::~BLEService() { - for (auto &chr : this->characteristics) - delete chr; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLEService::parse_characteristics() { - uint16_t offset = 0; - esp_gattc_char_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_char( - this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) - characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - characteristic->properties = result.properties; - characteristic->handle = result.char_handle; - characteristic->service = this; - this->characteristics.push_back(characteristic); - ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), - characteristic->handle, characteristic->properties); - characteristic->parse_descriptors(); - offset++; - } -} - -BLECharacteristic::~BLECharacteristic() { - for (auto &desc : this->descriptors) - delete desc; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLECharacteristic::parse_descriptors() { - uint16_t offset = 0; - esp_gattc_descr_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_descr( - this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) - desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - desc->handle = result.handle; - desc->characteristic = this; - this->descriptors.push_back(desc); - ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); - offset++; - } -} - -BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { - for (auto &desc : this->descriptors) { - if (desc->uuid == uuid) - return desc; - } - return nullptr; -} -BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { - auto *client = this->service->client; - auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, - write_type, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); - } -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); + return true; } } // namespace ble_client diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 5ed8f219d1..0f8373ab1f 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -1,8 +1,9 @@ #pragma once +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #ifdef USE_ESP32 @@ -18,9 +19,9 @@ namespace ble_client { namespace espbt = esphome::esp32_ble_tracker; +using namespace esp32_ble_client; + class BLEClient; -class BLEService; -class BLECharacteristic; class BLEClientNode { public: @@ -42,57 +43,15 @@ class BLEClientNode { uint64_t address_; }; -class BLEDescriptor { - public: - espbt::ESPBTUUID uuid; - uint16_t handle; - - BLECharacteristic *characteristic; -}; - -class BLECharacteristic { - public: - ~BLECharacteristic(); - espbt::ESPBTUUID uuid; - uint16_t handle; - esp_gatt_char_prop_t properties; - std::vector descriptors; - void parse_descriptors(); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); - BLEDescriptor *get_descriptor(uint16_t uuid); - void write_value(uint8_t *new_val, int16_t new_val_size); - void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); - BLEService *service; -}; - -class BLEService { - public: - ~BLEService(); - espbt::ESPBTUUID uuid; - uint16_t start_handle; - uint16_t end_handle; - std::vector characteristics; - BLEClient *client; - void parse_characteristics(); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); - BLECharacteristic *get_characteristic(uint16_t uuid); -}; - -class BLEClient : public espbt::ESPBTClient, public Component { +class BLEClient : public BLEClientBase { public: void setup() override; void dump_config() override; void loop() override; - float get_setup_priority() const override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; - void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; - void on_scan_end() override {} - void connect() override; - - void set_address(uint64_t address) { this->address = address; } void set_enabled(bool enabled); @@ -102,43 +61,14 @@ class BLEClient : public espbt::ESPBTClient, public Component { this->nodes_.push_back(node); } - BLEService *get_service(espbt::ESPBTUUID uuid); - BLEService *get_service(uint16_t uuid); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); - BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); - BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); - // Get the configuration descriptor for the given characteristic handle. - BLEDescriptor *get_config_descriptor(uint16_t handle); - - float parse_char_value(uint8_t *value, uint16_t length); - - int gattc_if; - esp_bd_addr_t remote_bda; - esp_ble_addr_type_t remote_addr_type; - uint16_t conn_id; - uint64_t address; bool enabled; - std::string address_str() const; + + void set_state(espbt::ClientState state) override; protected: - void set_states_(espbt::ClientState st) { - this->set_state(st); - for (auto &node : nodes_) - node->node_state = st; - } - bool all_nodes_established_() { - if (this->state() != espbt::ClientState::ESTABLISHED) - return false; - for (auto &node : nodes_) { - if (node->node_state != espbt::ClientState::ESTABLISHED) - return false; - } - return true; - } + bool all_nodes_established_(); std::vector nodes_; - std::vector services_; }; } // namespace ble_client diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 2baaafe2ec..d830165d30 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger, public BLESensor { break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index ee5afd3f7b..a05efad60b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -122,8 +122,8 @@ void BLESensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(NAN); diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index be85892c5a..c504c35a58 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 1a71cd6cd8..1a304593c7 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -121,8 +121,8 @@ void BLETextSensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(EMPTY); diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index a6a5a4391b..b5acea89dd 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -1,19 +1,23 @@ -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ACTIVE, CONF_ID -DEPENDENCIES = ["esp32", "esp32_ble_tracker"] +AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] +DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz"] bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") -BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) +BluetoothProxy = bluetooth_proxy_ns.class_( + "BluetoothProxy", esp32_ble_client.BLEClientBase +) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(BluetoothProxy), + cv.Optional(CONF_ACTIVE, default=False): cv.boolean, } ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -22,6 +26,8 @@ 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_active(config[CONF_ACTIVE])) + + await esp32_ble_tracker.register_client(var, config) cg.add_define("USE_BLUETOOTH_PROXY") diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 41871295ce..96fee39f95 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -1,22 +1,39 @@ #include "bluetooth_proxy.h" -#ifdef USE_API -#include "esphome/components/api/api_pb2.h" -#include "esphome/components/api/api_server.h" -#endif // USE_API #include "esphome/core/log.h" #ifdef USE_ESP32 +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + namespace esphome { namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy"; +BluetoothProxy::BluetoothProxy() { + global_bluetooth_proxy = this; + this->address_ = 0; +} + bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), device.get_rssi()); this->send_api_packet_(device); + + this->address_type_map_[device.address_uint64()] = device.get_address_type(); + + if (this->address_ == 0) + return true; + + if (this->state_ == espbt::ClientState::DISCOVERED) { + ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str()); + return true; + } + + BLEClientBase::parse_device(device); return true; } @@ -35,23 +52,309 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi for (auto &data : device.get_service_datas()) { api::BluetoothServiceData service_data; service_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - service_data.data.push_back(d); - resp.service_data.push_back(service_data); + service_data.data.assign(data.data.begin(), data.data.end()); + resp.service_data.push_back(std::move(service_data)); } for (auto &data : device.get_manufacturer_datas()) { api::BluetoothServiceData manufacturer_data; manufacturer_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - manufacturer_data.data.push_back(d); - resp.manufacturer_data.push_back(manufacturer_data); + manufacturer_data.data.assign(data.data.begin(), data.data.end()); + resp.manufacturer_data.push_back(std::move(manufacturer_data)); } api::global_api_server->send_bluetooth_le_advertisement(resp); #endif } +void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + BLEClientBase::gattc_event_handler(event, gattc_if, param); + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, + param->disconnect.reason); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + this->address_ = 0; + } + case ESP_GATTC_OPEN_EVT: { + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status); + +#endif + break; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + break; + } + case ESP_GATTC_READ_DESCR_EVT: + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->conn_id_) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status); + break; + } +#ifdef USE_API + api::BluetoothGATTReadResponse resp; + resp.address = this->address_; + resp.handle = param->read.handle; + resp.data.reserve(param->read.value_len); + for (uint16_t i = 0; i < param->read.value_len; i++) { + resp.data.push_back(param->read.value[i]); + } + api::global_api_server->send_bluetooth_gatt_read_response(resp); +#endif + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->conn_id_) + break; + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle); +#ifdef USE_API + api::BluetoothGATTNotifyDataResponse resp; + resp.address = this->address_; + resp.handle = param->notify.handle; + resp.data.reserve(param->notify.value_len); + for (uint16_t i = 0; i < param->notify.value_len; i++) { + resp.data.push_back(param->notify.value[i]); + } + api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); +#endif + break; + } + default: + break; + } +} + void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } +void BluetoothProxy::loop() { + BLEClientBase::loop(); +#ifdef USE_API + if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + + if (this->send_service_ == this->services_.size()) { + this->send_service_ = -1; + api::global_api_server->send_bluetooth_gatt_services_done(this->address_); + } else if (this->send_service_ >= 0) { + auto &service = this->services_[this->send_service_]; + api::BluetoothGATTGetServicesResponse resp; + resp.address = this->address_; + api::BluetoothGATTService service_resp; + service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()}; + service_resp.handle = service->start_handle; + for (auto &characteristic : service->characteristics) { + api::BluetoothGATTCharacteristic characteristic_resp; + characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()}; + characteristic_resp.handle = characteristic->handle; + characteristic_resp.properties = characteristic->properties; + for (auto &descriptor : characteristic->descriptors) { + api::BluetoothGATTDescriptor descriptor_resp; + descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()}; + descriptor_resp.handle = descriptor->handle; + characteristic_resp.descriptors.push_back(std::move(descriptor_resp)); + } + service_resp.characteristics.push_back(std::move(characteristic_resp)); + } + resp.services.push_back(std::move(service_resp)); + api::global_api_server->send_bluetooth_gatt_services(resp); + this->send_service_++; + } +#endif +} + +#ifdef USE_API +void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { + switch (msg.request_type) { + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { + this->address_ = msg.address; + if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) { + // Utilise the address type cache + this->remote_addr_type_ = this->address_type_map_[this->address_]; + } else { + this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC; + } + this->remote_bda_[0] = (this->address_ >> 40) & 0xFF; + this->remote_bda_[1] = (this->address_ >> 32) & 0xFF; + this->remote_bda_[2] = (this->address_ >> 24) & 0xFF; + this->remote_bda_[3] = (this->address_ >> 16) & 0xFF; + this->remote_bda_[4] = (this->address_ >> 8) & 0xFF; + this->remote_bda_[5] = (this->address_ >> 0) & 0xFF; + this->set_state(espbt::ClientState::DISCOVERED); + esp_ble_gap_stop_scanning(); + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { + if (this->state() != espbt::ClientState::IDLE) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + break; + } +} + +void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str()); + characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(), + msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP); +} + +void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(), + (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for service list request"); + return; + } + this->send_service_ = 0; +} + +void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for notify"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found."); + return; + } + + esp_err_t err; + if (msg.enable) { + err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err); + } + } else { + err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err); + } + } +} + +#endif + +BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 9a936747c0..8ff43aff3f 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -4,22 +4,59 @@ #include +#include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#include + +#ifdef USE_API +#include "esphome/components/api/api_pb2.h" +#endif // USE_API namespace esphome { namespace bluetooth_proxy { -class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +using namespace esp32_ble_client; + +class BluetoothProxy : public BLEClientBase { public: + BluetoothProxy(); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + +#ifdef USE_API + void bluetooth_device_request(const api::BluetoothDeviceRequest &msg); + void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg); + void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg); + void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg); + void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg); + void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg); + void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg); +#endif + + int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; } + int get_bluetooth_connections_limit() { return 1; } + + void set_active(bool active) { this->active_ = active; } + bool has_active() { return this->active_; } protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); + + std::map address_type_map_; + int16_t send_service_{-1}; + bool active_; }; +extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py new file mode 100644 index 0000000000..94a5576d0b --- /dev/null +++ b/esphome/components/esp32_ble_client/__init__.py @@ -0,0 +1,12 @@ +import esphome.codegen as cg + +from esphome.components import esp32_ble_tracker + +AUTO_LOAD = ["esp32_ble_tracker"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["esp32"] + +esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client") +BLEClientBase = esp32_ble_client_ns.class_( + "BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component +) diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp new file mode 100644 index 0000000000..873833368c --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -0,0 +1,83 @@ +#include "ble_characteristic.h" +#include "ble_client_base.h" +#include "ble_service.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.characteristic"; + +BLECharacteristic::~BLECharacteristic() { + for (auto &desc : this->descriptors) + delete desc; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLECharacteristic::parse_descriptors() { + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(), + this->handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) + desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + desc->handle = result.handle; + desc->characteristic = this; + this->descriptors.push_back(desc); + ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); + offset++; + } +} + +BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { + for (auto &desc : this->descriptors) { + if (desc->uuid == uuid) + return desc; + } + return nullptr; +} +BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); +} +BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) { + for (auto &desc : this->descriptors) { + if (desc->handle == handle) + return desc; + } + return nullptr; +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { + auto *client = this->service->client; + auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size, + new_val, write_type, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); + } +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_characteristic.h b/esphome/components/esp32_ble_client/ble_characteristic.h new file mode 100644 index 0000000000..ffa9227cc4 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_descriptor.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEService; + +class BLECharacteristic { + public: + ~BLECharacteristic(); + espbt::ESPBTUUID uuid; + uint16_t handle; + esp_gatt_char_prop_t properties; + std::vector descriptors; + void parse_descriptors(); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); + BLEDescriptor *get_descriptor(uint16_t uuid); + BLEDescriptor *get_descriptor_by_handle(uint16_t handle); + void write_value(uint8_t *new_val, int16_t new_val_size); + void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); + BLEService *service; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp new file mode 100644 index 0000000000..0e81c9aca8 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -0,0 +1,324 @@ +#include "ble_client_base.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client"; + +void BLEClientBase::setup() { + auto ret = esp_ble_gattc_app_register(this->app_id); + if (ret) { + ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); + this->mark_failed(); + } + this->set_state(espbt::ClientState::IDLE); +} + +void BLEClientBase::loop() { + if (this->state_ == espbt::ClientState::DISCOVERED) { + this->connect(); + } +} + +float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + +bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) + return false; + if (this->state_ != espbt::ClientState::IDLE) + return false; + + ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); + this->set_state(espbt::ClientState::DISCOVERED); + + auto addr = device.address_uint64(); + this->remote_bda_[0] = (addr >> 40) & 0xFF; + this->remote_bda_[1] = (addr >> 32) & 0xFF; + this->remote_bda_[2] = (addr >> 24) & 0xFF; + this->remote_bda_[3] = (addr >> 16) & 0xFF; + this->remote_bda_[4] = (addr >> 8) & 0xFF; + this->remote_bda_[5] = (addr >> 0) & 0xFF; + this->remote_addr_type_ = device.get_address_type(); + return true; +} + +std::string BLEClientBase::address_str() const { + return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff, + (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff, + (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff, + (uint8_t)(this->address_ >> 0) & 0xff); +} + +void BLEClientBase::connect() { + ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); + auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); + this->set_state(espbt::ClientState::IDLE); + } else { + this->set_state(espbt::ClientState::CONNECTING); + } +} + +void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, + esp_ble_gattc_cb_param_t *param) { + if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) + return; + if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_) + return; + + switch (event) { + case ESP_GATTC_REG_EVT: { + if (param->reg.status == ESP_GATT_OK) { + ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); + this->gattc_if_ = esp_gattc_if; + } else { + ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); + } + break; + } + case ESP_GATTC_OPEN_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); + this->conn_id_ = param->open.conn_id; + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { + ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + break; + } + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); + if (this->conn_id_ != param->connect.conn_id) { + ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", + this->address_str().c_str(), param->connect.conn_id, this->conn_id_); + } + auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); + } + break; + } + case ESP_GATTC_CFG_MTU_EVT: { + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, + param->cfg_mtu.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); + this->mtu_ = param->cfg_mtu.mtu; + esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) { + return; + } + ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); + for (auto &svc : this->services_) + delete svc; // NOLINT(cppcoreguidelines-owning-memory) + this->services_.clear(); + this->set_state(espbt::ClientState::IDLE); + break; + } + case ESP_GATTC_SEARCH_RES_EVT: { + BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) + ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); + ble_service->start_handle = param->search_res.start_handle; + ble_service->end_handle = param->search_res.end_handle; + ble_service->client = this; + this->services_.push_back(ble_service); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); + for (auto &svc : this->services_) { + ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); + ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); + svc->parse_characteristics(); + } + this->set_state(espbt::ClientState::CONNECTED); + this->state_ = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); + if (descr == nullptr) { + ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); + break; + } + if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || + descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, + descr->uuid.to_string().c_str()); + break; + } + uint16_t notify_en = 1; + auto status = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en), + (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); + } + break; + } + + default: + break; + } +} + +void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // This event is sent by the server when it requests security + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + // This event is sent once authentication has completed + case ESP_GAP_BLE_AUTH_CMPL_EVT: + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, + param->ble_security.auth_cmpl.auth_mode); + } + break; + // There are other events we'll want to implement at some point to support things like pass key + // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md + default: + break; + } +} + +// Parse GATT values into a float for a sensor. +// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ +float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { + // A length of one means a single octet value. + if (length == 0) + return 0; + if (length == 1) + return (float) ((uint8_t) value[0]); + + switch (value[0]) { + case 0x1: // boolean. + case 0x2: // 2bit. + case 0x3: // nibble. + case 0x4: // uint8. + return (float) ((uint8_t) value[1]); + case 0x5: // uint12. + case 0x6: // uint16. + if (length > 2) { + return (float) encode_uint16(value[1], value[2]); + } + case 0x7: // uint24. + if (length > 3) { + return (float) encode_uint24(value[1], value[2], value[3]); + } + case 0x8: // uint32. + if (length > 4) { + return (float) encode_uint32(value[1], value[2], value[3], value[4]); + } + case 0xC: // int8. + return (float) ((int8_t) value[1]); + case 0xD: // int12. + case 0xE: // int16. + if (length > 2) { + return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); + } + case 0xF: // int24. + if (length > 3) { + return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); + } + case 0x10: // int32. + if (length > 4) { + return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + + (int32_t)(value[4])); + } + } + ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); + return NAN; +} + +BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) { + for (auto *svc : this->services_) { + if (svc->uuid == uuid) + return svc; + } + return nullptr; +} + +BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } + +BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + return svc->get_characteristic(chr); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + if (chr->handle == handle) + return chr; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) { + auto *chr = this->get_characteristic(handle); + if (chr != nullptr) { + for (auto &desc : chr->descriptors) { + if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) + return desc; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + auto *ch = svc->get_characteristic(chr); + if (ch == nullptr) + return nullptr; + return ch->get_descriptor(descr); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), + espbt::ESPBTUUID::from_uint16(descr)); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + for (auto *desc : chr->descriptors) { + if (desc->handle == handle) + return desc; + } + } + } + return nullptr; +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h new file mode 100644 index 0000000000..eba70fa571 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -0,0 +1,72 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/component.h" + +#include "ble_service.h" + +#include +#include + +#include +#include +#include +#include + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase : public espbt::ESPBTClient, public Component { + public: + void setup() override; + void loop() override; + float get_setup_priority() const override; + + bool parse_device(const espbt::ESPBTDevice &device) override; + void on_scan_end() override {} + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + void connect() override; + + void set_address(uint64_t address) { this->address_ = address; } + std::string address_str() const; + + BLEService *get_service(espbt::ESPBTUUID uuid); + BLEService *get_service(uint16_t uuid); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); + BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); + BLECharacteristic *get_characteristic(uint16_t handle); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); + BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); + BLEDescriptor *get_descriptor(uint16_t handle); + // Get the configuration descriptor for the given characteristic handle. + BLEDescriptor *get_config_descriptor(uint16_t handle); + + float parse_char_value(uint8_t *value, uint16_t length); + + int get_gattc_if() const { return this->gattc_if_; } + uint8_t *get_remote_bda() { return this->remote_bda_; } + esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; } + uint16_t get_conn_id() const { return this->conn_id_; } + uint64_t get_address() const { return this->address_; } + + protected: + int gattc_if_; + esp_bd_addr_t remote_bda_; + esp_ble_addr_type_t remote_addr_type_; + uint16_t conn_id_; + uint64_t address_; + uint16_t mtu_{23}; + + std::vector services_; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_descriptor.h b/esphome/components/esp32_ble_client/ble_descriptor.h new file mode 100644 index 0000000000..c05430144f --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_descriptor.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLECharacteristic; + +class BLEDescriptor { + public: + espbt::ESPBTUUID uuid; + uint16_t handle; + + BLECharacteristic *characteristic; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp new file mode 100644 index 0000000000..1d81eaa556 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -0,0 +1,66 @@ +#include "ble_service.h" +#include "ble_client_base.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.service"; + +BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { + for (auto &chr : this->characteristics) { + if (chr->uuid == uuid) + return chr; + } + return nullptr; +} + +BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); +} + +BLEService::~BLEService() { + for (auto &chr : this->characteristics) + delete chr; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLEService::parse_characteristics() { + uint16_t offset = 0; + esp_gattc_char_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle, + this->end_handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) + characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + characteristic->properties = result.properties; + characteristic->handle = result.char_handle; + characteristic->service = this; + this->characteristics.push_back(characteristic); + ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), + characteristic->handle, characteristic->properties); + characteristic->parse_descriptors(); + offset++; + } +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.h b/esphome/components/esp32_ble_client/ble_service.h new file mode 100644 index 0000000000..04f2212e0e --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_characteristic.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase; + +class BLEService { + public: + ~BLEService(); + espbt::ESPBTUUID uuid; + uint16_t start_handle; + uint16_t end_handle; + std::vector characteristics; + BLEClientBase *client; + void parse_characteristics(); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); + BLECharacteristic *get_characteristic(uint16_t uuid); +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 6d7868d4c5..0d7abe32f9 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.const import ( + CONF_ACTIVE, CONF_ID, CONF_INTERVAL, CONF_DURATION, @@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" -CONF_ACTIVE = "active" CONF_CONTINUOUS = "continuous" CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 47c1f6022e..4aaa6dfa32 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } std::string ESPBTUUID::to_string() const { - char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: - sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); - break; + return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); case ESP_UUID_LEN_32: - sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff), - (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff); - break; + return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24, + (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff), + this->uuid_.uuid.uuid32 & 0xff); default: case ESP_UUID_LEN_128: - char *bpos = sbuf; + std::string buf; for (int8_t i = 15; i >= 0; i--) { - sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]); - bpos += 2; + buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]); if (i == 6 || i == 8 || i == 10 || i == 12) - sprintf(bpos++, "-"); + buf += "-"; } - sbuf[47] = '\0'; - break; + return buf; } - return sbuf; + return ""; +} + +uint64_t ESPBTUUID::get_128bit_high() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | + ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) | + ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) | + ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]); +} +uint64_t ESPBTUUID::get_128bit_low() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) | + ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) | + ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) | + ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]); } ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 29d0c81542..45abcd63fa 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -41,6 +41,9 @@ class ESPBTUUID { std::string to_string() const; + uint64_t get_128bit_high() const; + uint64_t get_128bit_low() const; + protected: esp_bt_uuid_t uuid_; }; @@ -158,7 +161,7 @@ class ESPBTClient : public ESPBTDeviceListener { esp_ble_gattc_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void connect() = 0; - void set_state(ClientState st) { this->state_ = st; } + virtual void set_state(ClientState st) { this->state_ = st; } ClientState state() const { return state_; } int app_id; diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index f09b2ca8d7..f3a2b3cb3c 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -72,13 +72,13 @@ class BLEEvent { // Need to also make a copy of relevant event data. switch (e) { case ESP_GATTC_NOTIFY_EVT: - memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len); - this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data; + this->data.assign(p->notify.value, p->notify.value + p->notify.value_len); + this->event_.gattc.gattc_param.notify.value = this->data.data(); break; case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_DESCR_EVT: - memcpy(this->event_.gattc.data, p->read.value, p->read.value_len); - this->event_.gattc.gattc_param.read.value = this->event_.gattc.data; + this->data.assign(p->read.value, p->read.value + p->read.value_len); + this->event_.gattc.gattc_param.read.value = this->data.data(); break; default: break; @@ -96,9 +96,9 @@ class BLEEvent { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; - uint8_t data[64]; } gattc; } event_; + std::vector data{}; uint8_t type_; // 0=gap 1=gattc }; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 21638ef7e4..384537e5d7 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -102,8 +102,9 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size, + blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } else { diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 6bb17f0508..3959178b94 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -50,7 +50,7 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -146,17 +146,17 @@ void RadonEyeRD200::update() { void RadonEyeRD200::write_query_message_() { ESP_LOGV(TAG, "writing 0x50 to write service"); int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, - sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->write_handle_, sizeof(request), (uint8_t *) &request, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); } } void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->read_handle_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/const.py b/esphome/const.py index 22f030e84e..d12708cdd6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -23,6 +23,7 @@ CONF_ACCURACY = "accuracy" CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACTION_ID = "action_id" CONF_ACTION_STATE_TOPIC = "action_state_topic" +CONF_ACTIVE = "active" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" From e8ff36d1f3766950c13bafcbcf0bd05d8184edc8 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Mon, 3 Oct 2022 18:50:33 -0300 Subject: [PATCH 107/838] fix dump preset string type (#3863) --- esphome/components/thermostat/thermostat_climate.cpp | 4 +--- esphome/components/thermostat/thermostat_climate.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a9b03187d3..54e9f1687c 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -935,10 +935,8 @@ bool ThermostatClimate::supplemental_heating_required_() { (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING)); } -void ThermostatClimate::dump_preset_config_(const std::string &preset, const ThermostatClimateTargetTempConfig &config, +void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config, bool is_default_preset) { - const auto *preset_name = preset.c_str(); - ESP_LOGCONFIG(TAG, " %s Is Default: %s", preset_name, YESNO(is_default_preset)); if (this->supports_heat_) { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index aa7529cfb1..a738ba4986 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -228,7 +228,7 @@ class ThermostatClimate : public climate::Climate, public Component { bool supplemental_cooling_required_(); bool supplemental_heating_required_(); - void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config, + void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config, bool is_default_preset); /// The sensor used for getting the current temperature From 16249c02a5800a90c134bafa4e770d64a43f0317 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:21:13 +1300 Subject: [PATCH 108/838] Bump CI to python 3.9 (#3869) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5b7521b3f..671b74d118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: uses: actions/setup-python@v4 id: python with: - python-version: "3.8" + python-version: "3.9" - name: Cache virtualenv uses: actions/cache@v3 From 05edfd0e82882f763253e72e1bc7e0c2f3963119 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 5 Oct 2022 00:50:03 +0200 Subject: [PATCH 109/838] Add cg.with_local_variable (#3577) --- esphome/codegen.py | 1 + esphome/components/wifi/__init__.py | 16 ++++++---- esphome/cpp_generator.py | 45 +++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 185e6599b1..ef5b490004 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -22,6 +22,7 @@ from esphome.cpp_generator import ( # noqa static_const_array, statement, variable, + with_local_variable, new_variable, Pvariable, new_Pvariable, diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 846a8e1303..a4c246da89 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -332,8 +332,7 @@ def manual_ip(config): ) -def wifi_network(config, static_ip): - ap = cg.variable(config[CONF_ID], WiFiAP()) +def wifi_network(config, ap, static_ip): if CONF_SSID in config: cg.add(ap.set_ssid(config[CONF_SSID])) if CONF_PASSWORD in config: @@ -360,14 +359,21 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) - for network in config.get(CONF_NETWORKS, []): + def add_sta(ap, network): ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) - cg.add(var.add_sta(wifi_network(network, ip_config))) + cg.add(var.add_sta(wifi_network(network, ap, ip_config))) + + for network in config.get(CONF_NETWORKS, []): + cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network) if CONF_AP in config: conf = config[CONF_AP] ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) - cg.add(var.set_ap(wifi_network(conf, ip_config))) + cg.with_local_variable( + conf[CONF_ID], + WiFiAP(), + 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(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 42828450e8..df806af1a5 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -5,7 +5,17 @@ import re from esphome.yaml_util import ESPHomeDataBase # pylint: disable=unused-import, wrong-import-order -from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence +from typing import ( + Any, + Callable, + Generator, + List, + Optional, + Tuple, + Type, + Union, + Sequence, +) from esphome.core import ( # noqa CORE, @@ -468,7 +478,9 @@ def statement(expression: Union[Expression, Statement]) -> Statement: return ExpressionStatement(expression) -def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": +def variable( + id_: ID, rhs: SafeExpType, type_: "MockObj" = None, register=True +) -> "MockObj": """Declare a new variable, not pointer type, in the code generation. :param id_: The ID used to declare the variable. @@ -485,10 +497,37 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": id_.type = type_ assignment = AssignmentExpression(id_.type, "", id_, rhs) CORE.add(assignment) - CORE.register_variable(id_, obj) + if register: + CORE.register_variable(id_, obj) return obj +def with_local_variable( + id_: ID, rhs: SafeExpType, callback: Callable[["MockObj"], None], *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. + + :param id_: The ID used to declare the variable. + :param rhs: The expression to place on the right hand side of the assignment. + :param callback: The function to invoke that will receive the temporary variable + :param args: args to pass to the callback in addition to the temporary variable + + """ + + # throw if the callback is async: + assert not inspect.iscoroutinefunction( + callback + ), "with_local_variable() callback cannot be async!" + + CORE.add(RawStatement("{")) # output opening curly brace + obj = variable(id_, rhs, None, True) + # invoke user-provided callback to generate code with this local variable + callback(obj, *args) + CORE.add(RawStatement("}")) # output closing curly brace + + def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": """Declare and define a new variable, not pointer type, in the code generation. From 584b722e7ed81c116faba601f46f20d952031a45 Mon Sep 17 00:00:00 2001 From: Javier Peletier Date: Wed, 5 Oct 2022 03:52:45 +0200 Subject: [PATCH 110/838] Fix time/automation (cron) wdt crash when time jumps ahead too much (#3844) --- esphome/components/time/automation.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 7e16d7141f..af2b6c720c 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -6,6 +6,8 @@ namespace esphome { namespace time { static const char *const TAG = "automation"; +static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider + // there has been a drastic time synchronization void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; } void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; } @@ -23,12 +25,17 @@ void CronTrigger::loop() { return; if (this->last_check_.has_value()) { - if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { + if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) { // We went back in time (a lot), probably caused by time synchronization ESP_LOGW(TAG, "Time has jumped back!"); } else if (*this->last_check_ >= time) { // already handled this one return; + } else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) { + // We went ahead in time (a lot), probably caused by time synchronization + ESP_LOGW(TAG, "Time has jumped ahead!"); + this->last_check_ = time; + return; } while (true) { From 263b6031886a097680a636e4f075c72eef02113b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 16:29:49 +1300 Subject: [PATCH 111/838] Bump pyupgrade from 2.37.3 to 3.0.0 (#3867) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 083aea117d..d53263dcef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --branch=release - --branch=beta - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v3.0.0 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/requirements_test.txt b/requirements_test.txt index 5ef791906d..e7b6387b73 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.15.2 flake8==5.0.4 black==22.8.0 # also change in .pre-commit-config.yaml when updating -pyupgrade==2.37.3 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.0.0 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests From c3a89725509ebfd97bcb191ffec41764ef71dc70 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Oct 2022 16:30:56 +1300 Subject: [PATCH 112/838] Add min_version to esphome config (#3866) --- .../external_components/__init__.py | 2 +- esphome/components/packages/__init__.py | 57 +++++++++++++++---- esphome/config_validation.py | 4 +- esphome/const.py | 1 + esphome/core/config.py | 15 +++++ esphome/git.py | 16 +++++- 6 files changed, 77 insertions(+), 18 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index d0153f6104..53fd337ed8 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -98,7 +98,7 @@ async def to_code(config): def _process_git_config(config: dict, refresh) -> str: - repo_dir = git.clone_or_update( + repo_dir, _ = git.clone_or_update( url=config[CONF_URL], ref=config.get(CONF_REF), refresh=refresh, diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 67220cae08..3b5a6a5908 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config from esphome import git, yaml_util from esphome.const import ( + CONF_ESPHOME, CONF_FILE, CONF_FILES, + CONF_MIN_VERSION, CONF_PACKAGES, CONF_REF, CONF_REFRESH, CONF_URL, CONF_USERNAME, CONF_PASSWORD, + __version__ as ESPHOME_VERSION, ) import esphome.config_validation as cv @@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All( def _process_base_package(config: dict) -> dict: - repo_dir = git.clone_or_update( + repo_dir, revert = git.clone_or_update( url=config[CONF_URL], ref=config.get(CONF_REF), refresh=config[CONF_REFRESH], @@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict: username=config.get(CONF_USERNAME), password=config.get(CONF_PASSWORD), ) - files: str = config[CONF_FILES] + files: list[str] = config[CONF_FILES] + + def get_packages(files) -> dict: + packages = {} + for file in files: + yaml_file: Path = repo_dir / file + + if not yaml_file.is_file(): + raise cv.Invalid( + f"{file} does not exist in repository", path=[CONF_FILES] + ) + + try: + new_yaml = yaml_util.load_yaml(yaml_file) + if ( + CONF_ESPHOME in new_yaml + and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME] + ): + min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION] + if cv.Version.parse(min_version) > cv.Version.parse( + ESPHOME_VERSION + ): + raise cv.Invalid( + f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}" + ) + + packages[file] = new_yaml + except EsphomeError as e: + raise cv.Invalid( + f"{file} is not a valid YAML file. Please check the file contents." + ) from e + return packages packages = {} - for file in files: - yaml_file: Path = repo_dir / file - if not yaml_file.is_file(): - raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES]) + 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") - try: - packages[file] = yaml_util.load_yaml(yaml_file) - except EsphomeError as e: - raise cv.Invalid( - f"{file} is not a valid YAML file. Please check the file contents." - ) from e return {"packages": packages} diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0ff0ba83d9..09436c1fbf 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1689,7 +1689,7 @@ class Version: @classmethod def parse(cls, value: str) -> "Version": - match = re.match(r"(\d+).(\d+).(\d+)", value) + match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value) if match is None: raise ValueError(f"Not a valid version number {value}") major = int(match[1]) @@ -1703,7 +1703,7 @@ def version_number(value): try: return str(Version.parse(value)) except ValueError as e: - raise Invalid("Not a version number") from e + raise Invalid("Not a valid version number") from e def platformio_version_constraint(value): diff --git a/esphome/const.py b/esphome/const.py index d12708cdd6..0f056498e4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -396,6 +396,7 @@ CONF_MIN_POWER = "min_power" CONF_MIN_RANGE = "min_range" CONF_MIN_TEMPERATURE = "min_temperature" CONF_MIN_VALUE = "min_value" +CONF_MIN_VERSION = "min_version" CONF_MINUTE = "minute" CONF_MINUTES = "minutes" CONF_MISO_PIN = "miso_pin" diff --git a/esphome/core/config.py b/esphome/core/config.py index 82cf37d44d..733cdcd66e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_FRAMEWORK, CONF_INCLUDES, CONF_LIBRARIES, + CONF_MIN_VERSION, CONF_NAME, CONF_ON_BOOT, CONF_ON_LOOP, @@ -30,6 +31,7 @@ from esphome.const import ( KEY_CORE, TARGET_PLATFORMS, PLATFORM_ESP8266, + __version__ as ESPHOME_VERSION, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -96,6 +98,16 @@ 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 + + CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONFIG_SCHEMA = cv.All( cv.Schema( @@ -136,6 +148,9 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_VERSION): cv.string_strict, } ), + cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All( + cv.version_number, validate_version + ), } ), validate_hostname, diff --git a/esphome/git.py b/esphome/git.py index 64c8d6a6b7..54fedc035f 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import hashlib import logging +from typing import Callable, Optional import urllib.parse from datetime import datetime @@ -12,7 +13,7 @@ import esphome.config_validation as cv _LOGGER = logging.getLogger(__name__) -def run_git_command(cmd, cwd=None): +def run_git_command(cmd, cwd=None) -> str: try: ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) except FileNotFoundError as err: @@ -28,6 +29,8 @@ def run_git_command(cmd, cwd=None): raise cv.Invalid(lines[-1][len("fatal: ") :]) raise cv.Invalid(err_str) + return ret.stdout.decode("utf-8").strip() + def _compute_destination_path(key: str, domain: str) -> Path: base_dir = Path(CORE.config_dir) / ".esphome" / domain @@ -44,7 +47,7 @@ def clone_or_update( domain: str, username: str = None, password: str = None, -) -> Path: +) -> tuple[Path, Optional[Callable[[], None]]]: key = f"{url}@{ref}" if username is not None and password is not None: @@ -78,6 +81,7 @@ def clone_or_update( file_timestamp = Path(repo_dir / ".git" / "HEAD") age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime) if age.total_seconds() > refresh.total_seconds: + old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir)) _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) @@ -92,4 +96,10 @@ def clone_or_update( # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) - return repo_dir + def revert(): + _LOGGER.info("Reverting changes to %s -> %s", key, old_sha) + run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir)) + + return repo_dir, revert + + return repo_dir, None From d220d41182e1b914a2eba39670f3c7396b630e38 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Oct 2022 20:09:27 +1300 Subject: [PATCH 113/838] Bump python min to 3.9 (#3871) --- .pre-commit-config.yaml | 2 +- esphome/components/esp8266/gpio.py | 3 +- esphome/components/neopixelbus/_methods.py | 4 +- esphome/components/select/__init__.py | 7 +-- esphome/config.py | 52 ++++++++--------- esphome/config_helpers.py | 3 +- esphome/core/__init__.py | 20 +++---- esphome/coroutine.py | 7 ++- esphome/cpp_generator.py | 26 ++++----- esphome/cpp_helpers.py | 5 +- esphome/dashboard/dashboard.py | 6 +- esphome/final_validate.py | 4 +- esphome/helpers.py | 2 +- esphome/loader.py | 18 +++--- esphome/platformio_api.py | 4 +- esphome/storage_json.py | 68 +++++++++++----------- esphome/types.py | 8 +-- esphome/util.py | 7 +-- esphome/vscode.py | 6 +- esphome/writer.py | 8 +-- esphome/zeroconf.py | 6 +- pyproject.toml | 2 +- script/lint-python | 2 +- setup.py | 2 +- tests/unit_tests/strategies.py | 5 +- 25 files changed, 130 insertions(+), 147 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d53263dcef..9c5610d9dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,4 +30,4 @@ repos: rev: v3.0.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index cf33ec126b..d4b2078524 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -1,6 +1,5 @@ import logging from dataclasses import dataclass -from typing import List from esphome.const import ( CONF_ID, @@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config): @coroutine_with_priority(-999.0) async def add_pin_initial_states_array(): # Add includes at the very end, so that they override everything - initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][ + initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][ KEY_PIN_INITIAL_STATES ] initial_modes_s = ", ".join(str(x.mode) for x in initial_states) diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py index 4e3c3ca778..98a2d152e1 100644 --- a/esphome/components/neopixelbus/_methods.py +++ b/esphome/components/neopixelbus/_methods.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, List +from typing import Any import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( @@ -349,7 +349,7 @@ def _spi_extra_validate(config): class MethodDescriptor: method_schema: Any to_code: Any - supported_chips: List[str] + supported_chips: list[str] extra_validate: Any = None diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index a1c73c385e..b505d89c6f 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -1,4 +1,3 @@ -from typing import List import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -60,7 +59,7 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e ) -async def setup_select_core_(var, config, *, options: List[str]): +async def setup_select_core_(var, config, *, options: list[str]): await setup_entity(var, config) cg.add(var.traits.set_options(options)) @@ -76,14 +75,14 @@ async def setup_select_core_(var, config, *, options: List[str]): await mqtt.register_mqtt_component(mqtt_, config) -async def register_select(var, config, *, options: List[str]): +async def register_select(var, config, *, options: list[str]): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_select(var)) await setup_select_core_(var, config, options=options) -async def new_select(config, *, options: List[str]): +async def new_select(config, *, options: list[str]): var = cg.new_Pvariable(config[CONF_ID]) await register_select(var, config, options=options) return var diff --git a/esphome/config.py b/esphome/config.py index 04717be6f5..0bf4ec8df3 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -23,7 +23,7 @@ from esphome.core import CORE, EsphomeError from esphome.helpers import indent from esphome.util import safe_print, OrderedDict -from typing import List, Optional, Tuple, Union +from typing import Optional, Union from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid @@ -50,10 +50,10 @@ def iter_components(config): yield p_name, platform, p_config -ConfigPath = List[Union[str, int]] +ConfigPath = list[Union[str, int]] -def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool +def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool: if len(path) < len(other): return False return path[: len(other)] == other @@ -67,7 +67,7 @@ class _ValidationStepTask: self.step = step @property - def _cmp_tuple(self) -> Tuple[float, int]: + def _cmp_tuple(self) -> tuple[float, int]: return (-self.priority, self.id_number) def __eq__(self, other): @@ -84,21 +84,20 @@ class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() # A list of voluptuous errors - self.errors = [] # type: List[vol.Invalid] + self.errors: list[vol.Invalid] = [] # A list of paths that should be fully outputted # The values will be the paths to all "domain", for example (['logger'], 'logger') # or (['sensor', 'ultrasonic'], 'sensor.ultrasonic') - self.output_paths = [] # type: List[Tuple[ConfigPath, str]] + self.output_paths: list[tuple[ConfigPath, str]] = [] # A list of components ids with the config path - self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + self.declare_ids: list[tuple[core.ID, ConfigPath]] = [] self._data = {} # Store pending validation tasks (in heap order) - self._validation_tasks: List[_ValidationStepTask] = [] + self._validation_tasks: list[_ValidationStepTask] = [] # ID to ensure stable order for keys with equal priority self._validation_tasks_id = 0 - def add_error(self, error): - # type: (vol.Invalid) -> None + def add_error(self, error: vol.Invalid) -> None: if isinstance(error, vol.MultipleInvalid): for err in error.errors: self.add_error(err) @@ -132,20 +131,16 @@ class Config(OrderedDict, fv.FinalValidateConfig): e.prepend(path) self.add_error(e) - def add_str_error(self, message, path): - # type: (str, ConfigPath) -> None + def add_str_error(self, message: str, path: ConfigPath) -> None: self.add_error(vol.Invalid(message, path)) - def add_output_path(self, path, domain): - # type: (ConfigPath, str) -> None + def add_output_path(self, path: ConfigPath, domain: str) -> None: self.output_paths.append((path, domain)) - def remove_output_path(self, path, domain): - # type: (ConfigPath, str) -> None + def remove_output_path(self, path: ConfigPath, domain: str) -> None: self.output_paths.remove((path, domain)) - def is_in_error_path(self, path): - # type: (ConfigPath) -> bool + def is_in_error_path(self, path: ConfigPath) -> bool: for err in self.errors: if _path_begins_with(err.path, path): return True @@ -157,16 +152,16 @@ class Config(OrderedDict, fv.FinalValidateConfig): conf = conf[key] conf[path[-1]] = value - def get_error_for_path(self, path): - # type: (ConfigPath) -> Optional[vol.Invalid] + def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]: for err in self.errors: if self.get_deepest_path(err.path) == path: self.errors.remove(err) return err return None - def get_deepest_document_range_for_path(self, path, get_key=False): - # type: (ConfigPath, bool) -> Optional[ESPHomeDataBase] + def get_deepest_document_range_for_path( + self, path: ConfigPath, get_key: bool = False + ) -> Optional[ESPHomeDataBase]: data = self doc_range = None for index, path_item in enumerate(path): @@ -207,8 +202,7 @@ class Config(OrderedDict, fv.FinalValidateConfig): return {} return data - def get_deepest_path(self, path): - # type: (ConfigPath) -> ConfigPath + def get_deepest_path(self, path: ConfigPath) -> ConfigPath: """Return the path that is the deepest reachable by following path.""" data = self part = [] @@ -532,7 +526,7 @@ class IDPassValidationStep(ConfigValidationStep): # because the component that did not validate doesn't have any IDs set return - searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + searching_ids: list[tuple[core.ID, ConfigPath]] = [] for id, path in iter_ids(result): if id.is_declaration: if id.id is not None: @@ -780,8 +774,7 @@ def _get_parent_name(path, config): return path[-1] -def _format_vol_invalid(ex, config): - # type: (vol.Invalid, Config) -> str +def _format_vol_invalid(ex: vol.Invalid, config: Config) -> str: message = "" paren = _get_parent_name(ex.path[:-1], config) @@ -862,8 +855,9 @@ def _print_on_next_line(obj): return False -def dump_dict(config, path, at_root=True): - # type: (Config, ConfigPath, bool) -> Tuple[str, bool] +def dump_dict( + config: Config, path: ConfigPath, at_root: bool = True +) -> tuple[str, bool]: conf = config.get_nested_item(path) ret = "" multiline = False diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index 39b57e441b..bfb0f65417 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -5,8 +5,7 @@ from esphome.core import CORE from esphome.helpers import read_file -def read_config_file(path): - # type: (str) -> str +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) ): diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 3ee94efd64..a422cd9507 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,7 +2,7 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Optional, Union from esphome.const import ( CONF_COMMENT, @@ -469,19 +469,19 @@ class EsphomeCore: # Task counter for pending tasks self.task_counter = 0 # The variable cache, for each ID this holds a MockObj of the variable obj - self.variables: Dict[str, "MockObj"] = {} + self.variables: dict[str, "MockObj"] = {} # A list of statements that go in the main setup() block - self.main_statements: List["Statement"] = [] + self.main_statements: list["Statement"] = [] # A list of statements to insert in the global block (includes and global variables) - self.global_statements: List["Statement"] = [] + self.global_statements: list["Statement"] = [] # A set of platformio libraries to add to the project - self.libraries: List[Library] = [] + self.libraries: list[Library] = [] # A set of build flags to set in the platformio project - self.build_flags: Set[str] = set() + self.build_flags: set[str] = set() # A set of defines to set for the compile process in esphome/core/defines.h - self.defines: Set["Define"] = set() + self.defines: set["Define"] = set() # A map of all platformio options to apply - self.platformio_options: Dict[str, Union[str, List[str]]] = {} + self.platformio_options: dict[str, Union[str, list[str]]] = {} # A set of strings of names of loaded integrations, used to find namespace ID conflicts self.loaded_integrations = set() # A set of component IDs to track what Component subclasses are declared @@ -701,7 +701,7 @@ class EsphomeCore: _LOGGER.debug("Adding define: %s", define) return define - def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None: + def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None: new_val = value old_val = self.platformio_options.get(key) if isinstance(old_val, list): @@ -734,7 +734,7 @@ class EsphomeCore: _LOGGER.debug("Waiting for variable %s", id) yield - async def get_variable_with_full_id(self, id: ID) -> Tuple[ID, "MockObj"]: + async def get_variable_with_full_id(self, id: ID) -> tuple[ID, "MockObj"]: if not isinstance(id, ID): raise ValueError(f"ID {id!r} must be of type ID!") return await _FakeAwaitable(self._get_variable_with_full_id_generator(id)) diff --git a/esphome/coroutine.py b/esphome/coroutine.py index 58f79c6b36..5f391dc7ad 100644 --- a/esphome/coroutine.py +++ b/esphome/coroutine.py @@ -48,7 +48,8 @@ import heapq import inspect import logging import types -from typing import Any, Awaitable, Callable, Generator, Iterator, List, Tuple +from typing import Any, Callable +from collections.abc import Awaitable, Generator, Iterator _LOGGER = logging.getLogger(__name__) @@ -177,7 +178,7 @@ class _Task: return _Task(priority, self.id_number, self.iterator, self.original_function) @property - def _cmp_tuple(self) -> Tuple[float, int]: + def _cmp_tuple(self) -> tuple[float, int]: return (-self.priority, self.id_number) def __eq__(self, other): @@ -194,7 +195,7 @@ class FakeEventLoop: """Emulate an asyncio EventLoop to run some registered coroutine jobs in sequence.""" def __init__(self): - self._pending_tasks: List[_Task] = [] + self._pending_tasks: list[_Task] = [] self._task_counter = 0 def add_job(self, func, *args, **kwargs): diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index df806af1a5..a0f60e1ff8 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -8,14 +8,10 @@ from esphome.yaml_util import ESPHomeDataBase from typing import ( Any, Callable, - Generator, - List, Optional, - Tuple, - Type, Union, - Sequence, ) +from collections.abc import Generator, Sequence from esphome.core import ( # noqa CORE, @@ -54,9 +50,9 @@ SafeExpType = Union[ int, float, TimePeriod, - Type[bool], - Type[int], - Type[float], + type[bool], + type[int], + type[float], Sequence[Any], ] @@ -150,7 +146,7 @@ class CallExpression(Expression): class StructInitializer(Expression): __slots__ = ("base", "args") - def __init__(self, base: Expression, *args: Tuple[str, Optional[SafeExpType]]): + def __init__(self, base: Expression, *args: tuple[str, Optional[SafeExpType]]): self.base = base # TODO: args is always a Tuple, is this check required? if not isinstance(args, OrderedDict): @@ -210,7 +206,7 @@ class ParameterListExpression(Expression): __slots__ = ("parameters",) def __init__( - self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]] + self, *parameters: Union[ParameterExpression, tuple[SafeExpType, str]] ): self.parameters = [] for parameter in parameters: @@ -629,7 +625,7 @@ def add_define(name: str, value: SafeExpType = None): CORE.add_define(Define(name, safe_exp(value))) -def add_platformio_option(key: str, value: Union[str, List[str]]): +def add_platformio_option(key: str, value: Union[str, list[str]]): CORE.add_platformio_option(key, value) @@ -646,7 +642,7 @@ async def get_variable(id_: ID) -> "MockObj": return await CORE.get_variable(id_) -async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]: +async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: """ Wait for the given ID to be defined in the code generation and return it as a MockObj. @@ -661,7 +657,7 @@ async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]: async def process_lambda( value: Lambda, - parameters: List[Tuple[SafeExpType, str]], + parameters: list[tuple[SafeExpType, str]], capture: str = "=", return_type: SafeExpType = None, ) -> Generator[LambdaExpression, None, None]: @@ -715,7 +711,7 @@ def is_template(value): async def templatable( value: Any, - args: List[Tuple[SafeExpType, str]], + args: list[tuple[SafeExpType, str]], output_type: Optional[SafeExpType], to_exp: Any = None, ): @@ -763,7 +759,7 @@ class MockObj(Expression): attr = attr[1:] return MockObj(f"{self.base}{self.op}{attr}", next_op) - def __call__(self, *args): # type: (SafeExpType) -> MockObj + def __call__(self, *args: SafeExpType) -> "MockObj": call = CallExpression(self.base, *args) return MockObj(call, self.op) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 9127f88e39..822197341e 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -107,8 +107,9 @@ async def setup_entity(var, config): add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) -def extract_registry_entry_config(registry, full_config): - # type: (Registry, ConfigType) -> RegistryEntry +def extract_registry_entry_config( + registry: Registry, full_config: ConfigType +) -> RegistryEntry: key, config = next((k, v) for k, v in full_config.items() if k in registry) return registry[key], config diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 9a8f072237..4f361d0936 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -522,7 +522,7 @@ class DashboardEntry: return os.path.basename(self.path) @property - def storage(self): # type: () -> Optional[StorageJSON] + def storage(self) -> Optional[StorageJSON]: if not self._loaded_storage: self._storage = StorageJSON.load( ext_storage_path(settings.config_dir, self.filename) @@ -817,7 +817,7 @@ class UndoDeleteRequestHandler(BaseHandler): shutil.move(os.path.join(trash_path, configuration), config_file) -PING_RESULT = {} # type: dict +PING_RESULT: dict = {} IMPORT_RESULT = {} STOP_EVENT = threading.Event() PING_REQUEST = threading.Event() @@ -933,7 +933,7 @@ def get_static_path(*args): return os.path.join(get_base_frontend_path(), "static", *args) -@functools.lru_cache(maxsize=None) +@functools.cache def get_static_file_url(name): base = f"./static/{name}" diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 96dd2fd651..5e9d2207b0 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Dict, Any +from typing import Any import contextvars from esphome.types import ConfigFragmentType, ID, ConfigPathType @@ -9,7 +9,7 @@ import esphome.config_validation as cv class FinalValidateConfig(ABC): @property @abstractmethod - def data(self) -> Dict[str, Any]: + def data(self) -> dict[str, Any]: """A dictionary that can be used by post validation functions to store global data during the validation phase. Each component should store its data under a unique key diff --git a/esphome/helpers.py b/esphome/helpers.py index e958aca78e..85a767036a 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -40,7 +40,7 @@ def indent(text, padding=" "): # From https://stackoverflow.com/a/14945195/8924614 def cpp_string_escape(string, encoding="utf-8"): - def _should_escape(byte): # type: (int) -> bool + def _should_escape(byte: int) -> bool: if not 32 <= byte < 127: return True if byte in (ord("\\"), ord('"')): diff --git a/esphome/loader.py b/esphome/loader.py index 05d2e5a213..a0676eb90e 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -1,5 +1,5 @@ import logging -from typing import Callable, List, Optional, Any, ContextManager +from typing import Callable, Optional, Any, ContextManager from types import ModuleType import importlib import importlib.util @@ -62,19 +62,19 @@ class ComponentManifest: return getattr(self.module, "to_code", None) @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: return getattr(self.module, "DEPENDENCIES", []) @property - def conflicts_with(self) -> List[str]: + def conflicts_with(self) -> list[str]: return getattr(self.module, "CONFLICTS_WITH", []) @property - def auto_load(self) -> List[str]: + def auto_load(self) -> list[str]: return getattr(self.module, "AUTO_LOAD", []) @property - def codeowners(self) -> List[str]: + def codeowners(self) -> list[str]: return getattr(self.module, "CODEOWNERS", []) @property @@ -87,7 +87,7 @@ class ComponentManifest: return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) @property - def resources(self) -> List[FileResource]: + def resources(self) -> list[FileResource]: """Return a list of all file resources defined in the package of this component. This will return all cpp source files that are located in the same folder as the @@ -106,7 +106,7 @@ class ComponentManifest: class ComponentMetaFinder(importlib.abc.MetaPathFinder): def __init__( - self, components_path: Path, allowed_components: Optional[List[str]] = None + self, components_path: Path, allowed_components: Optional[list[str]] = None ) -> None: self._allowed_components = allowed_components self._finders = [] @@ -117,7 +117,7 @@ class ComponentMetaFinder(importlib.abc.MetaPathFinder): continue self._finders.append(finder) - def find_spec(self, fullname: str, path: Optional[List[str]], target=None): + def find_spec(self, fullname: str, path: Optional[list[str]], target=None): if not fullname.startswith("esphome.components."): return None parts = fullname.split(".") @@ -144,7 +144,7 @@ def clear_component_meta_finders(): def install_meta_finder( - components_path: Path, allowed_components: Optional[List[str]] = None + components_path: Path, allowed_components: Optional[list[str]] = None ): sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components)) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index c4bf3d3f1a..a4a3f32092 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -1,6 +1,6 @@ from dataclasses import dataclass import json -from typing import List, Union +from typing import Union from pathlib import Path import logging @@ -310,7 +310,7 @@ class IDEData: return str(Path(self.firmware_elf_path).with_suffix(".bin")) @property - def extra_flash_images(self) -> List[FlashImage]: + def extra_flash_images(self) -> list[FlashImage]: return [ FlashImage(path=entry["path"], offset=entry["offset"]) for entry in self.raw["extra"]["flash_images"] diff --git a/esphome/storage_json.py b/esphome/storage_json.py index a941fca0af..af71d4583c 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -4,7 +4,7 @@ from datetime import datetime import json import logging import os -from typing import Any, Optional, List +from typing import Optional from esphome import const from esphome.core import CORE @@ -15,19 +15,19 @@ from esphome.types import CoreType _LOGGER = logging.getLogger(__name__) -def storage_path(): # type: () -> str +def storage_path() -> str: return CORE.relative_internal_path(f"{CORE.config_filename}.json") -def ext_storage_path(base_path, config_filename): # type: (str, str) -> str +def ext_storage_path(base_path: str, config_filename: str) -> str: return os.path.join(base_path, ".esphome", f"{config_filename}.json") -def esphome_storage_path(base_path): # type: (str) -> str +def esphome_storage_path(base_path: str) -> str: return os.path.join(base_path, ".esphome", "esphome.json") -def trash_storage_path(base_path): # type: (str) -> str +def trash_storage_path(base_path: str) -> str: return os.path.join(base_path, ".esphome", "trash") @@ -49,29 +49,29 @@ class StorageJSON: ): # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) - self.storage_version = storage_version # type: int + self.storage_version: int = storage_version # The name of the node - self.name = name # type: str + self.name: str = name # The comment of the node - self.comment = comment # type: str + self.comment: str = comment # The esphome version this was compiled with - self.esphome_version = esphome_version # type: str + self.esphome_version: str = 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 = src_version # type: int + self.src_version: int = src_version # Address of the ESP, for example livingroom.local or a static IP - self.address = address # type: str + self.address: str = address # Web server port of the ESP, for example 80 assert web_port is None or isinstance(web_port, int) - self.web_port = web_port # type: int + self.web_port: int = web_port # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc. - self.target_platform = target_platform # type: str + self.target_platform: str = target_platform # The absolute path to the platformio project - self.build_path = build_path # type: str + self.build_path: str = build_path # The absolute path to the firmware binary - self.firmware_bin_path = firmware_bin_path # type: str + self.firmware_bin_path: str = firmware_bin_path # A list of strings of names of loaded integrations - self.loaded_integrations = loaded_integrations # type: List[str] + self.loaded_integrations: list[str] = loaded_integrations self.loaded_integrations.sort() def as_dict(self): @@ -97,8 +97,8 @@ class StorageJSON: @staticmethod def from_esphome_core( - esph, old - ): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON + esph: CoreType, old: Optional["StorageJSON"] + ) -> "StorageJSON": hardware = esph.target_platform.upper() if esph.is_esp32: from esphome.components import esp32 @@ -135,7 +135,7 @@ class StorageJSON: ) @staticmethod - def _load_impl(path): # type: (str) -> Optional[StorageJSON] + def _load_impl(path: str) -> Optional["StorageJSON"]: with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) storage_version = storage["storage_version"] @@ -166,13 +166,13 @@ class StorageJSON: ) @staticmethod - def load(path): # type: (str) -> Optional[StorageJSON] + def load(path: str) -> Optional["StorageJSON"]: try: return StorageJSON._load_impl(path) except Exception: # pylint: disable=broad-except return None - def __eq__(self, o): # type: (Any) -> bool + def __eq__(self, o) -> bool: return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict() @@ -182,15 +182,15 @@ class EsphomeStorageJSON: ): # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) - self.storage_version = storage_version # type: int + self.storage_version: int = storage_version # The cookie secret for the dashboard - self.cookie_secret = cookie_secret # type: str + self.cookie_secret: str = cookie_secret # The last time ESPHome checked for an update as an isoformat encoded str - self.last_update_check_str = last_update_check # type: str + self.last_update_check_str: str = last_update_check # Cache of the version gotten in the last version check - self.remote_version = remote_version # type: Optional[str] + self.remote_version: Optional[str] = remote_version - def as_dict(self): # type: () -> dict + def as_dict(self) -> dict: return { "storage_version": self.storage_version, "cookie_secret": self.cookie_secret, @@ -199,24 +199,24 @@ class EsphomeStorageJSON: } @property - def last_update_check(self): # type: () -> Optional[datetime] + def last_update_check(self) -> Optional[datetime]: try: return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S") except Exception: # pylint: disable=broad-except return None @last_update_check.setter - def last_update_check(self, new): # type: (datetime) -> None + def last_update_check(self, new: datetime) -> None: self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") - def to_json(self): # type: () -> dict + def to_json(self) -> dict: return f"{json.dumps(self.as_dict(), indent=2)}\n" - def save(self, path): # type: (str) -> None + def save(self, path: str) -> None: write_file_if_changed(path, self.to_json()) @staticmethod - def _load_impl(path): # type: (str) -> Optional[EsphomeStorageJSON] + def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]: with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) storage_version = storage["storage_version"] @@ -228,14 +228,14 @@ class EsphomeStorageJSON: ) @staticmethod - def load(path): # type: (str) -> Optional[EsphomeStorageJSON] + def load(path: str) -> Optional["EsphomeStorageJSON"]: try: return EsphomeStorageJSON._load_impl(path) except Exception: # pylint: disable=broad-except return None @staticmethod - def get_default(): # type: () -> EsphomeStorageJSON + def get_default() -> "EsphomeStorageJSON": return EsphomeStorageJSON( storage_version=1, cookie_secret=binascii.hexlify(os.urandom(64)).decode(), @@ -243,5 +243,5 @@ class EsphomeStorageJSON: remote_version=None, ) - def __eq__(self, o): # type: (Any) -> bool + def __eq__(self, o) -> bool: return isinstance(o, EsphomeStorageJSON) and self.as_dict() == o.as_dict() diff --git a/esphome/types.py b/esphome/types.py index 6bbfb00ce6..adb16fa91b 100644 --- a/esphome/types.py +++ b/esphome/types.py @@ -1,5 +1,5 @@ """This helper module tracks commonly used types in the esphome python codebase.""" -from typing import Dict, Union, List +from typing import Union from esphome.core import ID, Lambda, EsphomeCore @@ -8,11 +8,11 @@ ConfigFragmentType = Union[ int, float, None, - Dict[Union[str, int], "ConfigFragmentType"], - List["ConfigFragmentType"], + dict[Union[str, int], "ConfigFragmentType"], + list["ConfigFragmentType"], ID, Lambda, ] -ConfigType = Dict[str, ConfigFragmentType] +ConfigType = dict[str, ConfigFragmentType] CoreType = EsphomeCore ConfigPathType = Union[str, int] diff --git a/esphome/util.py b/esphome/util.py index 927c50fe89..1779e8ccfb 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -1,5 +1,4 @@ -import typing -from typing import Union, List +from typing import Union import collections import io @@ -242,7 +241,7 @@ def is_dev_esphome_version(): return "dev" in const.__version__ -def parse_esphome_version() -> typing.Tuple[int, int, int]: +def parse_esphome_version() -> tuple[int, int, int]: match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__) if match is None: raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'") @@ -282,7 +281,7 @@ class SerialPort: # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py -def get_serial_ports() -> List[SerialPort]: +def get_serial_ports() -> list[SerialPort]: from serial.tools.list_ports import comports result = [] diff --git a/esphome/vscode.py b/esphome/vscode.py index 6a43a654ed..32a6b524f6 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -10,15 +10,13 @@ import esphome.config_validation as cv from typing import Optional -def _get_invalid_range(res, invalid): - # type: (Config, cv.Invalid) -> Optional[DocumentRange] +def _get_invalid_range(res: Config, invalid: cv.Invalid) -> Optional[DocumentRange]: return res.get_deepest_document_range_for_path( invalid.path, invalid.error_message == "extra keys not allowed" ) -def _dump_range(range): - # type: (Optional[DocumentRange]) -> Optional[dict] +def _dump_range(range: Optional[DocumentRange]) -> Optional[dict]: if range is None: return None return { diff --git a/esphome/writer.py b/esphome/writer.py index 31b47e243e..7a3c13e80b 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -2,7 +2,7 @@ import logging import os import re from pathlib import Path -from typing import Dict, List, Union +from typing import Union from esphome.config import iter_components from esphome.const import ( @@ -98,7 +98,7 @@ def replace_file_content(text, pattern, repl): return content_new, count -def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool +def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool: if old is None: return True @@ -123,7 +123,7 @@ def update_storage_json(): new.save(path) -def format_ini(data: Dict[str, Union[str, List[str]]]) -> str: +def format_ini(data: dict[str, Union[str, list[str]]]) -> str: content = "" for key, value in sorted(data.items()): if isinstance(value, list): @@ -226,7 +226,7 @@ the custom_components folder or the external_components feature. def copy_src_tree(): - source_files: List[loader.FileResource] = [] + source_files: list[loader.FileResource] = [] for _, component, _ in iter_components(CORE.config): source_files += component.resources source_files_map = { diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 1fbdf7e93f..3a491d9b99 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,7 +1,7 @@ import socket import threading import time -from typing import Dict, Optional +from typing import Optional import logging from dataclasses import dataclass @@ -71,12 +71,12 @@ class DashboardStatus(threading.Thread): threading.Thread.__init__(self) self.zc = zc self.query_hosts: set[str] = set() - self.key_to_host: Dict[str, str] = {} + 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: + def request_query(self, hosts: dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts self.query_event.set() diff --git a/pyproject.toml b/pyproject.toml index 7a75060c8e..a49abb7b3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [tool.black] -target-version = ["py36", "py37", "py38"] +target-version = ["py39", "py310"] exclude = 'generated' diff --git a/script/lint-python b/script/lint-python index 90b5dcd59f..7de1de80b0 100755 --- a/script/lint-python +++ b/script/lint-python @@ -109,7 +109,7 @@ def main(): print_error(file_, linno, msg) errors += 1 - PYUPGRADE_TARGET = "--py38-plus" + PYUPGRADE_TARGET = "--py39-plus" cmd = ["pyupgrade", PYUPGRADE_TARGET] + files print() print("Running pyupgrade...") diff --git a/setup.py b/setup.py index 941c8089ec..95453960ff 100755 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ setup( zip_safe=False, platforms="any", test_suite="tests", - python_requires=">=3.8,<4.0", + python_requires=">=3.9.0", install_requires=REQUIRES, keywords=["home", "automation"], entry_points={"console_scripts": ["esphome = esphome.__main__:main"]}, diff --git a/tests/unit_tests/strategies.py b/tests/unit_tests/strategies.py index 30768f9d56..20a3d190da 100644 --- a/tests/unit_tests/strategies.py +++ b/tests/unit_tests/strategies.py @@ -1,12 +1,9 @@ -from typing import Text - import hypothesis.strategies._internal.core as st from hypothesis.strategies._internal.strategies import SearchStrategy @st.defines_strategy(force_reusable_values=True) -def mac_addr_strings(): - # type: () -> SearchStrategy[Text] +def mac_addr_strings() -> SearchStrategy[str]: """A strategy for MAC address strings. This consists of six strings representing integers [0..255], From 1001d9c04e738d619c8a76796a55abc1a3549367 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:45:06 +1300 Subject: [PATCH 114/838] Bluetooth Proxy active connections (#3817) --- CODEOWNERS | 1 + .../airthings_wave_mini.cpp | 6 +- .../airthings_wave_plus.cpp | 6 +- esphome/components/am43/am43.cpp | 10 +- esphome/components/am43/cover/am43_cover.cpp | 21 +- esphome/components/anova/anova.cpp | 22 +- esphome/components/api/api.proto | 177 +++- esphome/components/api/api_connection.cpp | 72 +- esphome/components/api/api_connection.h | 31 +- esphome/components/api/api_pb2.cpp | 764 +++++++++++++++++- esphome/components/api/api_pb2.h | 231 +++++- esphome/components/api/api_pb2_service.cpp | 285 ++++++- esphome/components/api/api_pb2_service.h | 95 ++- esphome/components/api/api_server.cpp | 45 ++ esphome/components/api/api_server.h | 6 + esphome/components/bedjet/bedjet_hub.cpp | 48 +- esphome/components/bedjet/bedjet_hub.h | 2 - esphome/components/ble_client/__init__.py | 7 +- esphome/components/ble_client/automation.cpp | 6 +- esphome/components/ble_client/automation.h | 3 +- esphome/components/ble_client/ble_client.cpp | 409 +--------- esphome/components/ble_client/ble_client.h | 86 +- .../components/ble_client/sensor/automation.h | 3 +- .../ble_client/sensor/ble_sensor.cpp | 12 +- .../ble_client/text_sensor/automation.h | 3 +- .../text_sensor/ble_text_sensor.cpp | 12 +- .../components/bluetooth_proxy/__init__.py | 16 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 323 +++++++- .../bluetooth_proxy/bluetooth_proxy.h | 39 +- .../components/esp32_ble_client/__init__.py | 12 + .../esp32_ble_client/ble_characteristic.cpp | 83 ++ .../esp32_ble_client/ble_characteristic.h | 35 + .../esp32_ble_client/ble_client_base.cpp | 324 ++++++++ .../esp32_ble_client/ble_client_base.h | 72 ++ .../esp32_ble_client/ble_descriptor.h | 25 + .../esp32_ble_client/ble_service.cpp | 66 ++ .../components/esp32_ble_client/ble_service.h | 32 + .../components/esp32_ble_tracker/__init__.py | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 37 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 5 +- esphome/components/esp32_ble_tracker/queue.h | 10 +- .../display/pvvx_display.cpp | 5 +- .../radon_eye_rd200/radon_eye_rd200.cpp | 12 +- esphome/const.py | 1 + 44 files changed, 2818 insertions(+), 644 deletions(-) create mode 100644 esphome/components/esp32_ble_client/__init__.py create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.cpp create mode 100644 esphome/components/esp32_ble_client/ble_characteristic.h create mode 100644 esphome/components/esp32_ble_client/ble_client_base.cpp create mode 100644 esphome/components/esp32_ble_client/ble_client_base.h create mode 100644 esphome/components/esp32_ble_client/ble_descriptor.h create mode 100644 esphome/components/esp32_ble_client/ble_service.cpp create mode 100644 esphome/components/esp32_ble_client/ble_service.h diff --git a/CODEOWNERS b/CODEOWNERS index 69e30027a9..d95ebcce59 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -70,6 +70,7 @@ esphome/components/ektf2232/* @jesserockz esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 2e7a1fb024..40873ec005 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -88,8 +88,8 @@ void AirthingsWaveMini::update() { } void AirthingsWaveMini::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 02ed33b87a..11f86307fe 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -109,8 +109,8 @@ void AirthingsWavePlus::update() { } void AirthingsWavePlus::request_read_values_() { - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 1c4bad64c3..9a0e5999d2 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i if (this->current_sensor_ > 0) { if (this->illuminance_ != nullptr) { auto *packet = this->encoder_->get_light_level_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -102,8 +102,8 @@ void Am43::update() { if (this->battery_ != nullptr) { auto *packet = this->encoder_->get_battery_level_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 39089e73c0..ba8e350732 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -27,8 +27,8 @@ void Am43Component::loop() { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); if (status) { ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); @@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) { if (call.get_stop()) { auto *packet = this->encoder_->get_stop_request(); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); } @@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) { pos = 1 - pos; auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, - packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); } @@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->decoder_->pin_ok_) { ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); auto *packet = this->encoder_->get_position_request(); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_, packet->length, packet->data, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); } else { diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 6c8316d338..cafd30149d 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) { ESP_LOGW(TAG, "Unsupported mode: %d", mode); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } if (call.get_target_temperature().has_value()) { auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } @@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } this->char_handle_ = chr->handle; - auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), + chr->handle); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } @@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ } if (pkt != nullptr) { auto status = - esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, - pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); @@ -137,8 +140,9 @@ void Anova::update() { auto *pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, - pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, + pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); this->current_request_++; diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c6cde8b038..9fa77d2daa 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -27,7 +27,6 @@ service APIConnection { rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} - rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc get_time (GetTimeRequest) returns (GetTimeResponse) { option (needs_authentication) = false; } @@ -44,6 +43,16 @@ service APIConnection { rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} + + rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} + rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} + rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {} + rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {} + rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {} + rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} + rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} + rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} } @@ -78,6 +87,8 @@ message HelloRequest { // Not strictly necessary to send but nice for debugging // purposes. string client_info = 1; + uint32 api_version_major = 2; + uint32 api_version_minor = 3; } // Confirmation of successful connection request. @@ -192,7 +203,7 @@ message DeviceInfoResponse { uint32 webserver_port = 10; - bool has_bluetooth_proxy = 11; + uint32 bluetooth_proxy_version = 11; } message ListEntitiesRequest { @@ -1111,7 +1122,8 @@ message SubscribeBluetoothLEAdvertisementsRequest { message BluetoothServiceData { string uuid = 1; - repeated uint32 data = 2 [packed=false]; + repeated uint32 legacy_data = 2 [deprecated = true]; + bytes data = 3; // Changed in proto version 1.7 } message BluetoothLEAdvertisementResponse { option (id) = 67; @@ -1127,3 +1139,162 @@ message BluetoothLEAdvertisementResponse { repeated BluetoothServiceData service_data = 5; repeated BluetoothServiceData manufacturer_data = 6; } + +enum BluetoothDeviceRequestType { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0; + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2; + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; +} + +message BluetoothDeviceRequest { + option (id) = 68; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + BluetoothDeviceRequestType request_type = 2; +} + +message BluetoothDeviceConnectionResponse { + option (id) = 69; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool connected = 2; + uint32 mtu = 3; + int32 error = 4; +} + +message BluetoothGATTGetServicesRequest { + option (id) = 70; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTDescriptor { + repeated uint64 uuid = 1; + uint32 handle = 2; +} + +message BluetoothGATTCharacteristic { + repeated uint64 uuid = 1; + uint32 handle = 2; + uint32 properties = 3; + repeated BluetoothGATTDescriptor descriptors = 4; +} + +message BluetoothGATTService { + repeated uint64 uuid = 1; + uint32 handle = 2; + repeated BluetoothGATTCharacteristic characteristics = 3; +} + +message BluetoothGATTGetServicesResponse { + option (id) = 71; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + repeated BluetoothGATTService services = 2; +} + +message BluetoothGATTGetServicesDoneResponse { + option (id) = 72; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; +} + +message BluetoothGATTReadRequest { + option (id) = 73; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTReadResponse { + option (id) = 74; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; + +} + +message BluetoothGATTWriteRequest { + option (id) = 75; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool response = 3; + + bytes data = 4; +} + +message BluetoothGATTReadDescriptorRequest { + option (id) = 76; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTWriteDescriptorRequest { + option (id) = 77; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message BluetoothGATTNotifyRequest { + option (id) = 78; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + bool enable = 3; +} + +message BluetoothGATTNotifyDataResponse { + option (id) = 79; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + + bytes data = 3; +} + +message SubscribeBluetoothConnectionsFreeRequest { + option (id) = 80; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; +} + +message BluetoothConnectionsFreeResponse { + option (id) = 81; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint32 free = 1; + uint32 limit = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a3b000c778..1dbf0abec4 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,10 +1,10 @@ #include "api_connection.h" -#include "esphome/core/entity_base.h" -#include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include "esphome/core/version.h" -#include "esphome/core/hal.h" #include +#include "esphome/components/network/util.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/version.h" #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -12,6 +12,9 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif +#ifdef USE_BLUETOOTH_PROXY +#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" +#endif namespace esphome { namespace api { @@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { } #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) { + if (!this->bluetooth_le_advertisement_subscription_) + return false; + if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) { + BluetoothLEAdvertisementResponse resp = msg; + for (auto &service : resp.service_data) { + service.legacy_data.assign(service.data.begin(), service.data.end()); + service.data.clear(); + } + for (auto &manufacturer_data : resp.manufacturer_data) { + manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end()); + manufacturer_data.data.clear(); + } + return this->send_bluetooth_le_advertisement_response(resp); + } + return this->send_bluetooth_le_advertisement_response(msg); +} +void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg); +} +void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg); +} +void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg); +} +void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg); +} +void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg); +} +void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg); +} + +void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) { + bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); +} + +BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + BluetoothConnectionsFreeResponse resp; + resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free(); + resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit(); + return resp; +} +#endif + bool APIConnection::send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; @@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin HelloResponse APIConnection::hello(const HelloRequest &msg) { this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; this->helper_->set_log_info(client_info_); - ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); + this->client_api_version_major_ = msg.api_version_major; + this->client_api_version_minor_ = msg.api_version_minor; + ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), + this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 6; + resp.api_version_minor = 7; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); @@ -888,7 +944,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.webserver_port = USE_WEBSERVER_PORT; #endif #ifdef USE_BLUETOOTH_PROXY - resp.has_bluetooth_proxy = true; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1; #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index dcf8bacad2..028fb80175 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -1,15 +1,11 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/application.h" +#include "api_frame_helper.h" #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" -#include "api_frame_helper.h" - -#ifdef USE_BLUETOOTH_PROXY -#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" -#endif +#include "esphome/core/application.h" +#include "esphome/core/component.h" namespace esphome { namespace api { @@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection { this->send_homeassistant_service_response(call); } #ifdef USE_BLUETOOTH_PROXY - bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { - if (!this->bluetooth_le_advertisement_subscription_) - return false; - return this->send_bluetooth_le_advertisement_response(call); - } + bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); + + void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; + void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override; + void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override; + void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override; + void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; + void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; + void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; + BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) override; + #endif #ifdef USE_HOMEASSISTANT_TIME void send_time_request() { @@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection { std::unique_ptr helper_; std::string client_info_; + uint32_t client_api_version_major_{0}; + uint32_t client_api_version_minor_{0}; #ifdef USE_ESP32_CAMERA esp32_camera::CameraImageReader image_reader_; #endif @@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; - bool bluetooth_le_advertisement_subscription_{true}; + bool bluetooth_le_advertisement_subscription_{false}; bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 13969fce76..73d8044cde 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string(enums::Me return "UNKNOWN"; } } +template<> +const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) { + switch (value) { + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"; + default: + return "UNKNOWN"; + } +} +bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->api_version_major = value.as_uint32(); + return true; + } + case 3: { + this->api_version_minor = value.as_uint32(); + return true; + } + default: + return false; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) return false; } } -void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } +void HelloRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->client_info); + buffer.encode_uint32(2, this->api_version_major); + buffer.encode_uint32(3, this->api_version_minor); +} #ifdef HAS_PROTO_MESSAGE_DUMP void HelloRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; @@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const { out.append(" client_info: "); out.append("'").append(this->client_info).append("'"); out.append("\n"); + + out.append(" api_version_major: "); + sprintf(buffer, "%u", this->api_version_major); + out.append(buffer); + out.append("\n"); + + out.append(" api_version_minor: "); + sprintf(buffer, "%u", this->api_version_minor); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 11: { - this->has_bluetooth_proxy = value.as_bool(); + this->bluetooth_proxy_version = value.as_uint32(); return true; } default: @@ -548,7 +591,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); buffer.encode_uint32(10, this->webserver_port); - buffer.encode_bool(11, this->has_bluetooth_proxy); + buffer.encode_uint32(11, this->bluetooth_proxy_version); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -595,8 +638,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_bluetooth_proxy: "); - out.append(YESNO(this->has_bluetooth_proxy)); + out.append(" bluetooth_proxy_version: "); + sprintf(buffer, "%u", this->bluetooth_proxy_version); + out.append(buffer); out.append("\n"); out.append("}"); } @@ -4872,7 +4916,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { - this->data.push_back(value.as_uint32()); + this->legacy_data.push_back(value.as_uint32()); return true; } default: @@ -4885,15 +4929,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited this->uuid = value.as_string(); return true; } + case 3: { + this->data = value.as_string(); + return true; + } default: return false; } } void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->uuid); - for (auto &it : this->data) { + for (auto &it : this->legacy_data) { buffer.encode_uint32(2, it, true); } + buffer.encode_string(3, this->data); } #ifdef HAS_PROTO_MESSAGE_DUMP void BluetoothServiceData::dump_to(std::string &out) const { @@ -4903,12 +4952,16 @@ void BluetoothServiceData::dump_to(std::string &out) const { out.append("'").append(this->uuid).append("'"); out.append("\n"); - for (const auto &it : this->data) { - out.append(" data: "); + for (const auto &it : this->legacy_data) { + out.append(" legacy_data: "); sprintf(buffer, "%u", it); out.append(buffer); out.append("\n"); } + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -5000,6 +5053,699 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->request_type = value.as_enum(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_enum(2, this->request_type); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" request_type: "); + out.append(proto_enum_to_string(this->request_type)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->connected = value.as_bool(); + return true; + } + case 3: { + this->mtu = value.as_uint32(); + return true; + } + case 4: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->connected); + buffer.encode_uint32(3, this->mtu); + buffer.encode_int32(4, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceConnectionResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" connected: "); + out.append(YESNO(this->connected)); + out.append("\n"); + + out.append(" mtu: "); + sprintf(buffer, "%u", this->mtu); + out.append(buffer); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTDescriptor::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTDescriptor {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->properties = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->descriptors.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + buffer.encode_uint32(3, this->properties); + for (auto &it : this->descriptors) { + buffer.encode_message(4, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTCharacteristic::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTCharacteristic {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" properties: "); + sprintf(buffer, "%u", this->properties); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->descriptors) { + out.append(" descriptors: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->uuid.push_back(value.as_uint64()); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->characteristics.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->uuid) { + buffer.encode_uint64(1, it, true); + } + buffer.encode_uint32(2, this->handle); + for (auto &it : this->characteristics) { + buffer.encode_message(3, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTService::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTService {\n"); + for (const auto &it : this->uuid) { + out.append(" uuid: "); + sprintf(buffer, "%llu", it); + out.append(buffer); + out.append("\n"); + } + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->characteristics) { + out.append(" characteristics: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->services.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + for (auto &it : this->services) { + buffer.encode_message(2, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + for (const auto &it : this->services) { + out.append(" services: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif +bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + default: + return false; + } +} +void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTGetServicesDoneResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->response = value.as_bool(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->response); + buffer.encode_string(4, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" response: "); + out.append(YESNO(this->response)); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTReadDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteDescriptorRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->enable = value.as_bool(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_bool(3, this->enable); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyRequest {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" enable: "); + out.append(YESNO(this->enable)); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 3: { + this->data = value.as_string(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_string(3, this->data); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyDataResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" data: "); + out.append("'").append(this->data).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { + out.append("SubscribeBluetoothConnectionsFreeRequest {}"); +} +#endif +bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->free = value.as_uint32(); + return true; + } + case 2: { + this->limit = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->free); + buffer.encode_uint32(2, this->limit); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothConnectionsFreeResponse {\n"); + out.append(" free: "); + sprintf(buffer, "%u", this->free); + out.append(buffer); + out.append("\n"); + + out.append(" limit: "); + sprintf(buffer, "%u", this->limit); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2093f93ee7..325e9a23c3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_UNMUTE = 4, }; +enum BluetoothDeviceRequestType : uint32_t { + BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0, + BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1, + BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2, + BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, +}; } // namespace enums class HelloRequest : public ProtoMessage { public: std::string client_info{}; + uint32_t api_version_major{0}; + uint32_t api_version_minor{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class HelloResponse : public ProtoMessage { public: @@ -263,7 +272,7 @@ class DeviceInfoResponse : public ProtoMessage { std::string project_name{}; std::string project_version{}; uint32_t webserver_port{0}; - bool has_bluetooth_proxy{false}; + uint32_t bluetooth_proxy_version{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1227,7 +1236,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { class BluetoothServiceData : public ProtoMessage { public: std::string uuid{}; - std::vector data{}; + std::vector legacy_data{}; + std::string data{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1254,6 +1264,223 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothDeviceRequest : public ProtoMessage { + public: + uint64_t address{0}; + enums::BluetoothDeviceRequestType request_type{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothDeviceConnectionResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool connected{false}; + uint32_t mtu{0}; + int32_t error{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesRequest : public ProtoMessage { + public: + uint64_t address{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTDescriptor : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTCharacteristic : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + uint32_t properties{0}; + std::vector descriptors{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTService : public ProtoMessage { + public: + std::vector uuid{}; + uint32_t handle{0}; + std::vector characteristics{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesResponse : public ProtoMessage { + public: + uint64_t address{0}; + std::vector services{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTGetServicesDoneResponse : public ProtoMessage { + public: + uint64_t address{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTWriteRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool response{false}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTReadDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTWriteDescriptorRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTNotifyRequest : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + bool enable{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTNotifyDataResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + std::string data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class BluetoothConnectionsFreeResponse : public ProtoMessage { + public: + uint32_t free{0}; + uint32_t limit{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 6932675c41..7bfe1acf48 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -336,6 +336,71 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu return this->send_message_(msg, 67); } #endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 69); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 71); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response( + const BluetoothGATTGetServicesDoneResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 72); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 74); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 79); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 81); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -612,6 +677,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, this->on_subscribe_bluetooth_le_advertisements_request(msg); break; } + case 68: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothDeviceRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_device_request(msg); +#endif + break; + } + case 70: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTGetServicesRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_get_services_request(msg); +#endif + break; + } + case 73: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_request(msg); +#endif + break; + } + case 75: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_request(msg); +#endif + break; + } + case 76: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTReadDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_read_descriptor_request(msg); +#endif + break; + } + case 77: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTWriteDescriptorRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_write_descriptor_request(msg); +#endif + break; + } + case 78: { +#ifdef USE_BLUETOOTH_PROXY + BluetoothGATTNotifyRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); +#endif + this->on_bluetooth_gatt_notify_request(msg); +#endif + break; + } + case 80: { +#ifdef USE_BLUETOOTH_PROXY + SubscribeBluetoothConnectionsFreeRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); +#endif + this->on_subscribe_bluetooth_connections_free_request(msg); +#endif + break; + } default: return false; } @@ -708,18 +861,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc } this->subscribe_home_assistant_states(msg); } -void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &msg) { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return; - } - this->subscribe_bluetooth_le_advertisements(msg); -} void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { if (!this->is_connection_setup()) { this->on_no_setup_connection(); @@ -884,6 +1025,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma this->media_player_command(msg); } #endif +void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( + const SubscribeBluetoothLEAdvertisementsRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->subscribe_bluetooth_le_advertisements(msg); +} +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_device_request(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_get_services(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_read_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_write_descriptor(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->bluetooth_gatt_notify(msg); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +void APIServerConnection::on_subscribe_bluetooth_connections_free_request( + const SubscribeBluetoothConnectionsFreeRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); + if (!this->send_bluetooth_connections_free_response(ret)) { + this->on_fatal_error(); + } +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 49426d1bbc..c7ef1468d8 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -158,6 +158,48 @@ class APIServerConnectionBase : public ProtoService { const SubscribeBluetoothLEAdvertisementsRequest &value){}; #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){}; +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -175,7 +217,6 @@ class APIServerConnection : public APIServerConnectionBase { virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; - virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0; #ifdef USE_COVER @@ -210,6 +251,32 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; +#endif + virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; +#endif +#ifdef USE_BLUETOOTH_PROXY + virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( + const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -222,7 +289,6 @@ class APIServerConnection : public APIServerConnectionBase { void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override; #ifdef USE_COVER @@ -257,6 +323,31 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_MEDIA_PLAYER void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; +#endif +#ifdef USE_BLUETOOTH_PROXY + void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; #endif }; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 5851594955..965f08aa15 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -297,6 +297,51 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe client->send_bluetooth_le_advertisement(call); } } +void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { + BluetoothDeviceConnectionResponse call; + call.address = address; + call.connected = connected; + call.mtu = mtu; + call.error = error; + + for (auto &client : this->clients_) { + client->send_bluetooth_device_connection_response(call); + } +} + +void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { + BluetoothConnectionsFreeResponse call; + call.free = free; + call.limit = limit; + + for (auto &client : this->clients_) { + client->send_bluetooth_connections_free_response(call); + } +} + +void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_read_response(call); + } +} +void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_notify_data_response(call); + } +} +void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_get_services_response(call); + } +} +void APIServer::send_bluetooth_gatt_services_done(uint64_t address) { + BluetoothGATTGetServicesDoneResponse call; + call.address = address; + + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_get_services_done_response(call); + } +} #endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index dc892b2088..72ab55432c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -75,6 +75,12 @@ class APIServer : public Component, public Controller { void send_homeassistant_service_call(const HomeassistantServiceResponse &call); #ifdef USE_BLUETOOTH_PROXY void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); + void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK); + void send_bluetooth_connections_free(uint8_t free, uint8_t limit); + void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); + void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call); + void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call); + void send_bluetooth_gatt_services_done(uint64_t address); #endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index fd383eb6be..f90ca5cf54 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { } return -1; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_, - pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); return status; } @@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) { uint8_t BedJetHub::set_notify_(const bool enable) { uint8_t status; if (enable) { - status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); } } else { - status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, + status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(), this->char_handle_status_); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status); @@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() { result = false; } else { this->char_handle_name_ = chr->handle; - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), + this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status); } @@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->dispatch_state_(false); break; } - case ESP_GATTC_OPEN_EVT: { - // FIXME: bug in BLEClient - this->parent_->conn_id = param->open.conn_id; - this->open_conn_id_ = param->open.conn_id; - break; - } - - case ESP_GATTC_CONNECT_EVT: { - if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) { - // FIXME: bug in BLEClient - ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(), - this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id); - this->parent_->conn_id = this->open_conn_id_; - } - break; - } case ESP_GATTC_SEARCH_CMPL_EVT: { auto result = this->discover_characteristics_(); @@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent_->conn_id) + if (param->read.conn_id != this->parent_->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (this->processing_) break; - if (param->notify.conn_id != this->parent_->conn_id) { + if (param->notify.conn_id != this->parent_->get_conn_id()) { ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x", - this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id); + this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id); // FIXME: bug in BLEClient holding wrong conn_id. } @@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (needs_extra) { // This means the packet was partial, so read the status characteristic to get the second part. // Ideally this will complete quickly. We won't process additional notification events until it does. - auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, + auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str()); @@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) { // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits. uint16_t notify_en = enable ? 1 : 0; - auto status = - esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle, + sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); return status; } ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(), - enable ? "true" : "false", handle, this->parent_->conn_id); + enable ? "true" : "false", handle, this->parent_->get_conn_id()); return ESP_GATT_OK; } @@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); } void BedJetHub::dump_config() { ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str()); ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id); - ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->conn_id); + ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id()); LOG_UPDATE_INTERVAL(this) ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); for (auto *child : this->children_) { diff --git a/esphome/components/bedjet/bedjet_hub.h b/esphome/components/bedjet/bedjet_hub.h index c7583b78e9..f1479710a7 100644 --- a/esphome/components/bedjet/bedjet_hub.h +++ b/esphome/components/bedjet/bedjet_hub.h @@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo uint16_t char_handle_status_; uint16_t config_descr_status_; - uint8_t open_conn_id_ = -1; - uint8_t write_notify_config_descriptor_(bool enable); }; diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 4b7d5f5b8a..0f1f60e62b 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_ID, @@ -14,13 +14,12 @@ from esphome.const import ( ) from esphome import automation +AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["esp32_ble_tracker"] ble_client_ns = cg.esphome_ns.namespace("ble_client") -BLEClient = ble_client_ns.class_( - "BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient -) +BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase) BLEClientNode = ble_client_ns.class_("BLEClientNode") BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const") # Triggers diff --git a/esphome/components/ble_client/automation.cpp b/esphome/components/ble_client/automation.cpp index 6918ab31b4..395950dd26 100644 --- a/esphome/components/ble_client/automation.cpp +++ b/esphome/components/ble_client/automation.cpp @@ -1,8 +1,8 @@ #include "automation.h" +#include #include #include -#include #include "esphome/core/log.h" @@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector &value) { } ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str()); esp_err_t err = - esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(), - const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_, + value.size(), const_cast(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_OK) { ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err)); } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 38e64ebd76..ba5f78109e 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { void loop() override {} void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override { - if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0) + if (event == ESP_GATTC_DISCONNECT_EVT && + memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0) this->trigger(); if (event == ESP_GATTC_SEARCH_CMPL_EVT) this->node_state = espbt::ClientState::ESTABLISHED; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 5f58d8273f..3f86df32f5 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -1,8 +1,9 @@ -#include "esphome/core/log.h" +#include "ble_client.h" +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include "ble_client.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 @@ -11,22 +12,13 @@ namespace ble_client { static const char *const TAG = "ble_client"; -float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } - void BLEClient::setup() { - auto ret = esp_ble_gattc_app_register(this->app_id); - if (ret) { - ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); - this->mark_failed(); - } - this->set_states_(espbt::ClientState::IDLE); + BLEClientBase::setup(); this->enabled = true; } void BLEClient::loop() { - if (this->state() == espbt::ClientState::DISCOVERED) { - this->connect(); - } + BLEClientBase::loop(); for (auto *node : this->nodes_) node->loop(); } @@ -39,34 +31,7 @@ void BLEClient::dump_config() { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { if (!this->enabled) return false; - if (device.address_uint64() != this->address) - return false; - if (this->state() != espbt::ClientState::IDLE) - return false; - - ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); - this->set_states_(espbt::ClientState::DISCOVERED); - - auto addr = device.address_uint64(); - this->remote_bda[0] = (addr >> 40) & 0xFF; - this->remote_bda[1] = (addr >> 32) & 0xFF; - this->remote_bda[2] = (addr >> 24) & 0xFF; - this->remote_bda[3] = (addr >> 16) & 0xFF; - this->remote_bda[4] = (addr >> 8) & 0xFF; - this->remote_bda[5] = (addr >> 0) & 0xFF; - this->remote_addr_type = device.get_address_type(); - return true; -} - -std::string BLEClient::address_str() const { - char buf[20]; - sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff, - (uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff, - (uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff, - (uint8_t)(this->address >> 0) & 0xff); - std::string ret; - ret = buf; - return ret; + return BLEClientBase::parse_device(device); } void BLEClient::set_enabled(bool enabled) { @@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) { return; if (!enabled && this->state() != espbt::ClientState::IDLE) { ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str()); - auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); + auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); if (ret) { ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret); } @@ -82,125 +47,12 @@ void BLEClient::set_enabled(bool enabled) { this->enabled = enabled; } -void BLEClient::connect() { - ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); - auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); - this->set_states_(espbt::ClientState::IDLE); - } else { - this->set_states_(espbt::ClientState::CONNECTING); - } -} - void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, esp_ble_gattc_cb_param_t *param) { - if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) - return; - if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if) - return; - bool all_established = this->all_nodes_established_(); - switch (event) { - case ESP_GATTC_REG_EVT: { - if (param->reg.status == ESP_GATT_OK) { - ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); - this->gattc_if = esp_gattc_if; - } else { - ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); - } - break; - } - case ESP_GATTC_OPEN_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); - this->conn_id = param->open.conn_id; - if (param->open.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - break; - } - case ESP_GATTC_CONNECT_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); - if (this->conn_id != param->connect.conn_id) { - ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", - this->address_str().c_str(), param->connect.conn_id, this->conn_id); - } - auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); - if (ret) { - ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); - } - break; - } - case ESP_GATTC_CFG_MTU_EVT: { - if (param->cfg_mtu.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, - param->cfg_mtu.status); - this->set_states_(espbt::ClientState::IDLE); - break; - } - ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); - break; - } - case ESP_GATTC_DISCONNECT_EVT: { - if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { - return; - } - ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); - for (auto &svc : this->services_) - delete svc; // NOLINT(cppcoreguidelines-owning-memory) - this->services_.clear(); - this->set_states_(espbt::ClientState::IDLE); - break; - } - case ESP_GATTC_SEARCH_RES_EVT: { - BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) - ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); - ble_service->start_handle = param->search_res.start_handle; - ble_service->end_handle = param->search_res.end_handle; - ble_service->client = this; - this->services_.push_back(ble_service); - break; - } - case ESP_GATTC_SEARCH_CMPL_EVT: { - ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); - for (auto &svc : this->services_) { - ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); - ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); - svc->parse_characteristics(); - } - this->set_states_(espbt::ClientState::CONNECTED); - this->set_state(espbt::ClientState::ESTABLISHED); - break; - } - case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); - if (descr == nullptr) { - ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); - break; - } - if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || - descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { - ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, - descr->uuid.to_string().c_str()); - break; - } - uint16_t notify_en = 1; - auto status = - esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en), - (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); - } - break; - } + BLEClientBase::gattc_event_handler(event, esp_gattc_if, param); - default: - break; - } for (auto *node : this->nodes_) node->gattc_event_handler(event, esp_gattc_if, param); @@ -212,237 +64,20 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } -void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - switch (event) { - // This event is sent by the server when it requests security - case ESP_GAP_BLE_SEC_REQ_EVT: - ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - break; - // This event is sent once authentication has completed - case ESP_GAP_BLE_AUTH_CMPL_EVT: - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); - if (!param->ble_security.auth_cmpl.success) { - ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); - } else { - ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, - param->ble_security.auth_cmpl.auth_mode); - } - break; - // There are other events we'll want to implement at some point to support things like pass key - // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md - default: - break; +void BLEClient::set_state(espbt::ClientState state) { + BLEClientBase::set_state(state); + for (auto &node : nodes_) + node->node_state = state; +} + +bool BLEClient::all_nodes_established_() { + if (this->state() != espbt::ClientState::ESTABLISHED) + return false; + for (auto &node : nodes_) { + if (node->node_state != espbt::ClientState::ESTABLISHED) + return false; } -} - -// Parse GATT values into a float for a sensor. -// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ -float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { - // A length of one means a single octet value. - if (length == 0) - return 0; - if (length == 1) - return (float) ((uint8_t) value[0]); - - switch (value[0]) { - case 0x1: // boolean. - case 0x2: // 2bit. - case 0x3: // nibble. - case 0x4: // uint8. - return (float) ((uint8_t) value[1]); - case 0x5: // uint12. - case 0x6: // uint16. - if (length > 2) { - return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]); - } - case 0x7: // uint24. - if (length > 3) { - return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3])); - } - case 0x8: // uint32. - if (length > 4) { - return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) + - (uint32_t)(value[4])); - } - case 0xC: // int8. - return (float) ((int8_t) value[1]); - case 0xD: // int12. - case 0xE: // int16. - if (length > 2) { - return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); - } - case 0xF: // int24. - if (length > 3) { - return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); - } - case 0x10: // int32. - if (length > 4) { - return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + - (int32_t)(value[4])); - } - } - ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); - return NAN; -} - -BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto *svc : this->services_) { - if (svc->uuid == uuid) - return svc; - } - return nullptr; -} - -BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } - -BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - return svc->get_characteristic(chr); -} - -BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); -} - -BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) { - for (auto &svc : this->services_) { - for (auto &chr : svc->characteristics) { - if (chr->handle == handle) { - for (auto &desc : chr->descriptors) { - if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) - return desc; - } - } - } - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { - for (auto &chr : this->characteristics) { - if (chr->uuid == uuid) - return chr; - } - return nullptr; -} - -BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { - return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); -} - -BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { - auto *svc = this->get_service(service); - if (svc == nullptr) - return nullptr; - auto *ch = svc->get_characteristic(chr); - if (ch == nullptr) - return nullptr; - return ch->get_descriptor(descr); -} - -BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), - espbt::ESPBTUUID::from_uint16(descr)); -} - -BLEService::~BLEService() { - for (auto &chr : this->characteristics) - delete chr; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLEService::parse_characteristics() { - uint16_t offset = 0; - esp_gattc_char_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_char( - this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) - characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - characteristic->properties = result.properties; - characteristic->handle = result.char_handle; - characteristic->service = this; - this->characteristics.push_back(characteristic); - ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), - characteristic->handle, characteristic->properties); - characteristic->parse_descriptors(); - offset++; - } -} - -BLECharacteristic::~BLECharacteristic() { - for (auto &desc : this->descriptors) - delete desc; // NOLINT(cppcoreguidelines-owning-memory) -} - -void BLECharacteristic::parse_descriptors() { - uint16_t offset = 0; - esp_gattc_descr_elem_t result; - - while (true) { - uint16_t count = 1; - esp_gatt_status_t status = esp_ble_gattc_get_all_descr( - this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset); - if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { - break; - } - if (status != ESP_GATT_OK) { - ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); - break; - } - if (count == 0) { - break; - } - - BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) - desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); - desc->handle = result.handle; - desc->characteristic = this; - this->descriptors.push_back(desc); - ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); - offset++; - } -} - -BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { - for (auto &desc : this->descriptors) { - if (desc->uuid == uuid) - return desc; - } - return nullptr; -} -BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { - return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { - auto *client = this->service->client; - auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, - write_type, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); - } -} - -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); + return true; } } // namespace ble_client diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 5ed8f219d1..0f8373ab1f 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -1,8 +1,9 @@ #pragma once +#include "esphome/components/esp32_ble_client/ble_client_base.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #ifdef USE_ESP32 @@ -18,9 +19,9 @@ namespace ble_client { namespace espbt = esphome::esp32_ble_tracker; +using namespace esp32_ble_client; + class BLEClient; -class BLEService; -class BLECharacteristic; class BLEClientNode { public: @@ -42,57 +43,15 @@ class BLEClientNode { uint64_t address_; }; -class BLEDescriptor { - public: - espbt::ESPBTUUID uuid; - uint16_t handle; - - BLECharacteristic *characteristic; -}; - -class BLECharacteristic { - public: - ~BLECharacteristic(); - espbt::ESPBTUUID uuid; - uint16_t handle; - esp_gatt_char_prop_t properties; - std::vector descriptors; - void parse_descriptors(); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); - BLEDescriptor *get_descriptor(uint16_t uuid); - void write_value(uint8_t *new_val, int16_t new_val_size); - void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); - BLEService *service; -}; - -class BLEService { - public: - ~BLEService(); - espbt::ESPBTUUID uuid; - uint16_t start_handle; - uint16_t end_handle; - std::vector characteristics; - BLEClient *client; - void parse_characteristics(); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); - BLECharacteristic *get_characteristic(uint16_t uuid); -}; - -class BLEClient : public espbt::ESPBTClient, public Component { +class BLEClient : public BLEClientBase { public: void setup() override; void dump_config() override; void loop() override; - float get_setup_priority() const override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; - void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; - void on_scan_end() override {} - void connect() override; - - void set_address(uint64_t address) { this->address = address; } void set_enabled(bool enabled); @@ -102,43 +61,14 @@ class BLEClient : public espbt::ESPBTClient, public Component { this->nodes_.push_back(node); } - BLEService *get_service(espbt::ESPBTUUID uuid); - BLEService *get_service(uint16_t uuid); - BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); - BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); - BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); - BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); - // Get the configuration descriptor for the given characteristic handle. - BLEDescriptor *get_config_descriptor(uint16_t handle); - - float parse_char_value(uint8_t *value, uint16_t length); - - int gattc_if; - esp_bd_addr_t remote_bda; - esp_ble_addr_type_t remote_addr_type; - uint16_t conn_id; - uint64_t address; bool enabled; - std::string address_str() const; + + void set_state(espbt::ClientState state) override; protected: - void set_states_(espbt::ClientState st) { - this->set_state(st); - for (auto &node : nodes_) - node->node_state = st; - } - bool all_nodes_established_() { - if (this->state() != espbt::ClientState::ESTABLISHED) - return false; - for (auto &node : nodes_) { - if (node->node_state != espbt::ClientState::ESTABLISHED) - return false; - } - return true; - } + bool all_nodes_established_(); std::vector nodes_; - std::vector services_; }; } // namespace ble_client diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index 2baaafe2ec..d830165d30 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger, public BLESensor { break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index ee5afd3f7b..a05efad60b 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -122,8 +122,8 @@ void BLESensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(NAN); diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index be85892c5a..c504c35a58 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || + param->notify.handle != this->sensor_->handle) break; this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 1a71cd6cd8..1a304593c7 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->handle = descr->handle; } if (this->notify_) { - auto status = - esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), + this->parent()->get_remote_bda(), chr->handle); if (status) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); } @@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle) break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); @@ -121,8 +121,8 @@ void BLETextSensor::update() { return; } - auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle, + ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); this->publish_state(EMPTY); diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index a6a5a4391b..b5acea89dd 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -1,19 +1,23 @@ -from esphome.components import esp32_ble_tracker +from esphome.components import esp32_ble_tracker, esp32_ble_client import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ACTIVE, CONF_ID -DEPENDENCIES = ["esp32", "esp32_ble_tracker"] +AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] +DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz"] bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") -BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) +BluetoothProxy = bluetooth_proxy_ns.class_( + "BluetoothProxy", esp32_ble_client.BLEClientBase +) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(BluetoothProxy), + cv.Optional(CONF_ACTIVE, default=False): cv.boolean, } ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) @@ -22,6 +26,8 @@ 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_active(config[CONF_ACTIVE])) + + await esp32_ble_tracker.register_client(var, config) cg.add_define("USE_BLUETOOTH_PROXY") diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 41871295ce..96fee39f95 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -1,22 +1,39 @@ #include "bluetooth_proxy.h" -#ifdef USE_API -#include "esphome/components/api/api_pb2.h" -#include "esphome/components/api/api_server.h" -#endif // USE_API #include "esphome/core/log.h" #ifdef USE_ESP32 +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + namespace esphome { namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy"; +BluetoothProxy::BluetoothProxy() { + global_bluetooth_proxy = this; + this->address_ = 0; +} + bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), device.get_rssi()); this->send_api_packet_(device); + + this->address_type_map_[device.address_uint64()] = device.get_address_type(); + + if (this->address_ == 0) + return true; + + if (this->state_ == espbt::ClientState::DISCOVERED) { + ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str()); + return true; + } + + BLEClientBase::parse_device(device); return true; } @@ -35,23 +52,309 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi for (auto &data : device.get_service_datas()) { api::BluetoothServiceData service_data; service_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - service_data.data.push_back(d); - resp.service_data.push_back(service_data); + service_data.data.assign(data.data.begin(), data.data.end()); + resp.service_data.push_back(std::move(service_data)); } for (auto &data : device.get_manufacturer_datas()) { api::BluetoothServiceData manufacturer_data; manufacturer_data.uuid = data.uuid.to_string(); - for (auto d : data.data) - manufacturer_data.data.push_back(d); - resp.manufacturer_data.push_back(manufacturer_data); + manufacturer_data.data.assign(data.data.begin(), data.data.end()); + resp.manufacturer_data.push_back(std::move(manufacturer_data)); } api::global_api_server->send_bluetooth_le_advertisement(resp); #endif } +void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + BLEClientBase::gattc_event_handler(event, gattc_if, param); + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, + param->disconnect.reason); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + this->address_ = 0; + } + case ESP_GATTC_OPEN_EVT: { + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status); + +#endif + break; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { +#ifdef USE_API + api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); + api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), + this->get_bluetooth_connections_limit()); +#endif + break; + } + case ESP_GATTC_READ_DESCR_EVT: + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->conn_id_) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status); + break; + } +#ifdef USE_API + api::BluetoothGATTReadResponse resp; + resp.address = this->address_; + resp.handle = param->read.handle; + resp.data.reserve(param->read.value_len); + for (uint16_t i = 0; i < param->read.value_len; i++) { + resp.data.push_back(param->read.value[i]); + } + api::global_api_server->send_bluetooth_gatt_read_response(resp); +#endif + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->conn_id_) + break; + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle); +#ifdef USE_API + api::BluetoothGATTNotifyDataResponse resp; + resp.address = this->address_; + resp.handle = param->notify.handle; + resp.data.reserve(param->notify.value_len); + for (uint16_t i = 0; i < param->notify.value_len; i++) { + resp.data.push_back(param->notify.value[i]); + } + api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); +#endif + break; + } + default: + break; + } +} + void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } +void BluetoothProxy::loop() { + BLEClientBase::loop(); +#ifdef USE_API + if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + + if (this->send_service_ == this->services_.size()) { + this->send_service_ = -1; + api::global_api_server->send_bluetooth_gatt_services_done(this->address_); + } else if (this->send_service_ >= 0) { + auto &service = this->services_[this->send_service_]; + api::BluetoothGATTGetServicesResponse resp; + resp.address = this->address_; + api::BluetoothGATTService service_resp; + service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()}; + service_resp.handle = service->start_handle; + for (auto &characteristic : service->characteristics) { + api::BluetoothGATTCharacteristic characteristic_resp; + characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()}; + characteristic_resp.handle = characteristic->handle; + characteristic_resp.properties = characteristic->properties; + for (auto &descriptor : characteristic->descriptors) { + api::BluetoothGATTDescriptor descriptor_resp; + descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()}; + descriptor_resp.handle = descriptor->handle; + characteristic_resp.descriptors.push_back(std::move(descriptor_resp)); + } + service_resp.characteristics.push_back(std::move(characteristic_resp)); + } + resp.services.push_back(std::move(service_resp)); + api::global_api_server->send_bluetooth_gatt_services(resp); + this->send_service_++; + } +#endif +} + +#ifdef USE_API +void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { + switch (msg.request_type) { + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { + this->address_ = msg.address; + if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) { + // Utilise the address type cache + this->remote_addr_type_ = this->address_type_map_[this->address_]; + } else { + this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC; + } + this->remote_bda_[0] = (this->address_ >> 40) & 0xFF; + this->remote_bda_[1] = (this->address_ >> 32) & 0xFF; + this->remote_bda_[2] = (this->address_ >> 24) & 0xFF; + this->remote_bda_[3] = (this->address_ >> 16) & 0xFF; + this->remote_bda_[4] = (this->address_ >> 8) & 0xFF; + this->remote_bda_[5] = (this->address_ >> 0) & 0xFF; + this->set_state(espbt::ClientState::DISCOVERED); + esp_ble_gap_stop_scanning(); + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { + if (this->state() != espbt::ClientState::IDLE) { + ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); + auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err); + } + } + break; + } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: + break; + } +} + +void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str()); + characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(), + msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP); +} + +void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected."); + return; + } + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request"); + return; + } + + auto *descriptor = this->get_descriptor(msg.handle); + if (descriptor == nullptr) { + ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found."); + return; + } + + ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(), + descriptor->uuid.to_string().c_str()); + + esp_err_t err = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(), + (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err); + } +} + +void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for service list request"); + return; + } + this->send_service_ = 0; +} + +void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) { + if (this->address_ != msg.address) { + ESP_LOGW(TAG, "Address mismatch for notify"); + return; + } + + auto *characteristic = this->get_characteristic(msg.handle); + + if (characteristic == nullptr) { + ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found."); + return; + } + + esp_err_t err; + if (msg.enable) { + err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err); + } + } else { + err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err); + } + } +} + +#endif + +BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 9a936747c0..8ff43aff3f 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -4,22 +4,59 @@ #include +#include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#include + +#ifdef USE_API +#include "esphome/components/api/api_pb2.h" +#endif // USE_API namespace esphome { namespace bluetooth_proxy { -class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { +using namespace esp32_ble_client; + +class BluetoothProxy : public BLEClientBase { public: + BluetoothProxy(); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + +#ifdef USE_API + void bluetooth_device_request(const api::BluetoothDeviceRequest &msg); + void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg); + void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg); + void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg); + void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg); + void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg); + void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg); +#endif + + int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; } + int get_bluetooth_connections_limit() { return 1; } + + void set_active(bool active) { this->active_ = active; } + bool has_active() { return this->active_; } protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); + + std::map address_type_map_; + int16_t send_service_{-1}; + bool active_; }; +extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py new file mode 100644 index 0000000000..94a5576d0b --- /dev/null +++ b/esphome/components/esp32_ble_client/__init__.py @@ -0,0 +1,12 @@ +import esphome.codegen as cg + +from esphome.components import esp32_ble_tracker + +AUTO_LOAD = ["esp32_ble_tracker"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["esp32"] + +esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client") +BLEClientBase = esp32_ble_client_ns.class_( + "BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component +) diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp new file mode 100644 index 0000000000..873833368c --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -0,0 +1,83 @@ +#include "ble_characteristic.h" +#include "ble_client_base.h" +#include "ble_service.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.characteristic"; + +BLECharacteristic::~BLECharacteristic() { + for (auto &desc : this->descriptors) + delete desc; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLECharacteristic::parse_descriptors() { + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(), + this->handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) + desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + desc->handle = result.handle; + desc->characteristic = this; + this->descriptors.push_back(desc); + ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle); + offset++; + } +} + +BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { + for (auto &desc : this->descriptors) { + if (desc->uuid == uuid) + return desc; + } + return nullptr; +} +BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); +} +BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) { + for (auto &desc : this->descriptors) { + if (desc->handle == handle) + return desc; + } + return nullptr; +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { + auto *client = this->service->client; + auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size, + new_val, write_type, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); + } +} + +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_characteristic.h b/esphome/components/esp32_ble_client/ble_characteristic.h new file mode 100644 index 0000000000..ffa9227cc4 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_characteristic.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_descriptor.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEService; + +class BLECharacteristic { + public: + ~BLECharacteristic(); + espbt::ESPBTUUID uuid; + uint16_t handle; + esp_gatt_char_prop_t properties; + std::vector descriptors; + void parse_descriptors(); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); + BLEDescriptor *get_descriptor(uint16_t uuid); + BLEDescriptor *get_descriptor_by_handle(uint16_t handle); + void write_value(uint8_t *new_val, int16_t new_val_size); + void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); + BLEService *service; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp new file mode 100644 index 0000000000..0e81c9aca8 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -0,0 +1,324 @@ +#include "ble_client_base.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client"; + +void BLEClientBase::setup() { + auto ret = esp_ble_gattc_app_register(this->app_id); + if (ret) { + ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); + this->mark_failed(); + } + this->set_state(espbt::ClientState::IDLE); +} + +void BLEClientBase::loop() { + if (this->state_ == espbt::ClientState::DISCOVERED) { + this->connect(); + } +} + +float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + +bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) + return false; + if (this->state_ != espbt::ClientState::IDLE) + return false; + + ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); + this->set_state(espbt::ClientState::DISCOVERED); + + auto addr = device.address_uint64(); + this->remote_bda_[0] = (addr >> 40) & 0xFF; + this->remote_bda_[1] = (addr >> 32) & 0xFF; + this->remote_bda_[2] = (addr >> 24) & 0xFF; + this->remote_bda_[3] = (addr >> 16) & 0xFF; + this->remote_bda_[4] = (addr >> 8) & 0xFF; + this->remote_bda_[5] = (addr >> 0) & 0xFF; + this->remote_addr_type_ = device.get_address_type(); + return true; +} + +std::string BLEClientBase::address_str() const { + return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff, + (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff, + (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff, + (uint8_t)(this->address_ >> 0) & 0xff); +} + +void BLEClientBase::connect() { + ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str()); + auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret); + this->set_state(espbt::ClientState::IDLE); + } else { + this->set_state(espbt::ClientState::CONNECTING); + } +} + +void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if, + esp_ble_gattc_cb_param_t *param) { + if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id) + return; + if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_) + return; + + switch (event) { + case ESP_GATTC_REG_EVT: { + if (param->reg.status == ESP_GATT_OK) { + ESP_LOGV(TAG, "gattc registered app id %d", this->app_id); + this->gattc_if_ = esp_gattc_if; + } else { + ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status); + } + break; + } + case ESP_GATTC_OPEN_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str()); + this->conn_id_ = param->open.conn_id; + if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { + ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + break; + } + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); + if (this->conn_id_ != param->connect.conn_id) { + ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d", + this->address_str().c_str(), param->connect.conn_id, this->conn_id_); + } + auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id); + if (ret) { + ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); + } + break; + } + case ESP_GATTC_CFG_MTU_EVT: { + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, + param->cfg_mtu.status); + this->set_state(espbt::ClientState::IDLE); + break; + } + ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); + this->mtu_ = param->cfg_mtu.mtu; + esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) { + return; + } + ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); + for (auto &svc : this->services_) + delete svc; // NOLINT(cppcoreguidelines-owning-memory) + this->services_.clear(); + this->set_state(espbt::ClientState::IDLE); + break; + } + case ESP_GATTC_SEARCH_RES_EVT: { + BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) + ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); + ble_service->start_handle = param->search_res.start_handle; + ble_service->end_handle = param->search_res.end_handle; + ble_service->client = this; + this->services_.push_back(ble_service); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str()); + for (auto &svc : this->services_) { + ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str()); + ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle); + svc->parse_characteristics(); + } + this->set_state(espbt::ClientState::CONNECTED); + this->state_ = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); + if (descr == nullptr) { + ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); + break; + } + if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || + descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle, + descr->uuid.to_string().c_str()); + break; + } + uint16_t notify_en = 1; + auto status = + esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en), + (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status); + } + break; + } + + default: + break; + } +} + +void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // This event is sent by the server when it requests security + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + // This event is sent once authentication has completed + case ESP_GAP_BLE_AUTH_CMPL_EVT: + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, + param->ble_security.auth_cmpl.auth_mode); + } + break; + // There are other events we'll want to implement at some point to support things like pass key + // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md + default: + break; + } +} + +// Parse GATT values into a float for a sensor. +// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ +float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { + // A length of one means a single octet value. + if (length == 0) + return 0; + if (length == 1) + return (float) ((uint8_t) value[0]); + + switch (value[0]) { + case 0x1: // boolean. + case 0x2: // 2bit. + case 0x3: // nibble. + case 0x4: // uint8. + return (float) ((uint8_t) value[1]); + case 0x5: // uint12. + case 0x6: // uint16. + if (length > 2) { + return (float) encode_uint16(value[1], value[2]); + } + case 0x7: // uint24. + if (length > 3) { + return (float) encode_uint24(value[1], value[2], value[3]); + } + case 0x8: // uint32. + if (length > 4) { + return (float) encode_uint32(value[1], value[2], value[3], value[4]); + } + case 0xC: // int8. + return (float) ((int8_t) value[1]); + case 0xD: // int12. + case 0xE: // int16. + if (length > 2) { + return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]); + } + case 0xF: // int24. + if (length > 3) { + return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3])); + } + case 0x10: // int32. + if (length > 4) { + return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) + + (int32_t)(value[4])); + } + } + ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length); + return NAN; +} + +BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) { + for (auto *svc : this->services_) { + if (svc->uuid == uuid) + return svc; + } + return nullptr; +} + +BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } + +BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + return svc->get_characteristic(chr); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr)); +} + +BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + if (chr->handle == handle) + return chr; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) { + auto *chr = this->get_characteristic(handle); + if (chr != nullptr) { + for (auto &desc : chr->descriptors) { + if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) + return desc; + } + } + return nullptr; +} + +BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { + auto *svc = this->get_service(service); + if (svc == nullptr) + return nullptr; + auto *ch = svc->get_characteristic(chr); + if (ch == nullptr) + return nullptr; + return ch->get_descriptor(descr); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) { + return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr), + espbt::ESPBTUUID::from_uint16(descr)); +} + +BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) { + for (auto *svc : this->services_) { + for (auto *chr : svc->characteristics) { + for (auto *desc : chr->descriptors) { + if (desc->handle == handle) + return desc; + } + } + } + return nullptr; +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h new file mode 100644 index 0000000000..eba70fa571 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -0,0 +1,72 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/component.h" + +#include "ble_service.h" + +#include +#include + +#include +#include +#include +#include + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase : public espbt::ESPBTClient, public Component { + public: + void setup() override; + void loop() override; + float get_setup_priority() const override; + + bool parse_device(const espbt::ESPBTDevice &device) override; + void on_scan_end() override {} + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + void connect() override; + + void set_address(uint64_t address) { this->address_ = address; } + std::string address_str() const; + + BLEService *get_service(espbt::ESPBTUUID uuid); + BLEService *get_service(uint16_t uuid); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr); + BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr); + BLECharacteristic *get_characteristic(uint16_t handle); + BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr); + BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr); + BLEDescriptor *get_descriptor(uint16_t handle); + // Get the configuration descriptor for the given characteristic handle. + BLEDescriptor *get_config_descriptor(uint16_t handle); + + float parse_char_value(uint8_t *value, uint16_t length); + + int get_gattc_if() const { return this->gattc_if_; } + uint8_t *get_remote_bda() { return this->remote_bda_; } + esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; } + uint16_t get_conn_id() const { return this->conn_id_; } + uint64_t get_address() const { return this->address_; } + + protected: + int gattc_if_; + esp_bd_addr_t remote_bda_; + esp_ble_addr_type_t remote_addr_type_; + uint16_t conn_id_; + uint64_t address_; + uint16_t mtu_{23}; + + std::vector services_; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_descriptor.h b/esphome/components/esp32_ble_client/ble_descriptor.h new file mode 100644 index 0000000000..c05430144f --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_descriptor.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLECharacteristic; + +class BLEDescriptor { + public: + espbt::ESPBTUUID uuid; + uint16_t handle; + + BLECharacteristic *characteristic; +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp new file mode 100644 index 0000000000..1d81eaa556 --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -0,0 +1,66 @@ +#include "ble_service.h" +#include "ble_client_base.h" + +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32_ble_client { + +static const char *const TAG = "esp32_ble_client.service"; + +BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { + for (auto &chr : this->characteristics) { + if (chr->uuid == uuid) + return chr; + } + return nullptr; +} + +BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { + return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid)); +} + +BLEService::~BLEService() { + for (auto &chr : this->characteristics) + delete chr; // NOLINT(cppcoreguidelines-owning-memory) +} + +void BLEService::parse_characteristics() { + uint16_t offset = 0; + esp_gattc_char_elem_t result; + + while (true) { + uint16_t count = 1; + esp_gatt_status_t status = + esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle, + this->end_handle, &result, &count, offset); + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { + break; + } + if (status != ESP_GATT_OK) { + ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status); + break; + } + if (count == 0) { + break; + } + + BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) + characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); + characteristic->properties = result.properties; + characteristic->handle = result.char_handle; + characteristic->service = this; + this->characteristics.push_back(characteristic); + ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(), + characteristic->handle, characteristic->properties); + characteristic->parse_descriptors(); + offset++; + } +} + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_client/ble_service.h b/esphome/components/esp32_ble_client/ble_service.h new file mode 100644 index 0000000000..04f2212e0e --- /dev/null +++ b/esphome/components/esp32_ble_client/ble_service.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#include "ble_characteristic.h" + +namespace esphome { +namespace esp32_ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientBase; + +class BLEService { + public: + ~BLEService(); + espbt::ESPBTUUID uuid; + uint16_t start_handle; + uint16_t end_handle; + std::vector characteristics; + BLEClientBase *client; + void parse_characteristics(); + BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid); + BLECharacteristic *get_characteristic(uint16_t uuid); +}; + +} // namespace esp32_ble_client +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 6d7868d4c5..0d7abe32f9 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.const import ( + CONF_ACTIVE, CONF_ID, CONF_INTERVAL, CONF_DURATION, @@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" CONF_WINDOW = "window" -CONF_ACTIVE = "active" CONF_CONTINUOUS = "continuous" CONF_ON_SCAN_END = "on_scan_end" esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index f7e51a8ab3..d4512b52b0 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } std::string ESPBTUUID::to_string() const { - char sbuf[64]; switch (this->uuid_.len) { case ESP_UUID_LEN_16: - sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); - break; + return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); case ESP_UUID_LEN_32: - sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff), - (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff); - break; + return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24, + (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff), + this->uuid_.uuid.uuid32 & 0xff); default: case ESP_UUID_LEN_128: - char *bpos = sbuf; + std::string buf; for (int8_t i = 15; i >= 0; i--) { - sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]); - bpos += 2; + buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]); if (i == 6 || i == 8 || i == 10 || i == 12) - sprintf(bpos++, "-"); + buf += "-"; } - sbuf[47] = '\0'; - break; + return buf; } - return sbuf; + return ""; +} + +uint64_t ESPBTUUID::get_128bit_high() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | + ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) | + ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) | + ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]); +} +uint64_t ESPBTUUID::get_128bit_low() const { + esp_bt_uuid_t uuid = this->as_128bit().get_uuid(); + return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) | + ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) | + ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) | + ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]); } ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 29d0c81542..45abcd63fa 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -41,6 +41,9 @@ class ESPBTUUID { std::string to_string() const; + uint64_t get_128bit_high() const; + uint64_t get_128bit_low() const; + protected: esp_bt_uuid_t uuid_; }; @@ -158,7 +161,7 @@ class ESPBTClient : public ESPBTDeviceListener { esp_ble_gattc_cb_param_t *param) = 0; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; virtual void connect() = 0; - void set_state(ClientState st) { this->state_ = st; } + virtual void set_state(ClientState st) { this->state_ = st; } ClientState state() const { return state_; } int app_id; diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index f09b2ca8d7..f3a2b3cb3c 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -72,13 +72,13 @@ class BLEEvent { // Need to also make a copy of relevant event data. switch (e) { case ESP_GATTC_NOTIFY_EVT: - memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len); - this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data; + this->data.assign(p->notify.value, p->notify.value + p->notify.value_len); + this->event_.gattc.gattc_param.notify.value = this->data.data(); break; case ESP_GATTC_READ_CHAR_EVT: case ESP_GATTC_READ_DESCR_EVT: - memcpy(this->event_.gattc.data, p->read.value, p->read.value_len); - this->event_.gattc.gattc_param.read.value = this->event_.gattc.data; + this->data.assign(p->read.value, p->read.value + p->read.value_len); + this->event_.gattc.gattc_param.read.value = this->data.data(); break; default: break; @@ -96,9 +96,9 @@ class BLEEvent { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; - uint8_t data[64]; } gattc; } event_; + std::vector data{}; uint8_t type_; // 0=gap 1=gattc }; diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 21638ef7e4..384537e5d7 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -102,8 +102,9 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str()); return; } - auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size, + blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } else { diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 6bb17f0508..3959178b94 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -50,7 +50,7 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->conn_id) + if (param->read.conn_id != this->parent()->get_conn_id()) break; if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); @@ -146,17 +146,17 @@ void RadonEyeRD200::update() { void RadonEyeRD200::write_query_message_() { ESP_LOGV(TAG, "writing 0x50 to write service"); int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, - sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->write_handle_, sizeof(request), (uint8_t *) &request, + ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); } } void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, - ESP_GATT_AUTH_REQ_NONE); + auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), + this->read_handle_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } diff --git a/esphome/const.py b/esphome/const.py index a272f518df..e9a6975ca2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -23,6 +23,7 @@ CONF_ACCURACY = "accuracy" CONF_ACCURACY_DECIMALS = "accuracy_decimals" CONF_ACTION_ID = "action_id" CONF_ACTION_STATE_TOPIC = "action_state_topic" +CONF_ACTIVE = "active" CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" From 6b52f62531519451d934fe8cfbe74bbcf0b8564e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:22:01 +1300 Subject: [PATCH 115/838] Bump version to 2022.9.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e9a6975ca2..ff59f2b8bf 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.2" +__version__ = "2022.9.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 7171286c3cf63ea73f176522b8ab443e45ebb4c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:57:07 +1300 Subject: [PATCH 116/838] Bump pylint from 2.15.2 to 2.15.3 (#3870) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e7b6387b73..d72449fd66 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.15.2 +pylint==2.15.3 flake8==5.0.4 black==22.8.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.0.0 # also change in .pre-commit-config.yaml when updating From 01b7c4200ec498057a51caba24fd40910fa2ca90 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:42:28 +1300 Subject: [PATCH 117/838] Add network type to mdns service message (#3880) --- esphome/components/mdns/mdns_component.cpp | 6 ++++++ esphome/dashboard/dashboard.py | 1 + esphome/zeroconf.py | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 31858c0d3c..e1fcf320ed 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -45,6 +45,12 @@ void MDNSComponent::compile_records_() { service.txt_records.push_back({"board", ESPHOME_BOARD}); +#if defined(USE_WIFI) + service.txt_records.push_back({"network", "wifi"}); +#elif defined(USE_ETHERNET) + service.txt_records.push_back({"network", "ethernet"}); +#endif + #ifdef ESPHOME_PROJECT_NAME service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 4f361d0936..207d5cae93 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -613,6 +613,7 @@ class ListDevicesHandler(BaseHandler): "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 diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 3a491d9b99..3743f650f3 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -118,6 +118,7 @@ ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local." TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url" TXT_RECORD_PROJECT_NAME = b"project_name" TXT_RECORD_PROJECT_VERSION = b"project_version" +TXT_RECORD_NETWORK = b"network" @dataclass @@ -126,6 +127,7 @@ class DiscoveredImport: package_import_url: str project_name: str project_version: str + network: str class DashboardImportDiscovery: @@ -134,7 +136,7 @@ class DashboardImportDiscovery: self.service_browser = ServiceBrowser( self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] ) - self.import_state = {} + self.import_state: dict[str, DiscoveredImport] = {} def _on_update( self, @@ -171,12 +173,14 @@ class DashboardImportDiscovery: import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode() project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode() + network = info.properties.get(TXT_RECORD_NETWORK, b"wifi").decode() self.import_state[name] = DiscoveredImport( device_name=node_name, package_import_url=import_url, project_name=project_name, project_version=project_version, + network=network, ) def cancel(self) -> None: From 6087183a0c28f82a24545e9972388cf99943e4a7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 15:20:13 +1300 Subject: [PATCH 118/838] Bump esphome-dashboard to 20221007.0 (#3881) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f86acdbef..00e6e0ba67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220925.0 +esphome-dashboard==20221007.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From fd57b21affab08a48fd03e7b1e61c522fd524f0b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 15:35:48 +1300 Subject: [PATCH 119/838] Dont add wifi block to yaml if discovered device uses ethernet (#3882) --- esphome/components/dashboard_import/__init__.py | 15 ++++++++++----- esphome/dashboard/dashboard.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 41b4a8bed1..b795c85b12 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -3,6 +3,7 @@ from pathlib import Path import esphome.codegen as cg import esphome.config_validation as cv from esphome.components.packages import validate_source_shorthand +from esphome.const import CONF_WIFI from esphome.wizard import wizard_file from esphome.yaml_util import dump @@ -43,7 +44,9 @@ async def to_code(config): cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL])) -def import_config(path: str, name: str, project_name: str, import_url: str) -> None: +def import_config( + path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI +) -> None: p = Path(path) if p.exists(): @@ -69,7 +72,9 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N "name_add_mac_suffix": False, }, } - p.write_text( - dump(config) + WIFI_CONFIG, - encoding="utf8", - ) + output = dump(config) + + if network == CONF_WIFI: + output += WIFI_CONFIG + + p.write_text(output, encoding="utf8") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 207d5cae93..1a51f3056f 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -395,11 +395,22 @@ class ImportRequestHandler(BaseHandler): args = json.loads(self.request.body.decode()) try: name = args["name"] + + 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 + else: + network = const.CONF_WIFI + import_config( settings.rel_path(f"{name}.yaml"), name, args["project_name"], args["package_import_url"], + network, ) except FileExistsError: self.set_status(500) From 5df0e82c372362a310b332720e63e47e8284ab01 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:42:28 +1300 Subject: [PATCH 120/838] Add network type to mdns service message (#3880) --- esphome/components/mdns/mdns_component.cpp | 6 ++++++ esphome/dashboard/dashboard.py | 1 + esphome/zeroconf.py | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 31858c0d3c..e1fcf320ed 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -45,6 +45,12 @@ void MDNSComponent::compile_records_() { service.txt_records.push_back({"board", ESPHOME_BOARD}); +#if defined(USE_WIFI) + service.txt_records.push_back({"network", "wifi"}); +#elif defined(USE_ETHERNET) + service.txt_records.push_back({"network", "ethernet"}); +#endif + #ifdef ESPHOME_PROJECT_NAME service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 9a8f072237..c812ec4b49 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -613,6 +613,7 @@ class ListDevicesHandler(BaseHandler): "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 diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 1fbdf7e93f..089ae4f1c2 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -118,6 +118,7 @@ ESPHOME_SERVICE_TYPE = "_esphomelib._tcp.local." TXT_RECORD_PACKAGE_IMPORT_URL = b"package_import_url" TXT_RECORD_PROJECT_NAME = b"project_name" TXT_RECORD_PROJECT_VERSION = b"project_version" +TXT_RECORD_NETWORK = b"network" @dataclass @@ -126,6 +127,7 @@ class DiscoveredImport: package_import_url: str project_name: str project_version: str + network: str class DashboardImportDiscovery: @@ -134,7 +136,7 @@ class DashboardImportDiscovery: self.service_browser = ServiceBrowser( self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] ) - self.import_state = {} + self.import_state: dict[str, DiscoveredImport] = {} def _on_update( self, @@ -171,12 +173,14 @@ class DashboardImportDiscovery: import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode() project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode() + network = info.properties.get(TXT_RECORD_NETWORK, b"wifi").decode() self.import_state[name] = DiscoveredImport( device_name=node_name, package_import_url=import_url, project_name=project_name, project_version=project_version, + network=network, ) def cancel(self) -> None: From 5e96b8ef7c903d5c4a15a1e66384e0c7ff8dfa0f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 15:20:13 +1300 Subject: [PATCH 121/838] Bump esphome-dashboard to 20221007.0 (#3881) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7f86acdbef..00e6e0ba67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.0.2 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20220925.0 +esphome-dashboard==20221007.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 3b83f967e4859ae800ff9448cc47034ef045a011 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 15:35:48 +1300 Subject: [PATCH 122/838] Dont add wifi block to yaml if discovered device uses ethernet (#3882) --- esphome/components/dashboard_import/__init__.py | 15 ++++++++++----- esphome/dashboard/dashboard.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 41b4a8bed1..b795c85b12 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -3,6 +3,7 @@ from pathlib import Path import esphome.codegen as cg import esphome.config_validation as cv from esphome.components.packages import validate_source_shorthand +from esphome.const import CONF_WIFI from esphome.wizard import wizard_file from esphome.yaml_util import dump @@ -43,7 +44,9 @@ async def to_code(config): cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL])) -def import_config(path: str, name: str, project_name: str, import_url: str) -> None: +def import_config( + path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI +) -> None: p = Path(path) if p.exists(): @@ -69,7 +72,9 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N "name_add_mac_suffix": False, }, } - p.write_text( - dump(config) + WIFI_CONFIG, - encoding="utf8", - ) + output = dump(config) + + if network == CONF_WIFI: + output += WIFI_CONFIG + + p.write_text(output, encoding="utf8") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c812ec4b49..07ee151949 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -395,11 +395,22 @@ class ImportRequestHandler(BaseHandler): args = json.loads(self.request.body.decode()) try: name = args["name"] + + 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 + else: + network = const.CONF_WIFI + import_config( settings.rel_path(f"{name}.yaml"), name, args["project_name"], args["package_import_url"], + network, ) except FileExistsError: self.set_status(500) From 3c320c4c836c4611ac8171b0b69be3cdbfc40ee0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:34:39 +1300 Subject: [PATCH 123/838] Bump version to 2022.9.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ff59f2b8bf..e93808e6d4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.9.3" +__version__ = "2022.9.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 786c8b6cfe585914afa9d9676ba84b0be7a8b38c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:54:58 +1300 Subject: [PATCH 124/838] Add new sensor device classes (#3895) --- esphome/components/sensor/__init__.py | 10 ++++++++++ esphome/const.py | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index d6ba038057..12daf34d6e 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -29,6 +29,7 @@ from esphome.const import ( CONF_WINDOW_SIZE, CONF_MQTT_ID, CONF_FORCE_UPDATE, + DEVICE_CLASS_DISTANCE, DEVICE_CLASS_DURATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_APPARENT_POWER, @@ -43,6 +44,7 @@ from esphome.const import ( DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MONETARY, DEVICE_CLASS_NITROGEN_DIOXIDE, DEVICE_CLASS_NITROGEN_MONOXIDE, @@ -56,11 +58,14 @@ from esphome.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SPEED, DEVICE_CLASS_SULPHUR_DIOXIDE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_VOLUME, + DEVICE_CLASS_WEIGHT, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_generator import MockObjClass @@ -77,12 +82,14 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_DATE, + DEVICE_CLASS_DISTANCE, DEVICE_CLASS_DURATION, DEVICE_CLASS_ENERGY, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MONETARY, DEVICE_CLASS_NITROGEN_DIOXIDE, DEVICE_CLASS_NITROGEN_MONOXIDE, @@ -96,11 +103,14 @@ DEVICE_CLASSES = [ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SPEED, DEVICE_CLASS_SULPHUR_DIOXIDE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_VOLUME, + DEVICE_CLASS_WEIGHT, ] sensor_ns = cg.esphome_ns.namespace("sensor") diff --git a/esphome/const.py b/esphome/const.py index 0f056498e4..7dcb3c823c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -905,7 +905,6 @@ DEVICE_CLASS_GARAGE_DOOR = "garage_door" DEVICE_CLASS_HEAT = "heat" DEVICE_CLASS_LIGHT = "light" DEVICE_CLASS_LOCK = "lock" -DEVICE_CLASS_MOISTURE = "moisture" DEVICE_CLASS_MOTION = "motion" DEVICE_CLASS_MOVING = "moving" DEVICE_CLASS_OCCUPANCY = "occupancy" @@ -923,15 +922,17 @@ DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_GAS = "gas" +DEVICE_CLASS_MOISTURE = "moisture" DEVICE_CLASS_POWER = "power" # device classes of sensor component DEVICE_CLASS_APPARENT_POWER = "apparent_power" DEVICE_CLASS_AQI = "aqi" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" -DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_CURRENT = "current" DEVICE_CLASS_DATE = "date" +DEVICE_CLASS_DISTANCE = "distance" DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_FREQUENCY = "frequency" @@ -949,11 +950,14 @@ DEVICE_CLASS_POWER_FACTOR = "power_factor" DEVICE_CLASS_PRESSURE = "pressure" DEVICE_CLASS_REACTIVE_POWER = "reactive_power" DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" +DEVICE_CLASS_SPEED = "speed" DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide" DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" +DEVICE_CLASS_VOLUME = "volume" +DEVICE_CLASS_WEIGHT = "weight" # device classes of both binary_sensor and button component DEVICE_CLASS_UPDATE = "update" # device classes of button component From 3c2766448d7e6f077af42dee7cfcf64c9b4eaa82 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 10 Oct 2022 23:10:22 +0200 Subject: [PATCH 125/838] Refactor xpt2046 to be a touchscreen platform (#3793) --- CODEOWNERS | 2 +- esphome/components/animation/__init__.py | 2 +- esphome/components/xpt2046/__init__.py | 128 +-------------- esphome/components/xpt2046/binary_sensor.py | 54 +------ esphome/components/xpt2046/touchscreen.py | 116 ++++++++++++++ esphome/components/xpt2046/xpt2046.cpp | 168 ++++++++++---------- esphome/components/xpt2046/xpt2046.h | 57 +++---- tests/test4.yaml | 55 +++---- 8 files changed, 248 insertions(+), 334 deletions(-) create mode 100644 esphome/components/xpt2046/touchscreen.py diff --git a/CODEOWNERS b/CODEOWNERS index d95ebcce59..b04b480780 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -258,4 +258,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_rtcgq02lm/* @jesserockz -esphome/components/xpt2046/* @numo68 +esphome/components/xpt2046/* @nielsnl68 @numo68 diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3a150146e2..87d72254e8 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["display"] MULTI_CONF = True -Animation_ = display.display_ns.class_("Animation") +Animation_ = display.display_ns.class_("Animation", espImage.Image_) ANIMATION_SCHEMA = cv.Schema( { diff --git a/esphome/components/xpt2046/__init__.py b/esphome/components/xpt2046/__init__.py index 3de89a6425..3b8a925bb2 100644 --- a/esphome/components/xpt2046/__init__.py +++ b/esphome/components/xpt2046/__init__.py @@ -1,129 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation -from esphome import pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_THRESHOLD, CONF_TRIGGER_ID -CODEOWNERS = ["@numo68"] -AUTO_LOAD = ["binary_sensor"] -DEPENDENCIES = ["spi"] -MULTI_CONF = True - -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_DIMENSION_X = "dimension_x" -CONF_DIMENSION_Y = "dimension_y" -CONF_SWAP_X_Y = "swap_x_y" -CONF_IRQ_PIN = "irq_pin" - -xpt2046_ns = cg.esphome_ns.namespace("xpt2046") -CONF_XPT2046_ID = "xpt2046_id" - -XPT2046Component = xpt2046_ns.class_( - "XPT2046Component", cg.PollingComponent, spi.SPIDevice +CONFIG_SCHEMA = cv.invalid( + "This component sould now be used as platform of the Touchscreen component." ) - -XPT2046OnStateTrigger = xpt2046_ns.class_( - "XPT2046OnStateTrigger", automation.Trigger.template(cg.int_, cg.int_, cg.bool_) -) - - -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 = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(XPT2046Component), - cv.Optional(CONF_IRQ_PIN): pins.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_DIMENSION_X, default=100): cv.positive_not_null_int, - cv.Optional(CONF_DIMENSION_Y, default=100): cv.positive_not_null_int, - 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, - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - XPT2046OnStateTrigger - ), - } - ), - } - ) - .extend(cv.polling_component_schema("50ms")) - .extend(spi.spi_device_schema()), - 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) - - cg.add(var.set_threshold(config[CONF_THRESHOLD])) - cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL])) - cg.add(var.set_dimensions(config[CONF_DIMENSION_X], config[CONF_DIMENSION_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_SWAP_X_Y in config: - cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y])) - - if CONF_IRQ_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) - cg.add(var.set_irq_pin(pin)) - - for conf in config.get(CONF_ON_STATE, []): - await automation.build_automation( - var.get_on_state_trigger(), - [(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")], - conf, - ) diff --git a/esphome/components/xpt2046/binary_sensor.py b/esphome/components/xpt2046/binary_sensor.py index 6ec09a2295..5a6cfe4919 100644 --- a/esphome/components/xpt2046/binary_sensor.py +++ b/esphome/components/xpt2046/binary_sensor.py @@ -1,55 +1,3 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor -from . import ( - xpt2046_ns, - XPT2046Component, - CONF_XPT2046_ID, -) - -CONF_X_MIN = "x_min" -CONF_X_MAX = "x_max" -CONF_Y_MIN = "y_min" -CONF_Y_MAX = "y_max" - -DEPENDENCIES = ["xpt2046"] -XPT2046Button = xpt2046_ns.class_("XPT2046Button", binary_sensor.BinarySensor) - - -def validate_xpt2046_button(config): - if cv.int_(config[CONF_X_MAX]) < cv.int_(config[CONF_X_MIN]) or cv.int_( - config[CONF_Y_MAX] - ) < cv.int_(config[CONF_Y_MIN]): - raise cv.Invalid("x_max is less than x_min or y_max is less than y_min") - - return config - - -CONFIG_SCHEMA = cv.All( - binary_sensor.binary_sensor_schema(XPT2046Button).extend( - { - cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component), - cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095), - cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095), - cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095), - cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095), - } - ), - validate_xpt2046_button, -) - - -async def to_code(config): - var = await binary_sensor.new_binary_sensor(config) - hub = await cg.get_variable(config[CONF_XPT2046_ID]) - cg.add( - var.set_area( - config[CONF_X_MIN], - config[CONF_X_MAX], - config[CONF_Y_MIN], - config[CONF_Y_MAX], - ) - ) - - cg.add(hub.register_button(var)) +CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.") diff --git a/esphome/components/xpt2046/touchscreen.py b/esphome/components/xpt2046/touchscreen.py new file mode 100644 index 0000000000..868525ccb1 --- /dev/null +++ b/esphome/components/xpt2046/touchscreen.py @@ -0,0 +1,116 @@ +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 + +CODEOWNERS = ["@numo68", "@nielsnl68"] +DEPENDENCIES = ["spi"] + +XPT2046_ns = cg.esphome_ns.namespace("xpt2046") +XPT2046Component = XPT2046_ns.class_( + "XPT2046Component", touchscreen.Touchscreen, cg.PollingComponent, spi.SPIDevice +) + +CONF_INTERRUPT_PIN = "interrupt_pin" + +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/xpt2046.cpp b/esphome/components/xpt2046/xpt2046.cpp index aaadeea52e..d6cbe39aa0 100644 --- a/esphome/components/xpt2046/xpt2046.cpp +++ b/esphome/components/xpt2046/xpt2046.cpp @@ -9,31 +9,38 @@ 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->store_.pin = this->irq_pin_->to_isr(); + 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) { - // Force immediate update if a falling edge (= touched is seen) Ignore if still active - // (that would mean that we missed the release because of a too long update interval) - bool val = this->irq_pin_->digital_read(); - if (!val && this->last_irq_ && !this->touched) { - ESP_LOGD(TAG, "Falling penirq edge, forcing update"); - update(); - } - this->last_irq_ = val; - } + if ((this->irq_pin_ == nullptr) || (!this->store_.touch)) + return; + 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(); @@ -42,13 +49,13 @@ void XPT2046Component::update() { // In case the penirq pin is present only do the SPI transaction if it reports a touch (is low). // The touch has to be also confirmed with checking the pressure over threshold - if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) { + if ((this->irq_pin_ == nullptr) || !this->irq_pin_->digital_read()) { enable(); - int16_t z1 = read_adc_(0xB1 /* Z1 */); - int16_t z2 = read_adc_(0xC1 /* Z2 */); + 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 = z1 + 4095 - z2; + this->z_raw = touch_pressure_1 + 4095 - touch_pressure_2; touch = (this->z_raw >= this->threshold_); if (touch) { @@ -63,64 +70,73 @@ void XPT2046Component::update() { data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down disable(); - } - if (touch) { - this->x_raw = best_two_avg(data[0], data[2], data[4]); - this->y_raw = best_two_avg(data[1], data[3], data[5]); - } else { - this->x_raw = this->y_raw = 0; - } + if (touch) { + this->x_raw = best_two_avg(data[0], data[2], data[4]); + this->y_raw = best_two_avg(data[1], data[3], data[5]); - ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : "")); + ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw); - if (touch) { - // Normalize raw data according to calibration min and max + TouchPoint touchpoint; - int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_); - int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_); + 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(x_val, y_val); - } + if (this->swap_x_y_) { + std::swap(touchpoint.x, touchpoint.y); + } - if (this->invert_x_) { - x_val = 0x7fff - x_val; - } + if (this->invert_x_) { + touchpoint.x = 0xfff - touchpoint.x; + } - if (this->invert_y_) { - y_val = 0x7fff - y_val; - } + if (this->invert_y_) { + touchpoint.y = 0xfff - touchpoint.y; + } - x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff); - y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff); + 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; + } - if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) { - ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val); + touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff); + touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff); - this->x = x_val; - this->y = y_val; - this->touched = true; - this->last_pos_ms_ = now; + 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->on_state_trigger_->process(this->x, this->y, true); - for (auto *button : this->buttons_) - button->touch(this->x, this->y); - } - } else { - if (this->touched) { - ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y); + this->defer([this, touchpoint]() { this->send_touch_(touchpoint); }); - this->touched = false; - - this->on_state_trigger_->process(this->x, this->y, false); - for (auto *button : this->buttons_) - button->release(); + this->x = touchpoint.x; + this->y = touchpoint.y; + this->touched = true; + this->last_pos_ms_ = now; + } + } else { + this->x_raw = this->y_raw = 0; + if (this->touched) { + 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) { +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); @@ -137,11 +153,11 @@ void XPT2046Component::dump_config() { 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, " X dim: %d", this->x_dim_); - ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_); - if (this->swap_x_y_) { - ESP_LOGCONFIG(TAG, " Swap X/Y"); - } + + 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_); @@ -150,8 +166,8 @@ void XPT2046Component::dump_config() { 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) { - int16_t da, db, dc; +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; @@ -175,15 +191,15 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va if (val <= min_val) { ret = 0; } else if (val >= max_val) { - ret = 0x7fff; + ret = 0xfff; } else { - ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val)); + ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val)); } return ret; } -int16_t XPT2046Component::read_adc_(uint8_t ctrl) { +int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT uint8_t data[2]; write_byte(ctrl); @@ -193,25 +209,5 @@ int16_t XPT2046Component::read_adc_(uint8_t ctrl) { return ((data[0] << 8) | data[1]) >> 3; } -void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); } - -void XPT2046Button::touch(int16_t x, int16_t y) { - bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_); - - if (touched) { - this->publish_state(true); - this->state_ = true; - } else { - release(); - } -} - -void XPT2046Button::release() { - if (this->state_) { - this->publish_state(false); - this->state_ = false; - } -} - } // namespace xpt2046 } // namespace esphome diff --git a/esphome/components/xpt2046/xpt2046.h b/esphome/components/xpt2046/xpt2046.h index e7270f7d7d..c3d0462c6a 100644 --- a/esphome/components/xpt2046/xpt2046.h +++ b/esphome/components/xpt2046/xpt2046.h @@ -3,42 +3,31 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/spi/spi.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace xpt2046 { -class XPT2046OnStateTrigger : public Trigger { - public: - void process(int x, int y, bool touched); +using namespace touchscreen; + +struct XPT2046TouchscreenStore { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(XPT2046TouchscreenStore *store); }; -class XPT2046Button : public binary_sensor::BinarySensor { - public: - /// Set the touch screen area where the button will detect the touch. - void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { - this->x_min_ = x_min; - this->x_max_ = x_max; - this->y_min_ = y_min; - this->y_max_ = y_max; - } - - void touch(int16_t x, int16_t y); - void release(); - - protected: - int16_t x_min_, x_max_, y_min_, y_max_; - bool state_{false}; -}; - -class XPT2046Component : public PollingComponent, +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->x_dim_ = x; - this->y_dim_ = 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); @@ -47,14 +36,12 @@ class XPT2046Component : public PollingComponent, /// 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(GPIOPin *pin) { this->irq_pin_ = pin; } - /// Get an access to the on_state automation trigger - XPT2046OnStateTrigger *get_on_state_trigger() const { return this->on_state_trigger_; } - /// Register a virtual button to the component. - void register_button(XPT2046Button *button) { this->buttons_.push_back(button); } + void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; } void setup() override; void dump_config() override; @@ -103,21 +90,19 @@ class XPT2046Component : public PollingComponent, 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_; - int16_t x_dim_, y_dim_; + bool invert_x_, invert_y_; bool swap_x_y_; uint32_t report_millis_; uint32_t last_pos_ms_{0}; - GPIOPin *irq_pin_{nullptr}; - bool last_irq_{true}; - - XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()}; - std::vector buttons_{}; + InternalGPIOPin *irq_pin_{nullptr}; + XPT2046TouchscreenStore store_; }; } // namespace xpt2046 diff --git a/tests/test4.yaml b/tests/test4.yaml index 6293e0f7b7..cf517bb391 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -348,15 +348,16 @@ binary_sensor: on_state: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' - - platform: xpt2046 - xpt2046_id: xpt_touchscreen + - platform: touchscreen + touchscreen_id: xpt_touchscreen id: touch_key0 x_min: 80 x_max: 160 y_min: 106 y_max: 212 - on_state: - - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' + on_press: + - logger.log: Touched + - platform: gpio name: GPIO SX1509 test pin: @@ -598,33 +599,6 @@ external_components: components: [bh1750] - source: ../esphome/components components: [sntp] -xpt2046: - id: xpt_touchscreen - cs_pin: 17 - irq_pin: 16 - update_interval: 50ms - report_interval: 1s - threshold: 400 - dimension_x: 240 - dimension_y: 320 - calibration_x_min: 3860 - calibration_x_max: 280 - calibration_y_min: 340 - calibration_y_max: 3860 - swap_x_y: false - on_state: - # yamllint disable rule:line-length - - lambda: |- - ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); - ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", - id(xpt_touchscreen).x, - id(xpt_touchscreen).y, - (int) id(xpt_touchscreen).touched, - id(xpt_touchscreen).x_raw, - id(xpt_touchscreen).y_raw, - id(xpt_touchscreen).z_raw - ); - # yamllint enable rule:line-length button: - platform: restart @@ -648,6 +622,25 @@ touchscreen: format: Touch at (%d, %d) args: [touch.x, touch.y] + - platform: xpt2046 + id: xpt_touchscreen + cs_pin: 17 + interrupt_pin: 16 + 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 + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: [touch.x, touch.y] + + - platform: lilygo_t5_47 id: lilygo_touchscreen interrupt_pin: GPIO36 From edff9ae3222733822bb0de78532038d4e53c960a Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Tue, 11 Oct 2022 01:01:31 +0200 Subject: [PATCH 126/838] Proxy friendly host url resolution for `use_address` with path. (#3653) --- esphome/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/helpers.py b/esphome/helpers.py index 85a767036a..b5a6306342 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -6,6 +6,7 @@ import os from pathlib import Path from typing import Union import tempfile +from urllib.parse import urlparse _LOGGER = logging.getLogger(__name__) @@ -134,7 +135,8 @@ def resolve_ip_address(host): errs.append(str(err)) try: - return socket.gethostbyname(host) + host_url = host if (urlparse(host).scheme != "") else "http://" + host + return socket.gethostbyname(urlparse(host_url).hostname) except OSError as err: errs.append(str(err)) raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err From de23bbace21bcf88cf645dff3afb7735c13c0d3d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 11 Oct 2022 12:01:41 +1300 Subject: [PATCH 127/838] Update webserver index file (#3896) --- esphome/components/web_server/server_index.h | 116 ++++++++++--------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 75c7130151..c02ef279b7 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,7 +6,7 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3, 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x25, 0x10, 0x22, 0x29, 0xcb, 0x76, 0x81, 0x02, 0x79, 0xe5, 0xa5, 0xae, 0x5d, 0xe5, 0xad, 0x2c, 0xd9, 0x75, 0xab, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02, 0x92, 0x5a, 0x8a, 0x42, 0x9f, 0x7e, 0xea, 0xa7, 0x39, 0x67, 0xd6, 0x87, 0x7e, 0x99, 0xd3, 0xfd, 0x30, 0x1f, 0x31, @@ -524,62 +524,64 @@ const uint8_t INDEX_GZ[] PROGMEM = { 0x5b, 0x06, 0xb9, 0xff, 0xfc, 0x07, 0x47, 0x40, 0x7e, 0x68, 0x06, 0xb9, 0xf7, 0xd8, 0x4a, 0xa0, 0xa3, 0xe9, 0xac, 0x3d, 0x67, 0xce, 0xe1, 0x8f, 0x6c, 0x88, 0x69, 0xd3, 0xd0, 0xfa, 0x2f, 0xb5, 0x78, 0x50, 0x86, 0x1b, 0x79, 0x8f, 0x89, 0x9d, 0xcc, 0xf8, 0x15, 0x04, 0xe1, 0xfc, 0x46, 0x82, 0xbc, 0xb8, 0x1d, 0x3d, 0x38, 0xff, 0x63, 0xe9, 0x41, - 0x5f, 0xee, 0x57, 0xf4, 0xa8, 0x45, 0xdc, 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0xf6, 0x16, 0xc3, 0xb7, - 0xc2, 0x16, 0xb9, 0x80, 0xef, 0x3e, 0xe7, 0x74, 0x40, 0x64, 0x15, 0x87, 0xb6, 0x0c, 0xc0, 0xcc, 0xf1, 0xdb, 0xb4, - 0xea, 0xe5, 0x54, 0xdc, 0x5c, 0x09, 0xe9, 0xda, 0xd9, 0x8e, 0x0e, 0xde, 0x60, 0xa2, 0x77, 0xb8, 0xcc, 0x78, 0x84, - 0xbf, 0xfc, 0x88, 0xc7, 0x3c, 0xc1, 0x4b, 0xb1, 0xf2, 0x02, 0x19, 0xe6, 0x25, 0x7f, 0x87, 0x39, 0xd5, 0xea, 0x90, - 0x60, 0x86, 0x01, 0x83, 0x57, 0x6c, 0x1c, 0x47, 0x8e, 0xed, 0xcc, 0x61, 0xc7, 0xc2, 0x98, 0xad, 0x5a, 0x42, 0x33, - 0xe5, 0x32, 0xbb, 0xb6, 0xfa, 0x7d, 0x3b, 0x39, 0x7e, 0xbf, 0x2c, 0x3c, 0x94, 0x01, 0x46, 0x5b, 0x7a, 0x00, 0x30, - 0xbe, 0x2a, 0xc9, 0x51, 0xd8, 0x57, 0x56, 0x83, 0x2d, 0xcc, 0x86, 0x8e, 0xdf, 0x05, 0x37, 0x82, 0x8a, 0xf1, 0x7b, - 0x50, 0x3f, 0x38, 0xad, 0x6d, 0x30, 0x6b, 0x8c, 0x6e, 0x7a, 0xa0, 0xe1, 0x4a, 0x18, 0x49, 0x04, 0x07, 0x1a, 0xa5, - 0x9e, 0xfe, 0x05, 0x64, 0x55, 0xb8, 0xa8, 0x78, 0x7c, 0x71, 0x20, 0xef, 0x7c, 0xdb, 0x18, 0xb9, 0xa5, 0x88, 0x7d, - 0xf5, 0xbd, 0xa9, 0x4d, 0x50, 0x17, 0xf4, 0x5b, 0x20, 0xe9, 0xdc, 0x1b, 0x35, 0x02, 0xa6, 0x5c, 0x5b, 0xd2, 0x73, - 0x08, 0x6d, 0xa1, 0x0f, 0xc6, 0xec, 0x34, 0x1e, 0x49, 0xb1, 0xee, 0x59, 0xf2, 0xaa, 0x48, 0x8b, 0xb0, 0x08, 0x3b, - 0x9e, 0xf0, 0x9d, 0xe1, 0x05, 0xb5, 0x5a, 0x98, 0x66, 0x76, 0xff, 0x5e, 0x4f, 0x43, 0x52, 0xcf, 0x56, 0xb7, 0xf1, - 0x57, 0x52, 0x1e, 0x82, 0xaf, 0xf6, 0xf7, 0xe1, 0x3d, 0xfc, 0xa5, 0x94, 0xf7, 0x86, 0xb6, 0xeb, 0x93, 0x50, 0xbc, - 0x57, 0xfd, 0x66, 0x4a, 0x94, 0x08, 0x9b, 0xa0, 0xbf, 0xbc, 0xdb, 0x2a, 0x32, 0xa9, 0xb4, 0xba, 0x3b, 0x95, 0xd2, - 0x82, 0x67, 0x43, 0x4a, 0x81, 0x00, 0xed, 0xfa, 0x3b, 0x86, 0x28, 0x3c, 0x6d, 0xe1, 0xcf, 0x9a, 0x30, 0xbc, 0x0f, - 0x0d, 0x94, 0x34, 0x7c, 0x09, 0xcd, 0xb7, 0x85, 0xe0, 0x85, 0x7e, 0x3f, 0x92, 0xa8, 0x12, 0x62, 0xaa, 0xce, 0x31, - 0x6b, 0x0e, 0x91, 0x44, 0x8e, 0x80, 0xed, 0x19, 0xf1, 0x26, 0xc1, 0xae, 0x32, 0x9a, 0xf2, 0x14, 0xfa, 0x3a, 0xfa, - 0x33, 0xce, 0xeb, 0xea, 0xbc, 0xda, 0xce, 0x59, 0x33, 0x05, 0x32, 0x7c, 0xe3, 0xa0, 0x8a, 0xae, 0x2e, 0x88, 0xcf, - 0x99, 0x89, 0x6d, 0x5c, 0x7d, 0xf0, 0x6d, 0x4d, 0xf6, 0xad, 0xb9, 0x29, 0x58, 0xc5, 0x34, 0xb4, 0x2f, 0x30, 0x65, - 0x06, 0x7f, 0x56, 0xc5, 0xea, 0x41, 0x32, 0x94, 0x9f, 0x44, 0xf8, 0xdb, 0x58, 0xe8, 0x47, 0x59, 0x6d, 0x40, 0x4e, - 0xdf, 0xab, 0x24, 0x48, 0x5f, 0x8c, 0xcb, 0x26, 0x12, 0x60, 0x2f, 0xe0, 0x2f, 0xf7, 0xab, 0xae, 0x4a, 0xc8, 0x3b, - 0x90, 0x98, 0x53, 0x30, 0x8e, 0x73, 0xba, 0x5a, 0xab, 0xf0, 0xaf, 0x45, 0x34, 0x2b, 0x52, 0xd3, 0xae, 0x64, 0xc5, - 0xc0, 0xc6, 0x22, 0x3b, 0x90, 0xc9, 0x68, 0xe6, 0x07, 0x9b, 0xcd, 0xbb, 0x8f, 0x63, 0x91, 0x87, 0x86, 0x1f, 0xb4, - 0xb7, 0x05, 0x91, 0x6d, 0x10, 0x63, 0x57, 0xe2, 0x44, 0xc6, 0x0d, 0x5e, 0x19, 0xac, 0x7e, 0x43, 0x91, 0xb9, 0xe1, - 0x6d, 0x73, 0xb5, 0xf4, 0xb8, 0xb4, 0x0e, 0xae, 0x8c, 0xdf, 0x1d, 0xb3, 0x88, 0xfb, 0x51, 0x4a, 0xb9, 0x49, 0x8e, - 0x21, 0x16, 0xbc, 0x0e, 0xdb, 0x76, 0x4b, 0x90, 0x3c, 0xc6, 0xaf, 0x70, 0x12, 0xa4, 0xf7, 0xa1, 0xb0, 0x4a, 0xd8, - 0xda, 0x9d, 0x76, 0xfb, 0x6f, 0x0e, 0xf6, 0x2c, 0xb1, 0x9b, 0x77, 0xb7, 0xe0, 0x75, 0x97, 0xdc, 0x61, 0x91, 0x9f, - 0x11, 0x8a, 0xfc, 0x0c, 0x4b, 0x24, 0x74, 0x85, 0xf6, 0x96, 0x40, 0xd3, 0xb6, 0x58, 0x3a, 0x12, 0x31, 0xbc, 0x19, - 0xb8, 0x0b, 0x31, 0x7e, 0xd4, 0x6b, 0x0b, 0xbb, 0xb5, 0x70, 0xa5, 0x6d, 0x95, 0xe1, 0xa2, 0x0c, 0x04, 0x9e, 0xaa, - 0x88, 0x1f, 0xa8, 0x75, 0xa6, 0x92, 0x5d, 0xe4, 0x50, 0x3a, 0x27, 0x75, 0xb5, 0x75, 0xb1, 0x38, 0x9e, 0x81, 0x1c, - 0x52, 0x09, 0x2a, 0xef, 0x65, 0x87, 0x5d, 0x9a, 0x0a, 0x93, 0x62, 0x57, 0x23, 0x92, 0xd3, 0x4e, 0x7f, 0x37, 0x92, - 0xf6, 0x0e, 0xee, 0xdd, 0x02, 0x36, 0x2f, 0xa8, 0x39, 0x34, 0x2a, 0xfc, 0x38, 0xdb, 0x3a, 0x63, 0xc7, 0xad, 0x68, - 0x1e, 0x57, 0xe1, 0x3f, 0xd4, 0x7e, 0xfd, 0x5d, 0xa5, 0x08, 0x65, 0x9a, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24, - 0x1c, 0x31, 0x68, 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0, - 0xb4, 0xfb, 0x7f, 0xfd, 0x5f, 0xff, 0x5b, 0x86, 0x15, 0x81, 0xac, 0x00, 0x16, 0xa6, 0xc1, 0x54, 0x27, 0x8c, 0xec, - 0x1c, 0x1c, 0xd1, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, 0x20, 0x28, 0x98, 0xb8, 0xca, 0x24, 0xeb, 0x81, 0x0b, 0x24, - 0x58, 0xe6, 0xe1, 0xbc, 0x04, 0xaf, 0x5e, 0x84, 0x2b, 0xf6, 0xbb, 0xf2, 0x56, 0x55, 0xbe, 0x30, 0x31, 0xb4, 0x91, - 0xc5, 0x6a, 0xf0, 0x5c, 0x2d, 0x93, 0x55, 0xfd, 0x82, 0x24, 0x29, 0x3c, 0x58, 0x2d, 0x8d, 0x15, 0x5a, 0xea, 0x83, - 0x90, 0x7f, 0xfb, 0xe7, 0xff, 0xfc, 0xdf, 0xd5, 0x2b, 0x9e, 0x6f, 0xfc, 0xf5, 0x9f, 0xfe, 0xe1, 0xff, 0xfe, 0x9f, - 0xff, 0x82, 0x59, 0xc2, 0xf2, 0x0c, 0x84, 0xb6, 0x92, 0x55, 0x1d, 0x80, 0x88, 0x3d, 0x65, 0x55, 0x0e, 0x47, 0x3d, - 0xdd, 0x75, 0x9f, 0x26, 0x24, 0xde, 0x94, 0xd0, 0x11, 0x5f, 0x53, 0x7a, 0x34, 0x51, 0xed, 0x1a, 0xf2, 0xc1, 0x52, - 0x5a, 0x74, 0xac, 0x6f, 0xef, 0xb4, 0xed, 0x6a, 0x79, 0xfb, 0x46, 0xdf, 0x2d, 0x5c, 0x98, 0x5b, 0x65, 0xe0, 0xf8, - 0x7a, 0xd9, 0x96, 0x2a, 0x8c, 0x85, 0x25, 0x65, 0x55, 0x6e, 0x61, 0x7c, 0x79, 0x89, 0xaf, 0x41, 0xd7, 0x28, 0xa6, - 0x55, 0xae, 0xf5, 0xe9, 0xfd, 0xb2, 0x00, 0x44, 0x27, 0xb8, 0x34, 0x22, 0x58, 0x46, 0x67, 0xa7, 0x2d, 0xb4, 0x4e, - 0x92, 0x8b, 0x92, 0x46, 0x11, 0xde, 0xcc, 0xfd, 0x47, 0x7f, 0x57, 0xfe, 0x69, 0x86, 0x56, 0x81, 0xe5, 0xcc, 0xa2, - 0x73, 0xe9, 0xe3, 0x3c, 0x68, 0xb7, 0xe7, 0xe7, 0xee, 0xb2, 0x9a, 0xc1, 0xbb, 0x6a, 0x32, 0x0a, 0xb0, 0x99, 0x03, - 0xd2, 0xa1, 0xab, 0x8e, 0xe5, 0x81, 0x59, 0xdf, 0xc6, 0xd0, 0x4f, 0x59, 0x7e, 0xb9, 0xa4, 0x70, 0x52, 0xfc, 0x1b, - 0x1e, 0x8e, 0xca, 0xc8, 0x1b, 0x94, 0x18, 0x58, 0x2c, 0x8d, 0x5e, 0x5d, 0xd1, 0x6b, 0xda, 0x59, 0xcd, 0x4d, 0x31, - 0x0f, 0x77, 0xcd, 0x63, 0xd9, 0xfb, 0x78, 0xd0, 0x3a, 0xed, 0x78, 0xd3, 0xee, 0x52, 0x0f, 0xcf, 0x79, 0x36, 0x33, - 0x4f, 0x73, 0x59, 0xc4, 0x46, 0x6c, 0xa2, 0x22, 0x96, 0xb2, 0x5e, 0x9c, 0xd4, 0x96, 0x5f, 0xe0, 0x76, 0x03, 0xda, - 0x66, 0x11, 0x0f, 0x88, 0x69, 0x7b, 0xe6, 0x79, 0x6f, 0x84, 0x27, 0xe9, 0xd9, 0xd2, 0x98, 0xab, 0x27, 0x9a, 0x62, - 0x5c, 0xb0, 0x9e, 0xf7, 0x53, 0xfa, 0xd4, 0xdd, 0x1c, 0x4a, 0x84, 0x15, 0x5e, 0xc8, 0x63, 0xd4, 0x77, 0x35, 0x7f, - 0x5c, 0x8a, 0x62, 0x70, 0x81, 0xd7, 0xd6, 0x0b, 0xb5, 0x28, 0x6a, 0x5f, 0x80, 0xb5, 0x43, 0x60, 0xda, 0xcd, 0x56, - 0x54, 0x88, 0xad, 0xde, 0x85, 0x2f, 0xb4, 0xed, 0x1d, 0xcd, 0xe7, 0xd4, 0xd0, 0x05, 0x6e, 0x24, 0x1b, 0x1a, 0x25, - 0x05, 0xa5, 0x08, 0x88, 0x13, 0x79, 0xd9, 0x46, 0xb2, 0xad, 0x78, 0x92, 0x67, 0xf5, 0xf4, 0xfb, 0xb6, 0xff, 0x1f, - 0x22, 0x28, 0x4d, 0x5d, 0x85, 0x7b, 0x00, 0x00}; + 0x5f, 0xee, 0x57, 0xf4, 0xd0, 0x49, 0xe1, 0x85, 0xf1, 0xfb, 0x57, 0x16, 0x79, 0xa2, 0xaf, 0xa8, 0x65, 0x23, 0xca, + 0xf4, 0xf4, 0x91, 0x97, 0xe8, 0x9e, 0x20, 0x54, 0x6e, 0x86, 0xf0, 0x9f, 0xf1, 0x09, 0xb0, 0x2d, 0xfc, 0xd4, 0x9c, + 0x1d, 0xc0, 0x4f, 0xaa, 0xb5, 0x19, 0xc6, 0x0a, 0x0a, 0xbb, 0xac, 0x85, 0xf3, 0x29, 0x1c, 0x41, 0x51, 0x84, 0x7d, + 0x7a, 0x77, 0x70, 0x46, 0x91, 0x63, 0xf8, 0xee, 0x73, 0x4e, 0x1d, 0x44, 0xb6, 0x72, 0x68, 0xcb, 0xc0, 0xce, 0x1c, + 0xbf, 0x79, 0xab, 0x5e, 0x4e, 0xc5, 0x8d, 0x98, 0x90, 0xae, 0xb3, 0xed, 0xe8, 0xa0, 0x10, 0x26, 0x90, 0x87, 0xcb, + 0x8c, 0x47, 0xf8, 0x4b, 0x95, 0x78, 0xcc, 0x13, 0xbc, 0x6c, 0x2b, 0x2f, 0xa6, 0x61, 0xbe, 0xf3, 0x77, 0x98, 0xab, + 0xad, 0x30, 0x9e, 0x61, 0x20, 0xe2, 0x15, 0x1b, 0xc7, 0x91, 0x63, 0x3b, 0x73, 0x90, 0x04, 0x30, 0x66, 0xab, 0x96, + 0x28, 0x4d, 0x39, 0xd2, 0xae, 0xad, 0x7e, 0x8f, 0x4f, 0x8e, 0xdf, 0x45, 0x0b, 0x0f, 0x65, 0xe0, 0xd2, 0x96, 0x9e, + 0x05, 0x8c, 0xaf, 0x4a, 0x72, 0x54, 0x22, 0x95, 0x35, 0x62, 0x0b, 0x73, 0xa4, 0xe3, 0x77, 0xc1, 0x3d, 0xa1, 0x62, + 0xfc, 0xce, 0xd4, 0x0f, 0x4e, 0x6b, 0x1b, 0xcc, 0x25, 0xa3, 0x9b, 0x1e, 0x68, 0xb8, 0x12, 0x9e, 0x12, 0x41, 0x87, + 0x46, 0xa9, 0xa7, 0x7f, 0xb1, 0x59, 0x15, 0x86, 0x2a, 0x1e, 0x5f, 0x1c, 0xc8, 0xbb, 0xe4, 0x36, 0x46, 0x84, 0xe9, + 0x24, 0xa0, 0xfa, 0x8e, 0xd5, 0x26, 0xa8, 0x21, 0xfa, 0xed, 0x92, 0x74, 0x9e, 0x8e, 0x9a, 0x06, 0x53, 0xb9, 0x2d, + 0xe9, 0x91, 0x84, 0xb6, 0xd0, 0x33, 0x63, 0x76, 0x1a, 0x8f, 0xa4, 0xba, 0xf0, 0x2c, 0x79, 0x05, 0xa5, 0x45, 0x58, + 0x84, 0x1d, 0x4f, 0xf8, 0xe4, 0xf0, 0x82, 0xda, 0x32, 0x4c, 0x33, 0xbb, 0x7f, 0xaf, 0xa7, 0x21, 0xa9, 0x67, 0xc1, + 0xdb, 0xf8, 0xab, 0x2e, 0x0f, 0xc1, 0x07, 0xfc, 0xfb, 0xf0, 0x1e, 0xfe, 0xb2, 0xcb, 0x7b, 0x43, 0xdb, 0xf5, 0x49, + 0xd8, 0xde, 0xab, 0x7e, 0xe3, 0x25, 0x4a, 0x9a, 0x4d, 0xd0, 0x8b, 0xde, 0x6d, 0x15, 0xa4, 0x54, 0x86, 0xdd, 0x9d, + 0x4a, 0x19, 0xc2, 0xb3, 0x21, 0xfd, 0x40, 0x30, 0x77, 0xfd, 0x1d, 0x43, 0xc4, 0x9e, 0xb6, 0xf0, 0x67, 0x4d, 0xc8, + 0xde, 0x87, 0x06, 0x4a, 0xca, 0xbe, 0x84, 0xe6, 0xdb, 0x42, 0xa0, 0x43, 0xbf, 0x1f, 0x49, 0x04, 0x0a, 0xf1, 0x57, + 0xe7, 0x98, 0x35, 0x87, 0x53, 0x22, 0xf7, 0xc0, 0xf6, 0x8c, 0x38, 0x96, 0x60, 0x57, 0x19, 0xa5, 0x79, 0x0a, 0x7d, + 0x1d, 0xfd, 0x79, 0xe8, 0x75, 0x75, 0x5e, 0x6d, 0xd3, 0xac, 0x99, 0x02, 0x19, 0xbe, 0x71, 0x00, 0x46, 0x57, 0x22, + 0xc4, 0x67, 0xd2, 0x84, 0x78, 0xa8, 0x3e, 0x24, 0xb7, 0x26, 0xab, 0xd7, 0xdc, 0x14, 0xac, 0x62, 0x1a, 0xda, 0x17, + 0x98, 0x8a, 0x83, 0x3f, 0xab, 0x62, 0xf5, 0x20, 0x19, 0xca, 0x4f, 0x22, 0xfc, 0x2d, 0x2f, 0xf4, 0xa3, 0xac, 0x36, + 0x20, 0xa7, 0xef, 0x60, 0x12, 0xa4, 0x2f, 0xc6, 0x65, 0x13, 0x09, 0xb0, 0x43, 0xf0, 0x97, 0x06, 0x56, 0x57, 0x30, + 0xe4, 0xdd, 0x4a, 0xcc, 0x55, 0x18, 0xc7, 0x39, 0x5d, 0xd9, 0x55, 0xf8, 0xd7, 0x22, 0xa5, 0x15, 0xa9, 0x69, 0x57, + 0xb2, 0x62, 0x60, 0x63, 0x11, 0x88, 0x1a, 0x91, 0xe4, 0x66, 0x7e, 0x08, 0xda, 0xbc, 0x53, 0x39, 0x16, 0xf9, 0x6d, + 0xf8, 0xa1, 0x7c, 0x5b, 0x10, 0xd9, 0x06, 0xf1, 0x78, 0x25, 0x4e, 0x64, 0x34, 0xe1, 0x55, 0xc4, 0xea, 0x37, 0x1f, + 0x99, 0x1b, 0xde, 0x36, 0x57, 0x4b, 0x8f, 0x4b, 0xeb, 0xe0, 0xca, 0xb8, 0xe0, 0x31, 0x8b, 0xb8, 0x1f, 0xa5, 0x94, + 0xf3, 0xe4, 0x18, 0x62, 0xc1, 0xeb, 0xb0, 0x6d, 0xb7, 0x04, 0xc9, 0x63, 0xfc, 0x6a, 0x28, 0x41, 0x7a, 0x1f, 0x0a, + 0xab, 0x44, 0xb0, 0xdd, 0x69, 0xb7, 0xff, 0xe6, 0x60, 0xcf, 0x12, 0xbb, 0x79, 0x77, 0x0b, 0x5e, 0x77, 0xc9, 0xcd, + 0x16, 0x79, 0x1f, 0xa1, 0xc8, 0xfb, 0xb0, 0x44, 0xa2, 0x58, 0x68, 0x6f, 0x09, 0x34, 0x6d, 0x8b, 0xa5, 0x23, 0x11, + 0x1b, 0x9c, 0x81, 0x1b, 0x12, 0xe3, 0xc7, 0xc2, 0xb6, 0xb0, 0x5b, 0x0b, 0x57, 0xda, 0x56, 0x99, 0x33, 0xca, 0xf0, + 0xe0, 0xa9, 0x8a, 0x24, 0x82, 0xb9, 0xc0, 0x54, 0x12, 0x8d, 0x1c, 0x4a, 0xe7, 0xba, 0xae, 0xb6, 0x2e, 0x16, 0xc7, + 0x33, 0x90, 0x43, 0x2a, 0xf1, 0xe5, 0xbd, 0xec, 0xb0, 0x4b, 0x53, 0x61, 0xb2, 0xed, 0x6a, 0xa4, 0x73, 0xda, 0xe9, + 0xef, 0x46, 0xd2, 0x8e, 0xc2, 0xbd, 0x5b, 0xc0, 0xe6, 0x05, 0xf5, 0x89, 0xc6, 0x8a, 0x1f, 0x67, 0x5b, 0x67, 0xec, + 0xb8, 0x15, 0xcd, 0xe3, 0x2a, 0xac, 0x88, 0x5a, 0xb5, 0xbf, 0xab, 0x14, 0xac, 0x4c, 0xdf, 0x94, 0x8f, 0x91, 0x91, + 0x1d, 0x82, 0x84, 0x23, 0x06, 0x2d, 0x65, 0xcc, 0x92, 0x8c, 0x51, 0x20, 0x3e, 0xc0, 0x4a, 0xfc, 0xab, 0x62, 0x9b, + 0x52, 0x13, 0x94, 0x76, 0xff, 0xaf, 0xff, 0xeb, 0x7f, 0xcb, 0x70, 0x25, 0x90, 0x15, 0xc0, 0xc2, 0xf4, 0x9a, 0xea, + 0xe4, 0x92, 0x9d, 0x83, 0x83, 0x1b, 0x8f, 0x5b, 0xd3, 0x28, 0x99, 0x00, 0x04, 0x05, 0x13, 0xda, 0x50, 0xd6, 0x03, + 0x17, 0x48, 0xb0, 0xcc, 0x43, 0x7f, 0x09, 0x5e, 0xbd, 0x08, 0x57, 0xec, 0x77, 0xe5, 0xc3, 0xaa, 0x3c, 0x64, 0x62, + 0x68, 0x23, 0x3b, 0xd6, 0xe0, 0xb9, 0x5a, 0x86, 0xac, 0xfa, 0xc5, 0x4b, 0x52, 0x78, 0xb0, 0x5a, 0x7a, 0x2c, 0xb4, + 0xd4, 0x07, 0x2c, 0xff, 0xf6, 0xcf, 0xff, 0xf9, 0xbf, 0xab, 0x57, 0x3c, 0x37, 0xf9, 0xeb, 0x3f, 0xfd, 0xc3, 0xff, + 0xfd, 0x3f, 0xff, 0x05, 0xb3, 0x8f, 0xe5, 0xd9, 0x0a, 0x6d, 0x25, 0xab, 0x3a, 0x58, 0x11, 0x7b, 0xca, 0xaa, 0x1c, + 0x99, 0x7a, 0x1a, 0xed, 0x3e, 0x4d, 0x48, 0xbc, 0x29, 0xa1, 0x23, 0xbe, 0xa6, 0xb4, 0x6b, 0xa2, 0xda, 0x35, 0xe4, + 0x83, 0xa5, 0xb4, 0x28, 0x5d, 0xc0, 0xde, 0x69, 0xdb, 0xd5, 0xf2, 0xf6, 0x8d, 0xbe, 0x5b, 0xb8, 0x30, 0xb7, 0xca, + 0xec, 0xf1, 0xf5, 0xb2, 0x2d, 0x55, 0x78, 0x0c, 0x4b, 0xca, 0xaa, 0xdc, 0xc2, 0xb8, 0xf5, 0x12, 0x5f, 0x83, 0xae, + 0x51, 0x4c, 0xab, 0x5c, 0xeb, 0xd3, 0xfb, 0x65, 0x01, 0x88, 0x4e, 0x70, 0x69, 0x44, 0x10, 0x8e, 0xce, 0x64, 0x5b, + 0x68, 0xc0, 0x24, 0x17, 0x25, 0x8d, 0x22, 0xbc, 0xa4, 0xfb, 0x8f, 0xfe, 0xae, 0xfc, 0xd3, 0x0c, 0xad, 0x02, 0xcb, + 0x99, 0x45, 0xe7, 0xd2, 0x77, 0x7a, 0xd0, 0x6e, 0xcf, 0xcf, 0xdd, 0x65, 0x35, 0x83, 0x77, 0xd5, 0x64, 0x14, 0xb8, + 0x33, 0x07, 0xa4, 0xc3, 0x5c, 0x1d, 0x23, 0x04, 0x77, 0xa1, 0x8d, 0x21, 0xa5, 0xb2, 0xfc, 0x72, 0x49, 0x61, 0xaa, + 0xf8, 0x37, 0x3c, 0x74, 0x95, 0x11, 0x3d, 0x28, 0x31, 0xb0, 0x58, 0x1a, 0xbd, 0xba, 0xa2, 0xd7, 0xb4, 0xb3, 0x9a, + 0xf3, 0x62, 0x1e, 0x1a, 0x9b, 0xc7, 0xbd, 0xf7, 0xf1, 0x00, 0x77, 0xda, 0xf1, 0xa6, 0xdd, 0xa5, 0x1e, 0x9e, 0xf3, + 0x6c, 0x66, 0x9e, 0x12, 0xb3, 0x88, 0x8d, 0xd8, 0x44, 0x45, 0x42, 0x65, 0xbd, 0x38, 0x01, 0x2e, 0xbf, 0xc0, 0xed, + 0x06, 0xb4, 0xcd, 0x22, 0x1e, 0x10, 0xd3, 0xf6, 0xcc, 0x73, 0xe4, 0x08, 0x4f, 0xe8, 0xb3, 0xa5, 0x31, 0x57, 0x4f, + 0x34, 0xc5, 0x78, 0x63, 0x3d, 0x9f, 0xa8, 0xf4, 0xa9, 0xbb, 0x39, 0x94, 0x08, 0x57, 0xbc, 0x90, 0xc7, 0xb3, 0xef, + 0x6a, 0x7e, 0xbe, 0x14, 0xc5, 0xe0, 0x5a, 0xaf, 0xad, 0x17, 0x6a, 0x51, 0xd4, 0xbe, 0x00, 0x6b, 0x87, 0xc0, 0xb4, + 0x9b, 0xad, 0xa8, 0x10, 0x5b, 0xbd, 0x0b, 0x5f, 0x68, 0x9b, 0x3e, 0x9a, 0xcf, 0xa9, 0xa1, 0x0b, 0xdc, 0x48, 0xb6, + 0x39, 0x4a, 0x0a, 0x4a, 0x3d, 0x10, 0x27, 0xfd, 0xb2, 0x8d, 0x64, 0x5b, 0xf1, 0x24, 0x73, 0x00, 0xe8, 0xf7, 0x78, + 0xff, 0x3f, 0x32, 0x18, 0x26, 0x95, 0xdd, 0x7b, 0x00, 0x00}; } // namespace web_server } // namespace esphome From 44b335e7e369ff720918416655cd9b89a35f59a0 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Tue, 11 Oct 2022 01:02:53 +0200 Subject: [PATCH 128/838] Correctly set ble_write UUIDs based on their lengths. (#3885) --- esphome/components/ble_client/__init__.py | 40 ++++++++++++++++++---- esphome/components/ble_client/automation.h | 8 +++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 0f1f60e62b..44812d29b5 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -100,12 +100,40 @@ async def ble_write_to_code(config, action_id, template_arg, args): else: cg.add(var.set_value_simple(value)) - serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) - cg.add(var.set_service_uuid128(serv_uuid128)) - char_uuid128 = esp32_ble_tracker.as_reversed_hex_array( - config[CONF_CHARACTERISTIC_UUID] - ) - cg.add(var.set_char_uuid128(char_uuid128)) + if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): + cg.add( + var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) + cg.add(var.set_service_uuid128(uuid128)) + + if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_char_uuid16( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_char_uuid32( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) + cg.add(var.set_char_uuid128(uuid128)) + return var diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index ba5f78109e..98c19dedf8 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -46,10 +46,14 @@ class BLEWriterClientNode : public BLEClientNode { // Attempts to write the contents of value to char_uuid_. void write(const std::vector &value); - void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } - + void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; From 45861456f15792a5d77ffda470bae001a22a5709 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Tue, 11 Oct 2022 01:03:54 +0200 Subject: [PATCH 129/838] Fix default unit for ble_rssi sensor (#3874) --- esphome/components/ble_rssi/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index bd8e4f98e3..7c7bfc58a7 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -9,7 +9,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, - UNIT_DECIBEL, + UNIT_DECIBEL_MILLIWATT, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -31,7 +31,7 @@ def _validate(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( BLERSSISensor, - unit_of_measurement=UNIT_DECIBEL, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, From a8ff0a89134b271da6990c62ae00599510745d4c Mon Sep 17 00:00:00 2001 From: Gustavo Ambrozio Date: Mon, 10 Oct 2022 16:22:13 -0700 Subject: [PATCH 130/838] Exposing coordinates from touchscreen binary sensor (#3891) --- .../touchscreen/binary_sensor/touchscreen_binary_sensor.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index d7e53962e2..701468aa1e 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -23,6 +23,12 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor, this->y_min_ = y_min; this->y_max_ = y_max; } + int16_t get_x_min() { return this->x_min_; } + int16_t get_x_max() { return this->x_max_; } + int16_t get_y_min() { return this->y_min_; } + int16_t get_y_max() { return this->y_max_; } + int16_t get_width() { return this->x_max_ - this->x_min_; } + int16_t get_height() { return this->y_max_ - this->y_min_; } void set_page(display::DisplayPage *page) { this->page_ = page; } From 19900b004b0651ff62a94f78a0c5f977162dd95f Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 11 Oct 2022 22:15:03 -0400 Subject: [PATCH 131/838] Fix type annotation on `extract_registry_entry_config` (#3623) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/cpp_helpers.py | 7 ++++--- esphome/util.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 822197341e..0ab4b75a16 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -13,7 +13,7 @@ from esphome.const import ( # pylint: disable=unused-import from esphome.core import coroutine, ID, CORE -from esphome.types import ConfigType +from esphome.types import ConfigType, ConfigFragmentType from esphome.cpp_generator import add, get_variable from esphome.cpp_types import App from esphome.util import Registry, RegistryEntry @@ -108,8 +108,9 @@ async def setup_entity(var, config): def extract_registry_entry_config( - registry: Registry, full_config: ConfigType -) -> RegistryEntry: + registry: Registry, + full_config: ConfigType, +) -> tuple[RegistryEntry, ConfigFragmentType]: key, config = next((k, v) for k, v in full_config.items() if k in registry) return registry[key], config diff --git a/esphome/util.py b/esphome/util.py index 1779e8ccfb..0d60212f50 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -34,7 +34,7 @@ class RegistryEntry: return Schema(self.raw_schema) -class Registry(dict): +class Registry(dict[str, RegistryEntry]): def __init__(self, base_schema=None, type_id_key=None): super().__init__() self.base_schema = base_schema or {} From 9f9980e3382038ca43270110d8a6f1e5c946eb08 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Wed, 12 Oct 2022 04:23:56 +0200 Subject: [PATCH 132/838] Add ble RSSI sensor for connected devices (#3860) --- esphome/components/ble_client/ble_client.cpp | 7 ++ esphome/components/ble_client/ble_client.h | 5 +- .../components/ble_client/sensor/__init__.py | 95 ++++++++++++++----- .../ble_client/sensor/ble_rssi_sensor.cpp | 78 +++++++++++++++ .../ble_client/sensor/ble_rssi_sensor.h | 31 ++++++ tests/test1.yaml | 6 ++ 6 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 esphome/components/ble_client/sensor/ble_rssi_sensor.cpp create mode 100644 esphome/components/ble_client/sensor/ble_rssi_sensor.h diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 3f86df32f5..a757d6f903 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -64,6 +64,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } } +void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + BLEClientBase::gap_event_handler(event, param); + + for (auto *node : this->nodes_) + node->gap_event_handler(event, param); +} + void BLEClient::set_state(espbt::ClientState state) { BLEClientBase::set_state(state); for (auto &node : nodes_) diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 0f8373ab1f..177cab2e3c 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -27,7 +27,8 @@ class BLEClientNode { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; - virtual void loop(){}; + virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} + virtual void loop() {} void set_address(uint64_t address) { address_ = address; } espbt::ESPBTClient *client; // This should be transitioned to Established once the node no longer needs @@ -51,6 +52,8 @@ class BLEClient : public BLEClientBase { void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; + + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; bool parse_device(const espbt::ESPBTDevice &device) override; void set_enabled(bool enabled); diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index e8f84d2542..c9bf2995b1 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -5,7 +5,11 @@ from esphome.const import ( CONF_CHARACTERISTIC_UUID, CONF_LAMBDA, CONF_TRIGGER_ID, + CONF_TYPE, CONF_SERVICE_UUID, + DEVICE_CLASS_SIGNAL_STRENGTH, + STATE_CLASS_MEASUREMENT, + UNIT_DECIBEL_MILLIWATT, ) from esphome import automation from .. import ble_client_ns @@ -16,6 +20,8 @@ CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_NOTIFY = "notify" CONF_ON_NOTIFY = "on_notify" +TYPE_CHARACTERISTIC = "characteristic" +TYPE_RSSI = "rssi" adv_data_t = cg.std_vector.template(cg.uint8) adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") @@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_( "BLESensorNotifyTrigger", automation.Trigger.template(cg.float_) ) -CONFIG_SCHEMA = cv.All( - sensor.sensor_schema( - BLESensor, - accuracy_decimals=0, - ) - .extend( - { - cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, - cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, - cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_NOTIFY, default=False): cv.boolean, - cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BLESensorNotifyTrigger - ), - } - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(ble_client.BLE_CLIENT_SCHEMA) +BLEClientRssiSensor = ble_client_ns.class_( + "BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode ) -async def to_code(config): +def checkType(value): + if CONF_TYPE not in value and CONF_SERVICE_UUID in value: + raise cv.Invalid( + "Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config." + ) + return value + + +CONFIG_SCHEMA = cv.All( + checkType, + cv.typed_schema( + { + TYPE_CHARACTERISTIC: sensor.sensor_schema( + BLESensor, + accuracy_decimals=0, + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend( + { + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLESensorNotifyTrigger + ), + } + ), + } + ), + TYPE_RSSI: sensor.sensor_schema( + BLEClientRssiSensor, + accuracy_decimals=0, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA), + }, + lower=True, + ), +) + + +async def rssi_sensor_to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + + +async def characteristic_sensor_to_code(config): var = await sensor.new_sensor(config) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( @@ -125,3 +165,10 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await ble_client.register_ble_node(trigger, config) await automation.build_automation(trigger, [(float, "x")], conf) + + +async def to_code(config): + if config[CONF_TYPE] == TYPE_RSSI: + await rssi_sensor_to_code(config) + elif config[CONF_TYPE] == TYPE_CHARACTERISTIC: + await characteristic_sensor_to_code(config) diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp new file mode 100644 index 0000000000..13e51ed5b3 --- /dev/null +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -0,0 +1,78 @@ +#include "ble_rssi_sensor.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_rssi_sensor"; + +void BLEClientRSSISensor::loop() {} + +void BLEClientRSSISensor::dump_config() { + LOG_SENSOR("", "BLE Client RSSI Sensor", this); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str()); + LOG_UPDATE_INTERVAL(this); +} + +void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str()); + break; + } + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + this->status_set_warning(); + this->publish_state(NAN); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + this->node_state = espbt::ClientState::ESTABLISHED; + break; + default: + break; + } +} + +void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + switch (event) { + // server response on RSSI request: + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) { + int8_t rssi = param->read_rssi_cmpl.rssi; + ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi); + this->publish_state(rssi); + } + break; + default: + break; + } +} + +void BLEClientRSSISensor::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); + return; + } + + ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str()); + auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda()); + if (status != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status); + this->status_set_warning(); + this->publish_state(NAN); + } +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.h b/esphome/components/ble_client/sensor/ble_rssi_sensor.h new file mode 100644 index 0000000000..028df83832 --- /dev/null +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" + +#ifdef USE_ESP32 +#include + +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode { + public: + void loop() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; +}; + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/tests/test1.yaml b/tests/test1.yaml index e213a8b041..01672fcc01 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -321,6 +321,7 @@ mcp23s17: sensor: - platform: ble_client + type: characteristic ble_client_id: ble_foo name: Green iTag btn service_uuid: ffe0 @@ -335,6 +336,11 @@ sensor: then: - lambda: |- ESP_LOGD("green_btn", "Button was pressed, val%f", x); + - platform: ble_client + type: rssi + ble_client_id: ble_foo + name: Green iTag RSSI + update_interval: 15s - platform: adc pin: A0 name: Living Room Brightness From 03fca8d91eeeb257a66933f16bd871f36c193982 Mon Sep 17 00:00:00 2001 From: cstaahl <44496349+cstaahl@users.noreply.github.com> Date: Wed, 12 Oct 2022 04:26:35 +0200 Subject: [PATCH 133/838] Fix pulse_meter filter logic (#3321) --- .../pulse_meter/pulse_meter_sensor.cpp | 105 +++++++++++------- .../pulse_meter/pulse_meter_sensor.h | 7 +- 2 files changed, 68 insertions(+), 44 deletions(-) diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index f747f9ee40..52b7261f8b 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -11,62 +11,43 @@ void PulseMeterSensor::setup() { this->isr_pin_ = pin_->to_isr(); this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); + this->pulse_width_us_ = 0; this->last_detected_edge_us_ = 0; - this->last_valid_low_edge_us_ = 0; this->last_valid_high_edge_us_ = 0; + this->last_valid_low_edge_us_ = 0; this->sensor_is_high_ = this->isr_pin_.digital_read(); + this->has_valid_high_edge_ = false; + this->has_valid_low_edge_ = false; } void PulseMeterSensor::loop() { + // Get a local copy of the volatile sensor values, to make sure they are not + // modified by the ISR. This could cause overflow in the following arithmetic + const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_; + const bool has_valid_high_edge = this->has_valid_high_edge_; const uint32_t now = micros(); - // Check to see if we should filter this edge out - if (this->filter_mode_ == FILTER_EDGE) { - if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) { - // Don't measure the first valid pulse (we need at least two pulses to measure the width) - if (this->last_valid_high_edge_us_ != 0) { - this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); - } - this->total_pulses_++; - this->last_valid_high_edge_us_ = this->last_detected_edge_us_; - } - } else { - // Make sure the signal has been stable long enough - if ((now - this->last_detected_edge_us_) >= this->filter_us_) { - // Only consider HIGH pulses and "new" edges if sensor state is LOW - if (!this->sensor_is_high_ && this->isr_pin_.digital_read() && - (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) { - // Don't measure the first valid pulse (we need at least two pulses to measure the width) - if (this->last_valid_high_edge_us_ != 0) { - this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); - } - this->sensor_is_high_ = true; - this->total_pulses_++; - this->last_valid_high_edge_us_ = this->last_detected_edge_us_; - } - // Only consider LOW pulses and "new" edges if sensor state is HIGH - else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() && - (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) { - this->sensor_is_high_ = false; - this->last_valid_low_edge_us_ = this->last_detected_edge_us_; - } - } - } - - // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until - // we get at least two valid pulses. - const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_; - if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) && - (this->pulse_width_us_ != 0)) { + // If we've exceeded our timeout interval without receiving any pulses, assume + // 0 pulses/min until we get at least two valid pulses. + const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us; + if ((has_valid_high_edge) && (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->pulse_width_us_ = 0; + this->last_detected_edge_us_ = 0; + this->last_valid_high_edge_us_ = 0; + this->last_valid_low_edge_us_ = 0; + this->has_detected_edge_ = false; + this->has_valid_high_edge_ = false; + this->has_valid_low_edge_ = false; } // We quantize our pulse widths to 1 ms to avoid unnecessary jitter const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000; if (this->pulse_width_dedupe_.next(pulse_width_ms)) { if (pulse_width_ms == 0) { - // Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while) + // Treat 0 pulse width as 0 pulses/min (normally because we've not + // detected any pulses for a while) this->publish_state(0); } else { // Calculate pulses/min from the pulse width in ms @@ -96,9 +77,11 @@ void PulseMeterSensor::dump_config() { } void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { - // This is an interrupt handler - we can't call any virtual method from this method + // 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 + // Get the current time before we do anything else so the measurements are + // consistent const uint32_t now = micros(); // We only look at rising edges in EDGE mode, and all edges in PULSE mode @@ -106,7 +89,45 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { if (sensor->isr_pin_.digital_read()) { sensor->last_detected_edge_us_ = now; } + } + + // Check to see if we should filter this edge out + if (sensor->filter_mode_ == FILTER_EDGE) { + if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) { + // Don't measure the first valid pulse (we need at least two pulses to + // measure the width) + if (sensor->has_valid_high_edge_) { + sensor->pulse_width_us_ = (sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_); + } + sensor->total_pulses_++; + sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_; + sensor->has_valid_high_edge_ = true; + } } else { + // Filter Mode is PULSE + bool pin_val = sensor->isr_pin_.digital_read(); + // Ignore false edges that may be caused by bouncing and exit the ISR ASAP + if (pin_val == sensor->sensor_is_high_) { + return; + } + // Make sure the signal has been stable long enough + if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) { + if (pin_val) { + sensor->has_valid_high_edge_ = true; + sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_; + sensor->sensor_is_high_ = true; + } else { + // Count pulses when a sufficiently long high pulse is concluded. + sensor->total_pulses_++; + if (sensor->has_valid_low_edge_) { + sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_low_edge_us_; + } + sensor->has_valid_low_edge_ = true; + sensor->last_valid_low_edge_us_ = sensor->last_detected_edge_us_; + sensor->sensor_is_high_ = false; + } + } + sensor->has_detected_edge_ = true; sensor->last_detected_edge_us_ = now; } } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index bf50eab6ff..ed4fb2a1f4 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -1,8 +1,8 @@ #pragma once +#include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" -#include "esphome/components/sensor/sensor.h" #include "esphome/core/helpers.h" namespace esphome { @@ -42,11 +42,14 @@ class PulseMeterSensor : public sensor::Sensor, public Component { Deduplicator total_dedupe_; volatile uint32_t last_detected_edge_us_ = 0; - volatile uint32_t last_valid_low_edge_us_ = 0; volatile uint32_t last_valid_high_edge_us_ = 0; + volatile uint32_t last_valid_low_edge_us_ = 0; volatile uint32_t pulse_width_us_ = 0; volatile uint32_t total_pulses_ = 0; volatile bool sensor_is_high_ = false; + volatile bool has_detected_edge_ = false; + volatile bool has_valid_high_edge_ = false; + volatile bool has_valid_low_edge_ = false; }; } // namespace pulse_meter From fe38b36c265f88702d301f569a1e197ea081196f Mon Sep 17 00:00:00 2001 From: Chris Feenstra <73584137+cfeenstra1024@users.noreply.github.com> Date: Wed, 12 Oct 2022 04:29:57 +0200 Subject: [PATCH 134/838] Add support for ZHLT01 heatpump IR protocol (#3655) Co-authored-by: Chris Feenstra --- esphome/components/heatpumpir/climate.py | 1 + esphome/components/heatpumpir/heatpumpir.cpp | 1 + esphome/components/heatpumpir/heatpumpir.h | 1 + 3 files changed, 3 insertions(+) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index a253a778de..2e78db8356 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -58,6 +58,7 @@ PROTOCOLS = { "sharp": Protocol.PROTOCOL_SHARP, "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI, "toshiba": Protocol.PROTOCOL_TOSHIBA, + "zhlt01": Protocol.PROTOCOL_ZHLT01, } CONF_HORIZONTAL_DEFAULT = "horizontal_default" diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index cd24411763..bed1dc76c0 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -53,6 +53,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT + {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT }; void HeatpumpIRClimate::setup() { diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index decf1eae07..c60b944111 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -53,6 +53,7 @@ enum Protocol { PROTOCOL_SHARP, PROTOCOL_TOSHIBA_DAISEIKAI, PROTOCOL_TOSHIBA, + PROTOCOL_ZHLT01, }; // Simple enum to represent horizontal directios From b34d24735a42dabc7486e12211b27b2be3c7613b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:22:07 +1300 Subject: [PATCH 135/838] Send GATT error events to HA (#3884) --- esphome/components/api/api.proto | 28 +++++ esphome/components/api/api_pb2.cpp | 112 +++++++++++++++++ esphome/components/api/api_pb2.h | 37 ++++++ esphome/components/api/api_pb2_service.cpp | 24 ++++ esphome/components/api/api_pb2_service.h | 9 ++ esphome/components/api/api_server.cpp | 21 ++++ esphome/components/api/api_server.h | 3 + .../components/bluetooth_proxy/__init__.py | 2 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 115 ++++++++++++++---- .../bluetooth_proxy/bluetooth_proxy.h | 7 +- .../esp32_ble_client/ble_characteristic.cpp | 7 +- .../esp32_ble_client/ble_characteristic.h | 4 +- tests/test1.yaml | 2 - tests/test2.yaml | 3 + 14 files changed, 334 insertions(+), 40 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 9fa77d2daa..a8e45e1ea6 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1298,3 +1298,31 @@ message BluetoothConnectionsFreeResponse { uint32 free = 1; uint32 limit = 2; } + +message BluetoothGATTErrorResponse { + option (id) = 82; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; + int32 error = 3; +} + +message BluetoothGATTWriteResponse { + option (id) = 83; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} + +message BluetoothGATTNotifyResponse { + option (id) = 84; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + uint32 handle = 2; +} diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 73d8044cde..8cb244f1a1 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5746,6 +5746,118 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + case 3: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); + buffer.encode_int32(3, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTErrorResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTErrorResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTWriteResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTWriteResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->handle = value.as_uint32(); + return true; + } + default: + return false; + } +} +void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_uint32(2, this->handle); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothGATTNotifyResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" handle: "); + sprintf(buffer, "%u", this->handle); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 325e9a23c3..fb676f1cc8 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1481,6 +1481,43 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothGATTErrorResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + int32_t error{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTWriteResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class BluetoothGATTNotifyResponse : public ProtoMessage { + public: + uint64_t address{0}; + uint32_t handle{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 7bfe1acf48..b603ade9de 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -401,6 +401,30 @@ bool APIServerConnectionBase::send_bluetooth_connections_free_response(const Blu return this->send_message_(msg, 81); } #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 82); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 83); +} +#endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 84); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index c7ef1468d8..3cb8b59ba5 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -200,6 +200,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 965f08aa15..dbd732f466 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -324,11 +324,21 @@ void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadRespons client->send_bluetooth_gatt_read_response(call); } } +void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_write_response(call); + } +} void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) { for (auto &client : this->clients_) { client->send_bluetooth_gatt_notify_data_response(call); } } +void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) { + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_notify_response(call); + } +} void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) { for (auto &client : this->clients_) { client->send_bluetooth_gatt_get_services_response(call); @@ -342,6 +352,17 @@ void APIServer::send_bluetooth_gatt_services_done(uint64_t address) { client->send_bluetooth_gatt_get_services_done_response(call); } } +void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) { + BluetoothGATTErrorResponse call; + call.address = address; + call.handle = handle; + call.error = error; + + for (auto &client : this->clients_) { + client->send_bluetooth_gatt_error_response(call); + } +} + #endif APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 72ab55432c..ec8c54a269 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -78,9 +78,12 @@ class APIServer : public Component, public Controller { void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK); void send_bluetooth_connections_free(uint8_t free, uint8_t limit); void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); + void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call); + void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call); void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call); void send_bluetooth_gatt_services_done(uint64_t address); + void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error); #endif void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_HOMEASSISTANT_TIME diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index b5acea89dd..84841d9bf4 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -4,7 +4,7 @@ import esphome.codegen as cg from esphome.const import CONF_ACTIVE, CONF_ID AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] -DEPENDENCIES = ["esp32"] +DEPENDENCIES = ["api", "esp32"] CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 96fee39f95..a08c58bd9e 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -4,21 +4,24 @@ #ifdef USE_ESP32 -#ifdef USE_API #include "esphome/components/api/api_server.h" -#endif namespace esphome { namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy"; +static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; +static const esp_err_t ESP_GATT_WRONG_ADDRESS = -2; + BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; this->address_ = 0; } bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (!api::global_api_server->is_connected()) + return false; ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), device.get_rssi()); this->send_api_packet_(device); @@ -38,9 +41,6 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { -#ifndef USE_API - return; -#else api::BluetoothLEAdvertisementResponse resp; resp.address = device.address_uint64(); if (!device.get_name().empty()) @@ -62,7 +62,6 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi resp.manufacturer_data.push_back(std::move(manufacturer_data)); } api::global_api_server->send_bluetooth_le_advertisement(resp); -#endif } void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, @@ -70,30 +69,23 @@ void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if BLEClientBase::gattc_event_handler(event, gattc_if, param); switch (event) { case ESP_GATTC_DISCONNECT_EVT: { -#ifdef USE_API api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->disconnect.reason); api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->get_bluetooth_connections_limit()); -#endif this->address_ = 0; } case ESP_GATTC_OPEN_EVT: { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { -#ifdef USE_API api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status); - -#endif break; } break; } case ESP_GATTC_SEARCH_CMPL_EVT: { -#ifdef USE_API api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->get_bluetooth_connections_limit()); -#endif break; } case ESP_GATTC_READ_DESCR_EVT: @@ -101,10 +93,11 @@ void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if if (param->read.conn_id != this->conn_id_) break; if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status); + ESP_LOGW(TAG, "Error reading char/descriptor at handle 0x%2X, status=%d", param->read.handle, + param->read.status); + api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status); break; } -#ifdef USE_API api::BluetoothGATTReadResponse resp; resp.address = this->address_; resp.handle = param->read.handle; @@ -113,14 +106,56 @@ void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if resp.data.push_back(param->read.value[i]); } api::global_api_server->send_bluetooth_gatt_read_response(resp); -#endif + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + case ESP_GATTC_WRITE_DESCR_EVT: { + if (param->write.conn_id != this->conn_id_) + break; + if (param->write.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error writing char/descriptor at handle 0x%2X, status=%d", param->write.handle, + param->write.status); + api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status); + break; + } + api::BluetoothGATTWriteResponse resp; + resp.address = this->address_; + resp.handle = param->write.handle; + api::global_api_server->send_bluetooth_gatt_write_response(resp); + break; + } + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { + if (param->unreg_for_notify.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error unregistering notifications for handle 0x%2X, status=%d", param->unreg_for_notify.handle, + param->unreg_for_notify.status); + api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle, + param->unreg_for_notify.status); + break; + } + api::BluetoothGATTNotifyResponse resp; + resp.address = this->address_; + resp.handle = param->unreg_for_notify.handle; + api::global_api_server->send_bluetooth_gatt_notify_response(resp); + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (param->reg_for_notify.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error registering notifications for handle 0x%2X, status=%d", param->reg_for_notify.handle, + param->reg_for_notify.status); + api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle, + param->reg_for_notify.status); + break; + } + api::BluetoothGATTNotifyResponse resp; + resp.address = this->address_; + resp.handle = param->reg_for_notify.handle; + api::global_api_server->send_bluetooth_gatt_notify_response(resp); break; } case ESP_GATTC_NOTIFY_EVT: { if (param->notify.conn_id != this->conn_id_) break; - ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle); -#ifdef USE_API + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%2X", param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; resp.handle = param->notify.handle; @@ -129,7 +164,6 @@ void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if resp.data.push_back(param->notify.value[i]); } api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); -#endif break; } default: @@ -141,7 +175,6 @@ void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } void BluetoothProxy::loop() { BLEClientBase::loop(); -#ifdef USE_API if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) { ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str()); auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_); @@ -177,10 +210,8 @@ void BluetoothProxy::loop() { api::global_api_server->send_bluetooth_gatt_services(resp); this->send_service_++; } -#endif } -#ifdef USE_API void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) { switch (msg.request_type) { case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { @@ -220,16 +251,19 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { if (this->state_ != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS); return; } auto *characteristic = this->get_characteristic(msg.handle); if (characteristic == nullptr) { ESP_LOGW(TAG, "Cannot read GATT characteristic, not found."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE); return; } @@ -239,43 +273,53 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); } } void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) { if (this->state_ != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS); return; } auto *characteristic = this->get_characteristic(msg.handle); if (characteristic == nullptr) { ESP_LOGW(TAG, "Cannot write GATT characteristic, not found."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE); return; } ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str()); - characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(), - msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP); + auto err = characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(), + msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP); + if (err != ERR_OK) { + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); + } } void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) { if (this->state_ != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS); return; } auto *descriptor = this->get_descriptor(msg.handle); if (descriptor == nullptr) { ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE); return; } @@ -286,22 +330,26 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); } } void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) { if (this->state_ != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); return; } if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS); return; } auto *descriptor = this->get_descriptor(msg.handle); if (descriptor == nullptr) { ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE); return; } @@ -313,20 +361,34 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); } } void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot get GATT services, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED); + return; + } if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for service list request"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_WRONG_ADDRESS); return; } this->send_service_ = 0; } void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) { + if (this->state_ != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "Cannot configure notify, not connected."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); + return; + } + if (this->address_ != msg.address) { ESP_LOGW(TAG, "Address mismatch for notify"); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS); return; } @@ -334,6 +396,7 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest if (characteristic == nullptr) { ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found."); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE); return; } @@ -342,17 +405,17 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); } } else { err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle); if (err != ESP_OK) { ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err); + api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); } } } -#endif - BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace bluetooth_proxy diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 8ff43aff3f..97047c118b 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -4,6 +4,7 @@ #include +#include "esphome/components/api/api_pb2.h" #include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/core/automation.h" @@ -12,10 +13,6 @@ #include -#ifdef USE_API -#include "esphome/components/api/api_pb2.h" -#endif // USE_API - namespace esphome { namespace bluetooth_proxy { @@ -31,7 +28,6 @@ class BluetoothProxy : public BLEClientBase { void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; -#ifdef USE_API void bluetooth_device_request(const api::BluetoothDeviceRequest &msg); void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg); void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg); @@ -39,7 +35,6 @@ class BluetoothProxy : public BLEClientBase { void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg); void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg); void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg); -#endif int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; } int get_bluetooth_connections_limit() { return 1; } diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp index 873833368c..2692498e98 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -64,17 +64,18 @@ BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) { return nullptr; } -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { +esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { auto *client = this->service->client; auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size, new_val, write_type, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); } + return status; } -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); } } // namespace esp32_ble_client diff --git a/esphome/components/esp32_ble_client/ble_characteristic.h b/esphome/components/esp32_ble_client/ble_characteristic.h index ffa9227cc4..d290b282fc 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.h +++ b/esphome/components/esp32_ble_client/ble_characteristic.h @@ -24,8 +24,8 @@ class BLECharacteristic { BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(uint16_t uuid); BLEDescriptor *get_descriptor_by_handle(uint16_t handle); - void write_value(uint8_t *new_val, int16_t new_val_size); - void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); + esp_err_t write_value(uint8_t *new_val, int16_t new_val_size); + esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); BLEService *service; }; diff --git a/tests/test1.yaml b/tests/test1.yaml index 01672fcc01..82cdac4623 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -290,8 +290,6 @@ adalight: esp32_ble_tracker: -bluetooth_proxy: - ble_client: - mac_address: AA:BB:CC:DD:EE:FF id: ble_foo diff --git a/tests/test2.yaml b/tests/test2.yaml index 5507fa0631..d485d3f16c 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -506,6 +506,9 @@ xiaomi_ble: mopeka_ble: +bluetooth_proxy: + active: true + xiaomi_rtcgq02lm: - id: motion_rtcgq02lm mac_address: 01:02:03:04:05:06 From 48a1797e720729fe8a71465b1872bf26d9fd8a15 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:26:28 +0300 Subject: [PATCH 136/838] Do not require CS pin for ST7789V (#3888) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/st7789v/display.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index c276be2f5a..d18e305cc2 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -4,7 +4,6 @@ from esphome import pins from esphome.components import display, spi from esphome.const import ( CONF_BACKLIGHT_PIN, - CONF_CS_PIN, CONF_DC_PIN, CONF_HEIGHT, CONF_ID, @@ -69,7 +68,6 @@ CONFIG_SCHEMA = cv.All( 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.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, cv.Optional(CONF_HEIGHT): cv.int_, @@ -79,7 +77,7 @@ CONFIG_SCHEMA = cv.All( } ) .extend(cv.polling_component_schema("5s")) - .extend(spi.spi_device_schema()), + .extend(spi.spi_device_schema(cs_pin_required=False)), validate_st7789v, ) From f422fabab415ab39e78aab874bc19f75ca461ff0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Oct 2022 09:18:46 +1300 Subject: [PATCH 137/838] Bump version to 2022.10.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7dcb3c823c..e0f3bc77b5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.10.0-dev" +__version__ = "2022.10.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From bf15b1d302a05a9d73bdd5c856472b51d91b6e72 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Oct 2022 09:18:46 +1300 Subject: [PATCH 138/838] Bump version to 2022.11.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7dcb3c823c..f2d31bd21f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.10.0-dev" +__version__ = "2022.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 98171c9f499868314b0a862512c03d967a7bcb5c Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 12 Oct 2022 18:11:59 -0300 Subject: [PATCH 139/838] fix never calling preset change trigger (#3864) Co-authored-by: Keith Burzinski --- .../thermostat/thermostat_climate.cpp | 73 +++++++++++++------ .../thermostat/thermostat_climate.h | 3 +- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 54e9f1687c..61e279d4a6 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -974,9 +974,19 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { auto config = this->preset_config_.find(preset); if (config != this->preset_config_.end()) { - ESP_LOGI(TAG, "Switching to preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); - this->change_preset_internal_(config->second); + ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || + this->preset.value() != preset) { + // Fire any preset changed trigger if defined + Trigger<> *trig = this->preset_change_trigger_; + assert(trig != nullptr); + trig->trigger(); + this->refresh(); + ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + } else { + ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + } this->custom_preset.reset(); this->preset = preset; } else { @@ -988,9 +998,19 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) auto config = this->custom_preset_config_.find(custom_preset); if (config != this->custom_preset_config_.end()) { - ESP_LOGI(TAG, "Switching to custom preset %s", custom_preset.c_str()); - this->change_preset_internal_(config->second); + ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str()); + if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) || + this->custom_preset.value() != custom_preset) { + // Fire any preset changed trigger if defined + Trigger<> *trig = this->preset_change_trigger_; + assert(trig != nullptr); + trig->trigger(); + this->refresh(); + ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str()); + } else { + ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset.c_str()); + } this->preset.reset(); this->custom_preset = custom_preset; } else { @@ -998,39 +1018,46 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) } } -void ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) { +bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) { + bool something_changed = false; + if (this->supports_two_points_) { - this->target_temperature_low = config.default_temperature_low; - this->target_temperature_high = config.default_temperature_high; + if (this->target_temperature_low != config.default_temperature_low) { + this->target_temperature_low = config.default_temperature_low; + something_changed = true; + } + if (this->target_temperature_high != config.default_temperature_high) { + this->target_temperature_high = config.default_temperature_high; + something_changed = true; + } } else { - this->target_temperature = config.default_temperature; + if (this->target_temperature != config.default_temperature) { + this->target_temperature = config.default_temperature; + something_changed = true; + } } - // Note: The mode, fan_mode, and swing_mode can all be defined on the preset but if the climate.control call - // also specifies them then the control's version will override these for that call - if (config.mode_.has_value()) { - this->mode = *config.mode_; + // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call + // also specifies them then the climate.control call's values will override the preset's values for that call + if (config.mode_.has_value() && (this->mode != config.mode_.value())) { ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_))); + this->mode = *config.mode_; + something_changed = true; } - if (config.fan_mode_.has_value()) { - this->fan_mode = *config.fan_mode_; + if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_.value())) { ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_))); + this->fan_mode = *config.fan_mode_; + something_changed = true; } - if (config.swing_mode_.has_value()) { + if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) { ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_))); this->swing_mode = *config.swing_mode_; + something_changed = true; } - // Fire any preset changed trigger if defined - if (this->preset != preset) { - Trigger<> *trig = this->preset_change_trigger_; - assert(trig != nullptr); - trig->trigger(); - } - - this->refresh(); + return something_changed; } void ThermostatClimate::set_preset_config(climate::ClimatePreset preset, diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index a738ba4986..68cbb17e34 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -168,7 +168,8 @@ class ThermostatClimate : public climate::Climate, public Component { /// Applies the temperature, mode, fan, and swing modes of the provided config. /// This is agnostic of custom vs built in preset - void change_preset_internal_(const ThermostatClimateTargetTempConfig &config); + /// Returns true if something was changed + bool change_preset_internal_(const ThermostatClimateTargetTempConfig &config); /// Return the traits of this controller. climate::ClimateTraits traits() override; From 71387be72e36804773748d20c623d3e9cc7963d7 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Thu, 13 Oct 2022 03:50:45 +0400 Subject: [PATCH 140/838] Modbus QWORD fix (#3856) --- .../modbus_controller/modbus_controller.cpp | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index e60b016a17..57f714f233 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector &data, SensorValueType sens static_cast(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); } break; case SensorValueType::U_QWORD: - // Ignore bitmask for U_QWORD - value = get_data(data, offset); - break; case SensorValueType::S_QWORD: - // Ignore bitmask for S_QWORD - value = get_data(data, offset); + // Ignore bitmask for QWORD + value = get_data(data, offset); break; case SensorValueType::U_QWORD_R: - // Ignore bitmask for U_QWORD - value = get_data(data, offset); - value = static_cast(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 | - static_cast(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 | - static_cast(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16; - break; - case SensorValueType::S_QWORD_R: - // Ignore bitmask for S_QWORD - value = get_data(data, offset); - break; + case SensorValueType::S_QWORD_R: { + // Ignore bitmask for QWORD + uint64_t tmp = get_data(data, offset); + value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + } break; case SensorValueType::RAW: default: break; From 5ec158811025db6932bbbd999ce6cf322cd89287 Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Wed, 12 Oct 2022 16:59:07 -0700 Subject: [PATCH 141/838] Update the ibeacon code (#3859) --- .../components/esp32_ble_beacon/__init__.py | 57 +++++++++++++++---- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 30 ++++++++-- .../esp32_ble_beacon/esp32_ble_beacon.h | 13 ++++- 3 files changed, 83 insertions(+), 17 deletions(-) diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index d6cbb15dd2..c3bd4ee418 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID -from esphome.core import CORE +from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER +from esphome.core import CORE, TimePeriod from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32"] @@ -12,16 +12,47 @@ ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) CONF_MAJOR = "major" CONF_MINOR = "minor" +CONF_MIN_INTERVAL = "min_interval" +CONF_MAX_INTERVAL = "max_interval" +CONF_MEASURED_POWER = "measured_power" -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), - cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), - cv.Required(CONF_UUID): cv.uuid, - cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, - cv.Optional(CONF_MINOR, default=61958): cv.uint16_t, - } -).extend(cv.COMPONENT_SCHEMA) + +def validate_config(config): + if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL): + raise cv.Invalid("min_interval must be <= max_interval") + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), + cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), + cv.Required(CONF_UUID): cv.uuid, + cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, + cv.Optional(CONF_MINOR, default=61958): cv.uint16_t, + cv.Optional(CONF_MIN_INTERVAL, default="100ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240) + ), + ), + cv.Optional(CONF_MAX_INTERVAL, default="100ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240) + ), + ), + cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range( + min=-128, max=0 + ), + cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( + cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True) + ), + } + ).extend(cv.COMPONENT_SCHEMA), + validate_config, +) async def to_code(config): @@ -31,6 +62,10 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_minor(config[CONF_MINOR])) + cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL])) + cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL])) + cg.add(var.set_measured_power(config[CONF_MEASURED_POWER])) + cg.add(var.set_tx_power(config[CONF_TX_POWER])) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 955bc8595f..589fcc1e82 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -36,11 +36,24 @@ static esp_ble_adv_params_t ble_adv_params = { #define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8)) static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { - .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502}; + .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; void ESP32BLEBeacon::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:"); - ESP_LOGCONFIG(TAG, " Major: %u, Minor: %u", this->major_, this->minor_); + char uuid[37]; + char *bpos = uuid; + for (int8_t ii = 0; ii < 16; ++ii) { + bpos += sprintf(bpos, "%02X", this->uuid_[ii]); + if (ii == 3 || ii == 5 || ii == 7 || ii == 9) { + bpos += sprintf(bpos, "-"); + } + } + uuid[36] = '\0'; + ESP_LOGCONFIG(TAG, + " UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" + ", TX Power: %ddBm", + uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, + this->tx_power_); } void ESP32BLEBeacon::setup() { @@ -67,6 +80,9 @@ void ESP32BLEBeacon::ble_core_task(void *params) { } void ESP32BLEBeacon::ble_setup() { + ble_adv_params.adv_int_min = static_cast(global_esp32_ble_beacon->min_interval_ / 0.625f); + ble_adv_params.adv_int_max = static_cast(global_esp32_ble_beacon->max_interval_ / 0.625f); + // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); if (err != ESP_OK) { @@ -118,6 +134,12 @@ void ESP32BLEBeacon::ble_setup() { ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err); return; } + err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, + static_cast((global_esp32_ble_beacon->tx_power_ + 12) / 3)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); + return; + } err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err); @@ -130,7 +152,7 @@ void ESP32BLEBeacon::ble_setup() { sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_); - ibeacon_adv_data.ibeacon_vendor.measured_power = 0xC5; + ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast(global_esp32_ble_beacon->measured_power_); esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); } @@ -153,7 +175,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap break; } case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { - err = param->adv_start_cmpl.status; + err = param->adv_stop_cmpl.status; if (err != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(TAG, "BLE adv stop failed: %s", esp_err_to_name(err)); } else { diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index 80ad2041f2..5208b67ea3 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -5,6 +5,7 @@ #ifdef USE_ESP32 #include +#include namespace esphome { namespace esp32_ble_beacon { @@ -14,8 +15,8 @@ typedef struct { uint8_t flags[3]; uint8_t length; uint8_t type; - uint16_t company_id; - uint16_t beacon_type; + uint8_t company_id[2]; + uint8_t beacon_type[2]; } __attribute__((packed)) esp_ble_ibeacon_head_t; // NOLINTNEXTLINE(modernize-use-using) @@ -42,6 +43,10 @@ class ESP32BLEBeacon : public Component { void set_major(uint16_t major) { this->major_ = major; } void set_minor(uint16_t minor) { this->minor_ = minor; } + void set_min_interval(uint16_t val) { this->min_interval_ = val; } + void set_max_interval(uint16_t val) { this->max_interval_ = val; } + void set_measured_power(int8_t val) { this->measured_power_ = val; } + void set_tx_power(int8_t val) { this->tx_power_ = val; } protected: static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -51,6 +56,10 @@ class ESP32BLEBeacon : public Component { std::array uuid_; uint16_t major_{}; uint16_t minor_{}; + uint16_t min_interval_{}; + uint16_t max_interval_{}; + int8_t measured_power_{}; + int8_t tx_power_{}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) From 3b21d1d81eef1a5fac74b3f9239376cf180ae208 Mon Sep 17 00:00:00 2001 From: Brian Kaufman Date: Thu, 13 Oct 2022 12:55:59 -0700 Subject: [PATCH 142/838] Don't Use Base Network Manual IP for WiFi AP (#3902) --- esphome/components/wifi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a4c246da89..93ea6b92a4 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -368,7 +368,7 @@ async def to_code(config): if CONF_AP in config: conf = config[CONF_AP] - ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP)) + ip_config = conf.get(CONF_MANUAL_IP) cg.with_local_variable( conf[CONF_ID], WiFiAP(), From 4bf94e0757420d97d7bd4c1d1950d8efea1477b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 13 Oct 2022 21:58:42 +0200 Subject: [PATCH 143/838] Allow preserving WiFi credentials entered with captive_portal (#3813) --- esphome/components/captive_portal/__init__.py | 24 ++++++++++++++++++- esphome/components/wifi/wifi_component.cpp | 4 ++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index f024c94b01..b41095304e 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -1,8 +1,9 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI from esphome.core import coroutine_with_priority, CORE AUTO_LOAD = ["web_server_base"] @@ -12,6 +13,7 @@ CODEOWNERS = ["@OttoWinter"] captive_portal_ns = cg.esphome_ns.namespace("captive_portal") CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) +CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials" CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -19,12 +21,29 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), cv.only_with_arduino, ) +def validate_wifi(config): + wifi_conf = fv.full_config.get()[CONF_WIFI] + if config.get(CONF_KEEP_USER_CREDENTIALS, False) and ( + CONF_SSID in wifi_conf + or CONF_PASSWORD in wifi_conf + or CONF_NETWORKS in wifi_conf + ): + raise cv.Invalid( + f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}" + ) + return config + + +FINAL_VALIDATE_SCHEMA = validate_wifi + + @coroutine_with_priority(64.0) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) @@ -38,3 +57,6 @@ async def to_code(config): cg.add_library("WiFi", None) if CORE.is_esp8266: cg.add_library("DNSServer", None) + + if config.get(CONF_KEEP_USER_CREDENTIALS, False): + cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS") diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 37f5276c8f..0103106222 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -38,7 +38,11 @@ void WiFiComponent::setup() { this->last_connected_ = millis(); this->wifi_pre_setup_(); +#ifndef USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS uint32_t hash = fnv1_hash(App.get_compilation_time()); +#else + uint32_t hash = 88491487UL; +#endif this->pref_ = global_preferences->make_preference(hash, true); SavedWifiSettings save{}; From 225b3c14943f698de57e1ad17fee97c56e13b36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Jouault?= Date: Fri, 14 Oct 2022 01:47:05 +0200 Subject: [PATCH 144/838] Send true and not RSSI in ble_presence (#3904) --- esphome/components/ble_presence/ble_presence_device.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index dcccf844d2..1689c9ba3f 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, case MATCH_BY_SERVICE_UUID: for (auto uuid : device.get_service_uuids()) { if (this->uuid_ == uuid) { - this->publish_state(device.get_rssi()); + this->publish_state(true); this->found_ = true; return true; } @@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, return false; } - this->publish_state(device.get_rssi()); + this->publish_state(true); this->found_ = true; return true; } From 8bb670521d3d51632a64781753219a9df9bc9f71 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:35:35 +1300 Subject: [PATCH 145/838] Remove address type map from bluetooth proxy (#3905) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 21 ------------------- .../bluetooth_proxy/bluetooth_proxy.h | 1 - 2 files changed, 22 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index a08c58bd9e..cc1c178b08 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -26,16 +26,9 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) device.get_rssi()); this->send_api_packet_(device); - this->address_type_map_[device.address_uint64()] = device.get_address_type(); - if (this->address_ == 0) return true; - if (this->state_ == espbt::ClientState::DISCOVERED) { - ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str()); - return true; - } - BLEClientBase::parse_device(device); return true; } @@ -216,20 +209,6 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest switch (msg.request_type) { case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { this->address_ = msg.address; - if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) { - // Utilise the address type cache - this->remote_addr_type_ = this->address_type_map_[this->address_]; - } else { - this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC; - } - this->remote_bda_[0] = (this->address_ >> 40) & 0xFF; - this->remote_bda_[1] = (this->address_ >> 32) & 0xFF; - this->remote_bda_[2] = (this->address_ >> 24) & 0xFF; - this->remote_bda_[3] = (this->address_ >> 16) & 0xFF; - this->remote_bda_[4] = (this->address_ >> 8) & 0xFF; - this->remote_bda_[5] = (this->address_ >> 0) & 0xFF; - this->set_state(espbt::ClientState::DISCOVERED); - esp_ble_gap_stop_scanning(); break; } case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 97047c118b..9529b99f73 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -45,7 +45,6 @@ class BluetoothProxy : public BLEClientBase { protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); - std::map address_type_map_; int16_t send_service_{-1}; bool active_; }; From be914f2c1593453ba8ed7736e699ba5c16c9e80e Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 12 Oct 2022 18:11:59 -0300 Subject: [PATCH 146/838] fix never calling preset change trigger (#3864) Co-authored-by: Keith Burzinski --- .../thermostat/thermostat_climate.cpp | 73 +++++++++++++------ .../thermostat/thermostat_climate.h | 3 +- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 54e9f1687c..61e279d4a6 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -974,9 +974,19 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { auto config = this->preset_config_.find(preset); if (config != this->preset_config_.end()) { - ESP_LOGI(TAG, "Switching to preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); - this->change_preset_internal_(config->second); + ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || + this->preset.value() != preset) { + // Fire any preset changed trigger if defined + Trigger<> *trig = this->preset_change_trigger_; + assert(trig != nullptr); + trig->trigger(); + this->refresh(); + ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + } else { + ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + } this->custom_preset.reset(); this->preset = preset; } else { @@ -988,9 +998,19 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) auto config = this->custom_preset_config_.find(custom_preset); if (config != this->custom_preset_config_.end()) { - ESP_LOGI(TAG, "Switching to custom preset %s", custom_preset.c_str()); - this->change_preset_internal_(config->second); + ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str()); + if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) || + this->custom_preset.value() != custom_preset) { + // Fire any preset changed trigger if defined + Trigger<> *trig = this->preset_change_trigger_; + assert(trig != nullptr); + trig->trigger(); + this->refresh(); + ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str()); + } else { + ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset.c_str()); + } this->preset.reset(); this->custom_preset = custom_preset; } else { @@ -998,39 +1018,46 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) } } -void ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) { +bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) { + bool something_changed = false; + if (this->supports_two_points_) { - this->target_temperature_low = config.default_temperature_low; - this->target_temperature_high = config.default_temperature_high; + if (this->target_temperature_low != config.default_temperature_low) { + this->target_temperature_low = config.default_temperature_low; + something_changed = true; + } + if (this->target_temperature_high != config.default_temperature_high) { + this->target_temperature_high = config.default_temperature_high; + something_changed = true; + } } else { - this->target_temperature = config.default_temperature; + if (this->target_temperature != config.default_temperature) { + this->target_temperature = config.default_temperature; + something_changed = true; + } } - // Note: The mode, fan_mode, and swing_mode can all be defined on the preset but if the climate.control call - // also specifies them then the control's version will override these for that call - if (config.mode_.has_value()) { - this->mode = *config.mode_; + // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call + // also specifies them then the climate.control call's values will override the preset's values for that call + if (config.mode_.has_value() && (this->mode != config.mode_.value())) { ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_))); + this->mode = *config.mode_; + something_changed = true; } - if (config.fan_mode_.has_value()) { - this->fan_mode = *config.fan_mode_; + if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_.value())) { ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_))); + this->fan_mode = *config.fan_mode_; + something_changed = true; } - if (config.swing_mode_.has_value()) { + if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) { ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_))); this->swing_mode = *config.swing_mode_; + something_changed = true; } - // Fire any preset changed trigger if defined - if (this->preset != preset) { - Trigger<> *trig = this->preset_change_trigger_; - assert(trig != nullptr); - trig->trigger(); - } - - this->refresh(); + return something_changed; } void ThermostatClimate::set_preset_config(climate::ClimatePreset preset, diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index a738ba4986..68cbb17e34 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -168,7 +168,8 @@ class ThermostatClimate : public climate::Climate, public Component { /// Applies the temperature, mode, fan, and swing modes of the provided config. /// This is agnostic of custom vs built in preset - void change_preset_internal_(const ThermostatClimateTargetTempConfig &config); + /// Returns true if something was changed + bool change_preset_internal_(const ThermostatClimateTargetTempConfig &config); /// Return the traits of this controller. climate::ClimateTraits traits() override; From 8bf34e09f43a50c641c9e67892d99c4ed84ae7da Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Thu, 13 Oct 2022 03:50:45 +0400 Subject: [PATCH 147/838] Modbus QWORD fix (#3856) --- .../modbus_controller/modbus_controller.cpp | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index e60b016a17..57f714f233 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector &data, SensorValueType sens static_cast(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); } break; case SensorValueType::U_QWORD: - // Ignore bitmask for U_QWORD - value = get_data(data, offset); - break; case SensorValueType::S_QWORD: - // Ignore bitmask for S_QWORD - value = get_data(data, offset); + // Ignore bitmask for QWORD + value = get_data(data, offset); break; case SensorValueType::U_QWORD_R: - // Ignore bitmask for U_QWORD - value = get_data(data, offset); - value = static_cast(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 | - static_cast(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 | - static_cast(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16; - break; - case SensorValueType::S_QWORD_R: - // Ignore bitmask for S_QWORD - value = get_data(data, offset); - break; + case SensorValueType::S_QWORD_R: { + // Ignore bitmask for QWORD + uint64_t tmp = get_data(data, offset); + value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000); + } break; case SensorValueType::RAW: default: break; From b2d91ac5de0f001b9d058e097714d5f1a6c08f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Jouault?= Date: Fri, 14 Oct 2022 01:47:05 +0200 Subject: [PATCH 148/838] Send true and not RSSI in ble_presence (#3904) --- esphome/components/ble_presence/ble_presence_device.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index dcccf844d2..1689c9ba3f 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, case MATCH_BY_SERVICE_UUID: for (auto uuid : device.get_service_uuids()) { if (this->uuid_ == uuid) { - this->publish_state(device.get_rssi()); + this->publish_state(true); this->found_ = true; return true; } @@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, return false; } - this->publish_state(device.get_rssi()); + this->publish_state(true); this->found_ = true; return true; } From b6073408f4a820b2c38da517f9092a1f8abdfe1c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:35:35 +1300 Subject: [PATCH 149/838] Remove address type map from bluetooth proxy (#3905) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 21 ------------------- .../bluetooth_proxy/bluetooth_proxy.h | 1 - 2 files changed, 22 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index a08c58bd9e..cc1c178b08 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -26,16 +26,9 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) device.get_rssi()); this->send_api_packet_(device); - this->address_type_map_[device.address_uint64()] = device.get_address_type(); - if (this->address_ == 0) return true; - if (this->state_ == espbt::ClientState::DISCOVERED) { - ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str()); - return true; - } - BLEClientBase::parse_device(device); return true; } @@ -216,20 +209,6 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest switch (msg.request_type) { case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: { this->address_ = msg.address; - if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) { - // Utilise the address type cache - this->remote_addr_type_ = this->address_type_map_[this->address_]; - } else { - this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC; - } - this->remote_bda_[0] = (this->address_ >> 40) & 0xFF; - this->remote_bda_[1] = (this->address_ >> 32) & 0xFF; - this->remote_bda_[2] = (this->address_ >> 24) & 0xFF; - this->remote_bda_[3] = (this->address_ >> 16) & 0xFF; - this->remote_bda_[4] = (this->address_ >> 8) & 0xFF; - this->remote_bda_[5] = (this->address_ >> 0) & 0xFF; - this->set_state(espbt::ClientState::DISCOVERED); - esp_ble_gap_stop_scanning(); break; } case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 97047c118b..9529b99f73 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -45,7 +45,6 @@ class BluetoothProxy : public BLEClientBase { protected: void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); - std::map address_type_map_; int16_t send_service_{-1}; bool active_; }; From a84378c6c26aed20537810bee5ceb0386339c903 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:37:11 +1300 Subject: [PATCH 150/838] Bump version to 2022.10.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e0f3bc77b5..9d608ebf9b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.10.0b1" +__version__ = "2022.10.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d1263e583b2b5c41fe73fa3668cae4a5b960a088 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:30:22 +1300 Subject: [PATCH 151/838] Bump version to 2022.10.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 9d608ebf9b..d2bac8f9df 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.10.0b2" +__version__ = "2022.10.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 58cd754e0733c22b456e2472e730e9d84b6f12f4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:50:32 +1300 Subject: [PATCH 152/838] Fix bad merge --- .../bluetooth_proxy/bluetooth_proxy.cpp | 72 ------------------- 1 file changed, 72 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 3f06cd5c25..cc1c178b08 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -164,78 +164,6 @@ void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if } } -void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) { - BLEClientBase::gattc_event_handler(event, gattc_if, param); - switch (event) { - case ESP_GATTC_DISCONNECT_EVT: { -#ifdef USE_API - api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, - param->disconnect.reason); - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); -#endif - this->address_ = 0; - } - case ESP_GATTC_OPEN_EVT: { - if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { -#ifdef USE_API - api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status); - -#endif - break; - } - break; - } - case ESP_GATTC_SEARCH_CMPL_EVT: { -#ifdef USE_API - api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); - api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), - this->get_bluetooth_connections_limit()); -#endif - break; - } - case ESP_GATTC_READ_DESCR_EVT: - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->conn_id_) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status); - break; - } -#ifdef USE_API - api::BluetoothGATTReadResponse resp; - resp.address = this->address_; - resp.handle = param->read.handle; - resp.data.reserve(param->read.value_len); - for (uint16_t i = 0; i < param->read.value_len; i++) { - resp.data.push_back(param->read.value[i]); - } - api::global_api_server->send_bluetooth_gatt_read_response(resp); -#endif - break; - } - case ESP_GATTC_NOTIFY_EVT: { - if (param->notify.conn_id != this->conn_id_) - break; - ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle); -#ifdef USE_API - api::BluetoothGATTNotifyDataResponse resp; - resp.address = this->address_; - resp.handle = param->notify.handle; - resp.data.reserve(param->notify.value_len); - for (uint16_t i = 0; i < param->notify.value_len; i++) { - resp.data.push_back(param->notify.value[i]); - } - api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); -#endif - break; - } - default: - break; - } -} - void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } void BluetoothProxy::loop() { From 4ac72d7d08bc54f06751424ef57b1e794526745b Mon Sep 17 00:00:00 2001 From: Marcel Hoppe Date: Wed, 19 Oct 2022 02:44:26 +0200 Subject: [PATCH 153/838] Add support for wl-134 (#3569) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/wl_134/__init__.py | 0 esphome/components/wl_134/text_sensor.py | 31 +++++++ esphome/components/wl_134/wl_134.cpp | 111 +++++++++++++++++++++++ esphome/components/wl_134/wl_134.h | 63 +++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 esphome/components/wl_134/__init__.py create mode 100644 esphome/components/wl_134/text_sensor.py create mode 100644 esphome/components/wl_134/wl_134.cpp create mode 100644 esphome/components/wl_134/wl_134.h diff --git a/CODEOWNERS b/CODEOWNERS index b04b480780..4ac394345c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -254,6 +254,7 @@ esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/whynter/* @aeonsablaze +esphome/components/wl_134/* @hobbypunk90 esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs diff --git a/esphome/components/wl_134/__init__.py b/esphome/components/wl_134/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/wl_134/text_sensor.py b/esphome/components/wl_134/text_sensor.py new file mode 100644 index 0000000000..1373df77f4 --- /dev/null +++ b/esphome/components/wl_134/text_sensor.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor, uart +from esphome.const import ( + ICON_FINGERPRINT, +) + +CODEOWNERS = ["@hobbypunk90"] +DEPENDENCIES = ["uart"] +CONF_RESET = "reset" + +wl134_ns = cg.esphome_ns.namespace("wl_134") +Wl134Component = wl134_ns.class_( + "Wl134Component", text_sensor.TextSensor, cg.Component, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema( + Wl134Component, + icon=ICON_FINGERPRINT, + ) + .extend({cv.Optional(CONF_RESET, default=False): cv.boolean}) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) + cg.add(var.set_do_reset(config[CONF_RESET])) + await uart.register_uart_device(var, config) diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp new file mode 100644 index 0000000000..3ffa0c63ce --- /dev/null +++ b/esphome/components/wl_134/wl_134.cpp @@ -0,0 +1,111 @@ +#include "wl_134.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace wl_134 { + +static const char *const TAG = "wl_134.sensor"; +static const uint8_t ASCII_CR = 0x0D; +static const uint8_t ASCII_NBSP = 0xFF; +static const int MAX_DATA_LENGTH_BYTES = 6; + +void Wl134Component::setup() { this->publish_state(""); } + +void Wl134Component::loop() { + while (this->available() >= RFID134_PACKET_SIZE) { + Wl134Component::Rfid134Error error = this->read_packet_(); + if (error != RFID134_ERROR_NONE) { + ESP_LOGW(TAG, "Error: %d", error); + } + } +} + +Wl134Component::Rfid134Error Wl134Component::read_packet_() { + uint8_t packet[RFID134_PACKET_SIZE]; + packet[RFID134_PACKET_START_CODE] = this->read(); + + // check for the first byte being the packet start code + if (packet[RFID134_PACKET_START_CODE] != 0x02) { + // just out of sync, ignore until we are synced up + return RFID134_ERROR_NONE; + } + + if (!this->read_array(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_SIZE - 1)) { + return RFID134_ERROR_PACKET_SIZE; + } + + if (packet[RFID134_PACKET_END_CODE] != 0x03) { + return RFID134_ERROR_PACKET_END_CODE_MISSMATCH; + } + + // calculate checksum + uint8_t checksum = 0; + for (uint8_t i = RFID134_PACKET_ID; i < RFID134_PACKET_CHECKSUM; i++) { + checksum = checksum ^ packet[i]; + } + + // test checksum + if (checksum != packet[RFID134_PACKET_CHECKSUM]) { + return RFID134_ERROR_PACKET_CHECKSUM; + } + + if (static_cast(~checksum) != static_cast(packet[RFID134_PACKET_CHECKSUM_INVERT])) { + return RFID134_ERROR_PACKET_CHECKSUM_INVERT; + } + + Rfid134Reading reading; + + // convert packet into the reading struct + reading.id = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_COUNTRY - RFID134_PACKET_ID); + reading.country = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_COUNTRY]), + RFID134_PACKET_DATA_FLAG - RFID134_PACKET_COUNTRY); + reading.isData = packet[RFID134_PACKET_DATA_FLAG] == '1'; + reading.isAnimal = packet[RFID134_PACKET_ANIMAL_FLAG] == '1'; + reading.reserved0 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED0]), + RFID134_PACKET_RESERVED1 - RFID134_PACKET_RESERVED0); + reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]), + RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1); + + ESP_LOGV(TAG, "Tag id: %012lld", reading.id); + ESP_LOGV(TAG, "Country: %03d", reading.country); + ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false"); + ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false"); + ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0); + ESP_LOGV(TAG, "Reserved1: %d", reading.reserved1); + + char buf[20]; + sprintf(buf, "%03d%012lld", reading.country, reading.id); + this->publish_state(buf); + if (this->do_reset_) { + this->set_timeout(1000, [this]() { this->publish_state(""); }); + } + + return RFID134_ERROR_NONE; +} + +uint64_t Wl134Component::hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size) { + uint64_t value = 0; + uint8_t i = text_size; + do { + i--; + + uint8_t digit = text[i]; + if (digit >= 'A') { + digit = digit - 'A' + 10; + } else { + digit = digit - '0'; + } + value = (value << 4) + digit; + } while (i != 0); + + return value; +} + +void Wl134Component::dump_config() { + ESP_LOGCONFIG(TAG, "WL-134 Sensor:"); + LOG_TEXT_SENSOR("", "Tag", this); + // As specified in the sensor's data sheet + this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); +} +} // namespace wl_134 +} // namespace esphome diff --git a/esphome/components/wl_134/wl_134.h b/esphome/components/wl_134/wl_134.h new file mode 100644 index 0000000000..c0a90de17d --- /dev/null +++ b/esphome/components/wl_134/wl_134.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace wl_134 { + +class Wl134Component : public text_sensor::TextSensor, public Component, public uart::UARTDevice { + public: + enum Rfid134Error { + RFID134_ERROR_NONE, + + // from library + RFID134_ERROR_PACKET_SIZE = 0x81, + RFID134_ERROR_PACKET_END_CODE_MISSMATCH, + RFID134_ERROR_PACKET_CHECKSUM, + RFID134_ERROR_PACKET_CHECKSUM_INVERT + }; + + struct Rfid134Reading { + uint16_t country; + uint64_t id; + bool isData; + bool isAnimal; + uint16_t reserved0; + uint32_t reserved1; + }; + // Nothing really public. + + // ========== INTERNAL METHODS ========== + void setup() override; + void loop() override; + void dump_config() override; + + void set_do_reset(bool do_reset) { this->do_reset_ = do_reset; } + + private: + enum DfMp3Packet { + RFID134_PACKET_START_CODE, + RFID134_PACKET_ID = 1, + RFID134_PACKET_COUNTRY = 11, + RFID134_PACKET_DATA_FLAG = 15, + RFID134_PACKET_ANIMAL_FLAG = 16, + RFID134_PACKET_RESERVED0 = 17, + RFID134_PACKET_RESERVED1 = 21, + RFID134_PACKET_CHECKSUM = 27, + RFID134_PACKET_CHECKSUM_INVERT = 28, + RFID134_PACKET_END_CODE = 29, + RFID134_PACKET_SIZE + }; + + bool do_reset_; + + Rfid134Error read_packet_(); + uint64_t hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size); +}; + +} // namespace wl_134 +} // namespace esphome From 41b5cb06d3d9a58a08fed080741bef35404e149f Mon Sep 17 00:00:00 2001 From: Jadson Santos <42282908+gtjadsonsantos@users.noreply.github.com> Date: Tue, 18 Oct 2022 21:44:48 -0300 Subject: [PATCH 154/838] New platform ethernet_info from component text_sensor (#3811) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ethernet_info/__init__.py | 1 + .../ethernet_info_text_sensor.cpp | 16 +++++++++ .../ethernet_info/ethernet_info_text_sensor.h | 35 +++++++++++++++++++ .../components/ethernet_info/text_sensor.py | 34 ++++++++++++++++++ esphome/core/component.cpp | 1 + esphome/core/component.h | 1 + tests/test4.yaml | 3 ++ 8 files changed, 92 insertions(+) create mode 100644 esphome/components/ethernet_info/__init__.py create mode 100644 esphome/components/ethernet_info/ethernet_info_text_sensor.cpp create mode 100644 esphome/components/ethernet_info/ethernet_info_text_sensor.h create mode 100644 esphome/components/ethernet_info/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 4ac394345c..d3151c3f02 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -76,6 +76,7 @@ esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core +esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/factory_reset/* @anatoly-savchenkov diff --git a/esphome/components/ethernet_info/__init__.py b/esphome/components/ethernet_info/__init__.py new file mode 100644 index 0000000000..312688d3fd --- /dev/null +++ b/esphome/components/ethernet_info/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@gtjadsonsantos"] diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp new file mode 100644 index 0000000000..e69872c290 --- /dev/null +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -0,0 +1,16 @@ +#include "ethernet_info_text_sensor.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +namespace esphome { +namespace ethernet_info { + +static const char *const TAG = "ethernet_info"; + +void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); } + +} // namespace ethernet_info +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h new file mode 100644 index 0000000000..c683b68080 --- /dev/null +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/ethernet/ethernet_component.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +namespace esphome { +namespace ethernet_info { + +class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { + public: + void update() override { + tcpip_adapter_ip_info_t tcpip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &tcpip); + auto ip = tcpip.ip.addr; + if (ip != this->last_ip_) { + this->last_ip_ = ip; + this->publish_state(network::IPAddress(ip).str()); + } + } + + float get_setup_priority() const override { return setup_priority::ETHERNET; } + std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } + void dump_config() override; + + protected: + network::IPAddress last_ip_; +}; + +} // namespace ethernet_info +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py new file mode 100644 index 0000000000..7cb9944c92 --- /dev/null +++ b/esphome/components/ethernet_info/text_sensor.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_IP_ADDRESS, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +DEPENDENCIES = ["ethernet"] + +ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info") + +IPAddressEsthernetInfo = ethernet_info_ns.class_( + "IPAddressEthernetInfo", 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")) + } +) + + +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) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 5cb063cbec..7505e1f39b 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -20,6 +20,7 @@ const float PROCESSOR = 400.0; const float BLUETOOTH = 350.0f; const float AFTER_BLUETOOTH = 300.0f; const float WIFI = 250.0f; +const float ETHERNET = 250.0f; const float BEFORE_CONNECTION = 220.0f; const float AFTER_WIFI = 200.0f; const float AFTER_CONNECTION = 100.0f; diff --git a/esphome/core/component.h b/esphome/core/component.h index cb97a93d21..1d8499e262 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -29,6 +29,7 @@ extern const float PROCESSOR; extern const float BLUETOOTH; extern const float AFTER_BLUETOOTH; extern const float WIFI; +extern const float ETHERNET; /// For components that should be initialized after WiFi and before API is connected. extern const float BEFORE_CONNECTION; /// For components that should be initialized after WiFi is connected. diff --git a/tests/test4.yaml b/tests/test4.yaml index cf517bb391..a8077c625f 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -532,6 +532,9 @@ text_sensor: - platform: copy source_id: inverter0_device_mode name: Inverter Text Sensor Copy + - platform: ethernet_info + ip_address: + name: IP Address output: - platform: pipsolar From f30e54d1770f1e38e9a10931fa9199f6dbbe9f9e Mon Sep 17 00:00:00 2001 From: Carlos Gustavo Sarmiento Date: Tue, 18 Oct 2022 22:08:27 -0500 Subject: [PATCH 155/838] Implementation for Atlas Scientific Peristaltic Pump (#3528) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ezo_pmp/__init__.py | 291 +++++++++++ esphome/components/ezo_pmp/binary_sensor.py | 42 ++ esphome/components/ezo_pmp/ezo_pmp.cpp | 542 ++++++++++++++++++++ esphome/components/ezo_pmp/ezo_pmp.h | 252 +++++++++ esphome/components/ezo_pmp/sensor.py | 104 ++++ esphome/components/ezo_pmp/text_sensor.py | 39 ++ tests/test5.yaml | 54 ++ 8 files changed, 1325 insertions(+) create mode 100644 esphome/components/ezo_pmp/__init__.py create mode 100644 esphome/components/ezo_pmp/binary_sensor.py create mode 100644 esphome/components/ezo_pmp/ezo_pmp.cpp create mode 100644 esphome/components/ezo_pmp/ezo_pmp.h create mode 100644 esphome/components/ezo_pmp/sensor.py create mode 100644 esphome/components/ezo_pmp/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index d3151c3f02..86e6cd978b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -79,6 +79,7 @@ esphome/components/esp8266/* @esphome/core esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb +esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/factory_reset/* @anatoly-savchenkov esphome/components/fastled_base/* @OttoWinter esphome/components/feedback/* @ianchi diff --git a/esphome/components/ezo_pmp/__init__.py b/esphome/components/ezo_pmp/__init__.py new file mode 100644 index 0000000000..e65fcf74ca --- /dev/null +++ b/esphome/components/ezo_pmp/__init__.py @@ -0,0 +1,291 @@ +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 import automation +from esphome.automation import maybe_simple_id + +CODEOWNERS = ["@carlos-sarmiento"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +CONF_VOLUME = "volume" +CONF_VOLUME_PER_MINUTE = "volume_per_minute" + +ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp") +EzoPMP = ezo_pmp_ns.class_("EzoPMP", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EzoPMP), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(103)) +) + + +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) + + +EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + } +) + +# Actions that do not require more arguments + +EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action) +EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_( + "EzoPMPClearTotalVolumeDispensedAction", automation.Action +) +EzoPMPClearCalibrationAction = ezo_pmp_ns.class_( + "EzoPMPClearCalibrationAction", automation.Action +) +EzoPMPPauseDosingAction = ezo_pmp_ns.class_( + "EzoPMPPauseDosingAction", automation.Action +) +EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action) +EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_( + "EzoPMPDoseContinuouslyAction", automation.Action +) + +# Actions that require more arguments +EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action) +EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_( + "EzoPMPDoseVolumeOverTimeAction", automation.Action +) +EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_( + "EzoPMPDoseWithConstantFlowRateAction", automation.Action +) +EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_( + "EzoPMPSetCalibrationVolumeAction", automation.Action +) +EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_( + "EzoPMPChangeI2CAddressAction", automation.Action +) +EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_( + "EzoPMPArbitraryCommandAction", automation.Action +) + + +@automation.register_action( + "ezo_pmp.find", EzoPMPFindAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA +) +async def ezo_pmp_find_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( + "ezo_pmp.dose_continuously", + EzoPMPDoseContinuouslyAction, + EZO_PMP_NO_ARGS_ACTION_SCHEMA, +) +async def ezo_pmp_dose_continuously_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( + "ezo_pmp.clear_total_volume_dosed", + EzoPMPClearTotalVolumeDispensedAction, + EZO_PMP_NO_ARGS_ACTION_SCHEMA, +) +async def ezo_pmp_clear_total_volume_dosed_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( + "ezo_pmp.clear_calibration", + EzoPMPClearCalibrationAction, + EZO_PMP_NO_ARGS_ACTION_SCHEMA, +) +async def ezo_pmp_clear_calibration_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@automation.register_action( + "ezo_pmp.pause_dosing", EzoPMPPauseDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA +) +async def ezo_pmp_pause_dosing_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( + "ezo_pmp.stop_dosing", EzoPMPStopDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA +) +async def ezo_pmp_stop_dosing_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) + + +# Actions that require Multiple Args + +EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_VOLUME): cv.templatable( + cv.float_range() + ), # Any way to represent as proper volume (vs. raw int) + } +) + + +@automation.register_action( + "ezo_pmp.dose_volume", EzoPMPDoseVolumeAction, EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA +) +async def ezo_pmp_dose_volume_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_VOLUME], args, cg.double) + cg.add(var.set_volume(template_)) + + return var + + +EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_VOLUME): cv.templatable( + cv.float_range() + ), # Any way to represent as proper volume (vs. raw int) + cv.Required(CONF_DURATION): cv.templatable( + cv.int_range(1) + ), # Any way to represent it as minutes (vs. raw int) + } +) + + +@automation.register_action( + "ezo_pmp.dose_volume_over_time", + EzoPMPDoseVolumeOverTimeAction, + EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA, +) +async def ezo_pmp_dose_volume_over_time_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_VOLUME], args, cg.double) + cg.add(var.set_volume(template_)) + + template_ = await cg.templatable(config[CONF_DURATION], args, int) + cg.add(var.set_duration(template_)) + + return var + + +EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_VOLUME_PER_MINUTE): cv.templatable( + cv.float_range() + ), # Any way to represent as proper volume (vs. raw int) + cv.Required(CONF_DURATION): cv.templatable( + cv.int_range(1) + ), # Any way to represent it as minutes (vs. raw int) + } +) + + +@automation.register_action( + "ezo_pmp.dose_with_constant_flow_rate", + EzoPMPDoseWithConstantFlowRateAction, + EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA, +) +async def ezo_pmp_dose_with_constant_flow_rate_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_VOLUME_PER_MINUTE], args, cg.double) + cg.add(var.set_volume(template_)) + + template_ = await cg.templatable(config[CONF_DURATION], args, int) + cg.add(var.set_duration(template_)) + + return var + + +EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_VOLUME): cv.templatable( + cv.float_range() + ), # Any way to represent as proper volume (vs. raw int) + } +) + + +@automation.register_action( + "ezo_pmp.set_calibration_volume", + EzoPMPSetCalibrationVolumeAction, + EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA, +) +async def ezo_pmp_set_calibration_volume_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_VOLUME], args, cg.double) + cg.add(var.set_volume(template_)) + + return var + + +EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_ADDRESS): cv.templatable(cv.int_range(min=1, max=127)), + } +) + + +@automation.register_action( + "ezo_pmp.change_i2c_address", + EzoPMPChangeI2CAddressAction, + EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA, +) +async def ezo_pmp_change_i2c_address_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_ADDRESS], args, cg.double) + cg.add(var.set_address(template_)) + + return var + + +EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(EzoPMP), + cv.Required(CONF_COMMAND): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action( + "ezo_pmp.arbitrary_command", + EzoPMPArbitraryCommandAction, + EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA, +) +async def ezo_pmp_arbitrary_command_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_COMMAND], args, cg.std_string) + cg.add(var.set_command(template_)) + + return var diff --git a/esphome/components/ezo_pmp/binary_sensor.py b/esphome/components/ezo_pmp/binary_sensor.py new file mode 100644 index 0000000000..582eb7af25 --- /dev/null +++ b/esphome/components/ezo_pmp/binary_sensor.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + ENTITY_CATEGORY_NONE, + DEVICE_CLASS_RUNNING, + DEVICE_CLASS_EMPTY, + CONF_ID, +) + +from . import EzoPMP + +DEPENDENCIES = ["ezo_pmp"] + +CONF_PUMP_STATE = "pump_state" +CONF_IS_PAUSED = "is_paused" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(EzoPMP), + cv.Optional(CONF_PUMP_STATE): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_RUNNING, + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_IS_PAUSED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_EMPTY, + entity_category=ENTITY_CATEGORY_NONE, + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if CONF_PUMP_STATE in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE]) + cg.add(parent.set_is_dosing(sens)) + + if CONF_IS_PAUSED in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED]) + cg.add(parent.set_is_paused(sens)) diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp new file mode 100644 index 0000000000..9c96ce2af0 --- /dev/null +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -0,0 +1,542 @@ +#include "ezo_pmp.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ezo_pmp { + +static const char *const TAG = "ezo-pmp"; + +static const uint16_t EZO_PMP_COMMAND_NONE = 0; +static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1; + +static const uint16_t EZO_PMP_COMMAND_FIND = 2; +static const uint16_t EZO_PMP_COMMAND_DOSE_CONTINUOUSLY = 4; +static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME = 8; +static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME = 16; +static const uint16_t EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE = 32; +static const uint16_t EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME = 64; +static const uint16_t EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED = 128; +static const uint16_t EZO_PMP_COMMAND_CLEAR_CALIBRATION = 256; +static const uint16_t EZO_PMP_COMMAND_PAUSE_DOSING = 512; +static const uint16_t EZO_PMP_COMMAND_STOP_DOSING = 1024; +static const uint16_t EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS = 2048; +static const uint16_t EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS = 4096; + +static const uint16_t EZO_PMP_COMMAND_READ_DOSING = 3; +static const uint16_t EZO_PMP_COMMAND_READ_SINGLE_REPORT = 5; +static const uint16_t EZO_PMP_COMMAND_READ_MAX_FLOW_RATE = 9; +static const uint16_t EZO_PMP_COMMAND_READ_PAUSE_STATUS = 17; +static const uint16_t EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED = 33; +static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65; +static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129; +static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257; + +static const std::string DOSING_MODE_NONE = "None"; +static const std::string DOSING_MODE_VOLUME = "Volume"; +static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time"; +static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate"; +static const std::string DOSING_MODE_CONTINUOUS = "Continuous"; + +void EzoPMP::dump_config() { + LOG_I2C_DEVICE(this); + if (this->is_failed()) + ESP_LOGE(TAG, "Communication with EZO-PMP circuit failed!"); + LOG_UPDATE_INTERVAL(this); +} + +void EzoPMP::update() { + if (this->is_waiting_) { + return; + } + + if (this->is_first_read_) { + this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_); + this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); + this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_); + this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, + (bool) this->absolute_total_volume_dosed_); + this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true); + this->is_first_read_ = false; + } + + if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) { + this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true); + + if (this->is_dosing_flag_) { + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); + this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_); + this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, + (bool) this->absolute_total_volume_dosed_); + } + + this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_); + } else { + ESP_LOGV(TAG, "Not Scheduling new Command during update()"); + } +} + +void EzoPMP::loop() { + // If we are not waiting for anything and there is no command to be sent, return + if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) { + return; + } + + // If we are not waiting for anything and there IS a command to be sent, do it. + if (!this->is_waiting_ && this->peek_next_command_() != EZO_PMP_COMMAND_NONE) { + this->send_next_command_(); + } + + // If we are waiting for something but it isn't ready yet, then return + if (this->is_waiting_ && millis() - this->start_time_ < this->wait_time_) { + return; + } + + // We are waiting for something and it should be ready. + this->read_command_result_(); +} + +void EzoPMP::clear_current_command_() { + this->current_command_ = EZO_PMP_COMMAND_NONE; + this->is_waiting_ = false; +} + +void EzoPMP::read_command_result_() { + uint8_t response_buffer[21] = {'\0'}; + + response_buffer[0] = 0; + if (!this->read_bytes_raw(response_buffer, 20)) { + ESP_LOGE(TAG, "read error"); + this->clear_current_command_(); + return; + } + + switch (response_buffer[0]) { + case 254: + return; // keep waiting + case 1: + break; + case 2: + ESP_LOGE(TAG, "device returned a syntax error"); + this->clear_current_command_(); + return; + case 255: + ESP_LOGE(TAG, "device returned no data"); + this->clear_current_command_(); + return; + default: + ESP_LOGE(TAG, "device returned an unknown response: %d", response_buffer[0]); + this->clear_current_command_(); + return; + } + + char first_parameter_buffer[10] = {'\0'}; + char second_parameter_buffer[10] = {'\0'}; + char third_parameter_buffer[10] = {'\0'}; + + first_parameter_buffer[0] = '\0'; + second_parameter_buffer[0] = '\0'; + third_parameter_buffer[0] = '\0'; + + int current_parameter = 1; + + size_t position_in_parameter_buffer = 0; + // some sensors return multiple comma-separated values, terminate string after first one + for (size_t i = 1; i < sizeof(response_buffer) - 1; i++) { + char current_char = response_buffer[i]; + + if (current_char == '\0') { + ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer); + ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer); + ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer); + ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer); + + break; + } + + if (current_char == ',') { + current_parameter++; + position_in_parameter_buffer = 0; + continue; + } + + switch (current_parameter) { + case 1: + first_parameter_buffer[position_in_parameter_buffer] = current_char; + first_parameter_buffer[position_in_parameter_buffer + 1] = '\0'; + break; + case 2: + second_parameter_buffer[position_in_parameter_buffer] = current_char; + second_parameter_buffer[position_in_parameter_buffer + 1] = '\0'; + break; + case 3: + third_parameter_buffer[position_in_parameter_buffer] = current_char; + third_parameter_buffer[position_in_parameter_buffer + 1] = '\0'; + break; + } + + position_in_parameter_buffer++; + } + + auto parsed_first_parameter = parse_number(first_parameter_buffer); + auto parsed_second_parameter = parse_number(second_parameter_buffer); + auto parsed_third_parameter = parse_number(third_parameter_buffer); + + switch (this->current_command_) { + // Read Commands + case EZO_PMP_COMMAND_READ_DOSING: // Page 54 + if (parsed_third_parameter.has_value()) + this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1; + + if (this->is_dosing_) + this->is_dosing_->publish_state(this->is_dosing_flag_); + + if (parsed_second_parameter.has_value() && this->last_volume_requested_) { + this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0)); + } + + if (!this->is_dosing_flag_ && !this->is_paused_flag_) { + // If pump is not paused and not dispensing + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE) + this->dosing_mode_->publish_state(DOSING_MODE_NONE); + } + + break; + + case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53) + if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) { + this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0)); + } + break; + + case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: // Constant Flow Rate (page 57) + if (parsed_second_parameter.has_value() && this->max_flow_rate_) + this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0)); + break; + + case EZO_PMP_COMMAND_READ_PAUSE_STATUS: // Pause (page 61) + if (parsed_second_parameter.has_value()) + this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1; + + if (this->is_paused_) + this->is_paused_->publish_state(this->is_paused_flag_); + break; + + case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64) + if (parsed_second_parameter.has_value() && this->total_volume_dosed_) + this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0)); + break; + + case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64) + if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_) + this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0)); + break; + + case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: // Calibration (page 65) + if (parsed_second_parameter.has_value() && this->calibration_status_) { + if (parsed_second_parameter.value_or(0) == 1) { + this->calibration_status_->publish_state("Fixed Volume"); + } else if (parsed_second_parameter.value_or(0) == 2) { + this->calibration_status_->publish_state("Volume/Time"); + } else if (parsed_second_parameter.value_or(0) == 3) { + this->calibration_status_->publish_state("Fixed Volume & Volume/Time"); + } else { + this->calibration_status_->publish_state("Uncalibrated"); + } + } + break; + + case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: // Pump Voltage (page 67) + if (parsed_second_parameter.has_value() && this->pump_voltage_) + this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0)); + break; + + // Non-Read Commands + + case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55) + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME) + this->dosing_mode_->publish_state(DOSING_MODE_VOLUME); + break; + + case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56) + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME) + this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME); + break; + + case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57) + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE) + this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE); + break; + + case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54) + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS) + this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS); + break; + + case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62) + this->is_paused_flag_ = false; + if (this->is_paused_) + this->is_paused_->publish_state(this->is_paused_flag_); + if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE) + this->dosing_mode_->publish_state(DOSING_MODE_NONE); + break; + + case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: + ESP_LOGI(TAG, "Arbitrary Command Response: %s", (char *) response_buffer); + break; + + case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65) + case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61) + case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65) + case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64) + case EZO_PMP_COMMAND_FIND: // Find (page 52) + // Nothing to do here + break; + + case EZO_PMP_COMMAND_TYPE_READ: + case EZO_PMP_COMMAND_NONE: + default: + ESP_LOGE(TAG, "Unsupported command received: %d", this->current_command_); + return; + } + + this->clear_current_command_(); +} + +void EzoPMP::send_next_command_() { + int wait_time_for_command = 400; // milliseconds + uint8_t command_buffer[21]; + int command_buffer_length = 0; + + this->pop_next_command_(); // this->next_command will be updated. + + switch (this->next_command_) { + // Read Commands + case EZO_PMP_COMMAND_READ_DOSING: // Page 54 + command_buffer_length = sprintf((char *) command_buffer, "D,?"); + break; + + case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53) + command_buffer_length = sprintf((char *) command_buffer, "R"); + break; + + case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: + command_buffer_length = sprintf((char *) command_buffer, "DC,?"); + break; + + case EZO_PMP_COMMAND_READ_PAUSE_STATUS: + command_buffer_length = sprintf((char *) command_buffer, "P,?"); + break; + + case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: + command_buffer_length = sprintf((char *) command_buffer, "TV,?"); + break; + + case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: + command_buffer_length = sprintf((char *) command_buffer, "ATV,?"); + break; + + case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: + command_buffer_length = sprintf((char *) command_buffer, "Cal,?"); + break; + + case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: + command_buffer_length = sprintf((char *) command_buffer, "PV,?"); + break; + + // Non-Read Commands + + case EZO_PMP_COMMAND_FIND: // Find (page 52) + command_buffer_length = sprintf((char *) command_buffer, "Find"); + wait_time_for_command = 60000; // This command will block all updates for a minute + break; + + case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54) + command_buffer_length = sprintf((char *) command_buffer, "D,*"); + break; + + case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64) + command_buffer_length = sprintf((char *) command_buffer, "Clear"); + break; + + case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65) + command_buffer_length = sprintf((char *) command_buffer, "Cal,clear"); + break; + + case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61) + command_buffer_length = sprintf((char *) command_buffer, "P"); + break; + + case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62) + command_buffer_length = sprintf((char *) command_buffer, "X"); + break; + + // Non-Read commands with parameters + + case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55) + command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_); + break; + + case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56) + command_buffer_length = + sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); + break; + + case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57) + command_buffer_length = + sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_); + break; + + case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65) + command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_); + break; + + case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73) + command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_); + break; + + case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command + command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_); + ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer); + break; + + case EZO_PMP_COMMAND_TYPE_READ: + case EZO_PMP_COMMAND_NONE: + default: + ESP_LOGE(TAG, "Unsupported command received: %d", this->next_command_); + return; + } + + // Send command + ESP_LOGV(TAG, "Sending command to device: %s", (char *) command_buffer); + this->write(command_buffer, command_buffer_length); + + this->current_command_ = this->next_command_; + this->next_command_ = EZO_PMP_COMMAND_NONE; + this->is_waiting_ = true; + this->start_time_ = millis(); + this->wait_time_ = wait_time_for_command; +} + +void EzoPMP::pop_next_command_() { + if (this->next_command_queue_length_ <= 0) { + ESP_LOGE(TAG, "Tried to dequeue command from empty queue"); + this->next_command_ = EZO_PMP_COMMAND_NONE; + this->next_command_volume_ = 0; + this->next_command_duration_ = 0; + return; + } + + // Read from Head + this->next_command_ = this->next_command_queue_[this->next_command_queue_head_]; + this->next_command_volume_ = this->next_command_volume_queue_[this->next_command_queue_head_]; + this->next_command_duration_ = this->next_command_duration_queue_[this->next_command_queue_head_]; + + // Move positions + next_command_queue_head_++; + if (next_command_queue_head_ >= 10) { + next_command_queue_head_ = 0; + } + + next_command_queue_length_--; +} + +uint16_t EzoPMP::peek_next_command_() { + if (this->next_command_queue_length_ <= 0) { + return EZO_PMP_COMMAND_NONE; + } + + return this->next_command_queue_[this->next_command_queue_head_]; +} + +void EzoPMP::queue_command_(uint16_t command, double volume, int duration, bool should_schedule) { + if (!should_schedule) { + return; + } + + if (this->next_command_queue_length_ >= 10) { + ESP_LOGE(TAG, "Tried to queue command '%d' but queue is full", command); + return; + } + + this->next_command_queue_[this->next_command_queue_last_] = command; + this->next_command_volume_queue_[this->next_command_queue_last_] = volume; + this->next_command_duration_queue_[this->next_command_queue_last_] = duration; + + ESP_LOGV(TAG, "Queue command '%d' in position '%d'", command, next_command_queue_last_); + + // Move positions + next_command_queue_last_++; + if (next_command_queue_last_ >= 10) { + next_command_queue_last_ = 0; + } + + next_command_queue_length_++; +} + +// Actions + +void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); } + +void EzoPMP::dose_continuously() { + this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); +} + +void EzoPMP::dose_volume(double volume) { + this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); +} + +void EzoPMP::dose_volume_over_time(double volume, int duration) { + this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true); + this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); +} + +void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) { + this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true); + this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_); +} + +void EzoPMP::set_calibration_volume(double volume) { + this->queue_command_(EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME, volume, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true); +} + +void EzoPMP::clear_total_volume_dosed() { + this->queue_command_(EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, true); +} + +void EzoPMP::clear_calibration() { + this->queue_command_(EZO_PMP_COMMAND_CLEAR_CALIBRATION, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true); +} + +void EzoPMP::pause_dosing() { + this->queue_command_(EZO_PMP_COMMAND_PAUSE_DOSING, 0, 0, true); + this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true); +} + +void EzoPMP::stop_dosing() { this->queue_command_(EZO_PMP_COMMAND_STOP_DOSING, 0, 0, true); } + +void EzoPMP::change_i2c_address(int address) { + this->queue_command_(EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS, 0, address, true); +} + +void EzoPMP::exec_arbitrary_command(const std::basic_string &command) { + this->arbitrary_command_ = command.c_str(); + this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true); +} + +} // namespace ezo_pmp +} // namespace esphome diff --git a/esphome/components/ezo_pmp/ezo_pmp.h b/esphome/components/ezo_pmp/ezo_pmp.h new file mode 100644 index 0000000000..b41710cd78 --- /dev/null +++ b/esphome/components/ezo_pmp/ezo_pmp.h @@ -0,0 +1,252 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/i2c/i2c.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 + +namespace esphome { +namespace ezo_pmp { + +class EzoPMP : public PollingComponent, public i2c::I2CDevice { + public: + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void loop() override; + void update() override; + +#ifdef USE_SENSOR + void set_current_volume_dosed(sensor::Sensor *current_volume_dosed) { current_volume_dosed_ = current_volume_dosed; } + void set_total_volume_dosed(sensor::Sensor *total_volume_dosed) { total_volume_dosed_ = total_volume_dosed; } + void set_absolute_total_volume_dosed(sensor::Sensor *absolute_total_volume_dosed) { + absolute_total_volume_dosed_ = absolute_total_volume_dosed; + } + void set_pump_voltage(sensor::Sensor *pump_voltage) { pump_voltage_ = pump_voltage; } + void set_last_volume_requested(sensor::Sensor *last_volume_requested) { + last_volume_requested_ = last_volume_requested; + } + void set_max_flow_rate(sensor::Sensor *max_flow_rate) { max_flow_rate_ = max_flow_rate; } +#endif + +#ifdef USE_BINARY_SENSOR + void set_is_dosing(binary_sensor::BinarySensor *is_dosing) { is_dosing_ = is_dosing; } + void set_is_paused(binary_sensor::BinarySensor *is_paused) { is_paused_ = is_paused; } +#endif + +#ifdef USE_TEXT_SENSOR + void set_dosing_mode(text_sensor::TextSensor *dosing_mode) { dosing_mode_ = dosing_mode; } + void set_calibration_status(text_sensor::TextSensor *calibration_status) { calibration_status_ = calibration_status; } +#endif + + // Actions for EZO-PMP + void find(); + void dose_continuously(); + void dose_volume(double volume); + void dose_volume_over_time(double volume, int duration); + void dose_with_constant_flow_rate(double volume, int duration); + void set_calibration_volume(double volume); + void clear_total_volume_dosed(); + void clear_calibration(); + void pause_dosing(); + void stop_dosing(); + void change_i2c_address(int address); + void exec_arbitrary_command(const std::basic_string &command); + + protected: + uint32_t start_time_ = 0; + uint32_t wait_time_ = 0; + bool is_waiting_ = false; + bool is_first_read_ = true; + + uint16_t next_command_ = 0; + double next_command_volume_ = 0; // might be negative + int next_command_duration_ = 0; + + uint16_t next_command_queue_[10]; + double next_command_volume_queue_[10]; + int next_command_duration_queue_[10]; + int next_command_queue_head_ = 0; + int next_command_queue_last_ = 0; + int next_command_queue_length_ = 0; + + uint16_t current_command_ = 0; + bool is_paused_flag_ = false; + bool is_dosing_flag_ = false; + + const char *arbitrary_command_{nullptr}; + + void send_next_command_(); + void read_command_result_(); + void clear_current_command_(); + void queue_command_(uint16_t command, double volume, int duration, bool should_schedule); + void pop_next_command_(); + uint16_t peek_next_command_(); + +#ifdef USE_SENSOR + sensor::Sensor *current_volume_dosed_{nullptr}; + sensor::Sensor *total_volume_dosed_{nullptr}; + sensor::Sensor *absolute_total_volume_dosed_{nullptr}; + sensor::Sensor *pump_voltage_{nullptr}; + sensor::Sensor *max_flow_rate_{nullptr}; + sensor::Sensor *last_volume_requested_{nullptr}; +#endif + +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *is_dosing_{nullptr}; + binary_sensor::BinarySensor *is_paused_{nullptr}; +#endif + +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *dosing_mode_{nullptr}; + text_sensor::TextSensor *calibration_status_{nullptr}; +#endif +}; + +// Action Templates +template class EzoPMPFindAction : public Action { + public: + EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->find(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPDoseContinuouslyAction : public Action { + public: + EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->dose_continuously(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPDoseVolumeAction : public Action { + public: + EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); } + TEMPLATABLE_VALUE(double, volume) + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPDoseVolumeOverTimeAction : public Action { + public: + EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { + this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...)); + } + TEMPLATABLE_VALUE(double, volume) + TEMPLATABLE_VALUE(int, duration) + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPDoseWithConstantFlowRateAction : public Action { + public: + EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { + this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...)); + } + TEMPLATABLE_VALUE(double, volume) + TEMPLATABLE_VALUE(int, duration) + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPSetCalibrationVolumeAction : public Action { + public: + EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); } + TEMPLATABLE_VALUE(double, volume) + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPClearTotalVolumeDispensedAction : public Action { + public: + EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPClearCalibrationAction : public Action { + public: + EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->clear_calibration(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPPauseDosingAction : public Action { + public: + EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->pause_dosing(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPStopDosingAction : public Action { + public: + EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->stop_dosing(); } + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPChangeI2CAddressAction : public Action { + public: + EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); } + TEMPLATABLE_VALUE(int, address) + + protected: + EzoPMP *ezopmp_; +}; + +template class EzoPMPArbitraryCommandAction : public Action { + public: + EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {} + + void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); } + TEMPLATABLE_VALUE(std::string, command) + + protected: + EzoPMP *ezopmp_; +}; + +} // namespace ezo_pmp +} // namespace esphome diff --git a/esphome/components/ezo_pmp/sensor.py b/esphome/components/ezo_pmp/sensor.py new file mode 100644 index 0000000000..737985f4c5 --- /dev/null +++ b/esphome/components/ezo_pmp/sensor.py @@ -0,0 +1,104 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + CONF_ID, + UNIT_VOLT, +) + +from . import EzoPMP + + +DEPENDENCIES = ["ezo_pmp"] + +CONF_CURRENT_VOLUME_DOSED = "current_volume_dosed" +CONF_TOTAL_VOLUME_DOSED = "total_volume_dosed" +CONF_ABSOLUTE_TOTAL_VOLUME_DOSED = "absolute_total_volume_dosed" +CONF_PUMP_VOLTAGE = "pump_voltage" +CONF_LAST_VOLUME_REQUESTED = "last_volume_requested" +CONF_MAX_FLOW_RATE = "max_flow_rate" + +UNIT_MILILITER = "ml" +UNIT_MILILITERS_PER_MINUTE = "ml/min" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(EzoPMP), + cv.Optional(CONF_CURRENT_VOLUME_DOSED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILILITER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_LAST_VOLUME_REQUESTED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILILITER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_MAX_FLOW_RATE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILILITERS_PER_MINUTE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_TOTAL_VOLUME_DOSED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILILITER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILILITER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_PUMP_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if CONF_CURRENT_VOLUME_DOSED in config: + sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED]) + cg.add(parent.set_current_volume_dosed(sens)) + + if CONF_LAST_VOLUME_REQUESTED in config: + sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED]) + cg.add(parent.set_last_volume_requested(sens)) + + if CONF_TOTAL_VOLUME_DOSED in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED]) + cg.add(parent.set_total_volume_dosed(sens)) + + if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config: + sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED]) + cg.add(parent.set_absolute_total_volume_dosed(sens)) + + if CONF_PUMP_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE]) + cg.add(parent.set_pump_voltage(sens)) + + if CONF_MAX_FLOW_RATE in config: + sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE]) + cg.add(parent.set_max_flow_rate(sens)) diff --git a/esphome/components/ezo_pmp/text_sensor.py b/esphome/components/ezo_pmp/text_sensor.py new file mode 100644 index 0000000000..f8f133e316 --- /dev/null +++ b/esphome/components/ezo_pmp/text_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + ENTITY_CATEGORY_NONE, + ENTITY_CATEGORY_DIAGNOSTIC, + CONF_ID, +) + +from . import EzoPMP + +DEPENDENCIES = ["ezo_pmp"] + +CONF_DOSING_MODE = "dosing_mode" +CONF_CALIBRATION_STATUS = "calibration_status" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(EzoPMP), + cv.Optional(CONF_DOSING_MODE): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_CALIBRATION_STATUS): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if CONF_DOSING_MODE in config: + sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE]) + cg.add(parent.set_dosing_mode(sens)) + + if CONF_CALIBRATION_STATUS in config: + sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS]) + cg.add(parent.set_calibration_status(sens)) diff --git a/tests/test5.yaml b/tests/test5.yaml index 7fc20c452f..131d2f5b7f 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -165,6 +165,12 @@ binary_sensor: + - platform: ezo_pmp + pump_state: + name: "Pump State" + is_paused: + name: "Is Paused" + tlc5947: data_pin: GPIO12 clock_pin: GPIO14 @@ -220,6 +226,10 @@ esp32_improv: authorized_duration: 1min status_indicator: built_in_led +ezo_pmp: + id: hcl_pump + update_interval: 1s + number: - platform: template name: My template number @@ -440,6 +450,20 @@ sensor: 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 + script: - id: automation_test then: @@ -487,3 +511,33 @@ display: lambda: |- it.print("81818181"); +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,? From 138de643a27fc5cd41c0efcdb5432d27626fbb76 Mon Sep 17 00:00:00 2001 From: Maximilian <43999966+DeerMaximum@users.noreply.github.com> Date: Wed, 19 Oct 2022 06:06:22 +0200 Subject: [PATCH 156/838] Add adc128s102 sensor (#3822) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/adc128s102/__init__.py | 23 ++++++++++++ esphome/components/adc128s102/adc128s102.cpp | 35 +++++++++++++++++++ esphome/components/adc128s102/adc128s102.h | 23 ++++++++++++ .../components/adc128s102/sensor/__init__.py | 35 +++++++++++++++++++ .../adc128s102/sensor/adc128s102_sensor.cpp | 24 +++++++++++++ .../adc128s102/sensor/adc128s102_sensor.h | 29 +++++++++++++++ tests/test3.yaml | 8 +++++ 8 files changed, 178 insertions(+) create mode 100644 esphome/components/adc128s102/__init__.py create mode 100644 esphome/components/adc128s102/adc128s102.cpp create mode 100644 esphome/components/adc128s102/adc128s102.h create mode 100644 esphome/components/adc128s102/sensor/__init__.py create mode 100644 esphome/components/adc128s102/sensor/adc128s102_sensor.cpp create mode 100644 esphome/components/adc128s102/sensor/adc128s102_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 86e6cd978b..688c58b2a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -13,6 +13,7 @@ esphome/core/* @esphome/core # Integrations esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core +esphome/components/adc128s102/* @DeerMaximum esphome/components/addressable_light/* @justfalter esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_wave_mini/* @ncareau diff --git a/esphome/components/adc128s102/__init__.py b/esphome/components/adc128s102/__init__.py new file mode 100644 index 0000000000..c4e9d5831e --- /dev/null +++ b/esphome/components/adc128s102/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from esphome.const import CONF_ID + +DEPENDENCIES = ["spi"] +MULTI_CONF = True +CODEOWNERS = ["@DeerMaximum"] + +adc128s102_ns = cg.esphome_ns.namespace("adc128s102") +ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADC128S102), + } +).extend(spi.spi_device_schema(cs_pin_required=True)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/adc128s102/adc128s102.cpp b/esphome/components/adc128s102/adc128s102.cpp new file mode 100644 index 0000000000..c27a354dd3 --- /dev/null +++ b/esphome/components/adc128s102/adc128s102.cpp @@ -0,0 +1,35 @@ +#include "adc128s102.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace adc128s102 { + +static const char *const TAG = "adc128s102"; + +float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; } + +void ADC128S102::setup() { + ESP_LOGCONFIG(TAG, "Setting up adc128s102"); + this->spi_setup(); +} + +void ADC128S102::dump_config() { + ESP_LOGCONFIG(TAG, "ADC128S102:"); + LOG_PIN(" CS Pin:", this->cs_); +} + +uint16_t ADC128S102::read_data(uint8_t channel) { + uint8_t control = channel << 3; + + this->enable(); + uint8_t adc_primary_byte = this->transfer_byte(control); + uint8_t adc_secondary_byte = this->transfer_byte(0x00); + this->disable(); + + uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte; + + return digital_value; +} + +} // namespace adc128s102 +} // namespace esphome diff --git a/esphome/components/adc128s102/adc128s102.h b/esphome/components/adc128s102/adc128s102.h new file mode 100644 index 0000000000..bd6b7f7af1 --- /dev/null +++ b/esphome/components/adc128s102/adc128s102.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace adc128s102 { + +class ADC128S102 : public Component, + public spi::SPIDevice { + public: + ADC128S102() = default; + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + uint16_t read_data(uint8_t channel); +}; + +} // namespace adc128s102 +} // namespace esphome diff --git a/esphome/components/adc128s102/sensor/__init__.py b/esphome/components/adc128s102/sensor/__init__.py new file mode 100644 index 0000000000..3ab6fc4c38 --- /dev/null +++ b/esphome/components/adc128s102/sensor/__init__.py @@ -0,0 +1,35 @@ +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_CHANNEL + +from .. import adc128s102_ns, ADC128S102 + +AUTO_LOAD = ["voltage_sampler"] +DEPENDENCIES = ["adc128s102"] + +ADC128S102Sensor = adc128s102_ns.class_( + "ADC128S102Sensor", + sensor.Sensor, + cg.PollingComponent, + voltage_sampler.VoltageSampler, +) +CONF_ADC128S102_ID = "adc128s102_id" + +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ADC128S102Sensor), + cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_CHANNEL], + ) + await cg.register_parented(var, config[CONF_ADC128S102_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) diff --git a/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp b/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp new file mode 100644 index 0000000000..03ce31d3cb --- /dev/null +++ b/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp @@ -0,0 +1,24 @@ +#include "adc128s102_sensor.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace adc128s102 { + +static const char *const TAG = "adc128s102.sensor"; + +ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {} + +float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void ADC128S102Sensor::dump_config() { + LOG_SENSOR("", "ADC128S102 Sensor", this); + ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_); + LOG_UPDATE_INTERVAL(this); +} + +float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); } +void ADC128S102Sensor::update() { this->publish_state(this->sample()); } + +} // namespace adc128s102 +} // namespace esphome diff --git a/esphome/components/adc128s102/sensor/adc128s102_sensor.h b/esphome/components/adc128s102/sensor/adc128s102_sensor.h new file mode 100644 index 0000000000..234500c2f4 --- /dev/null +++ b/esphome/components/adc128s102/sensor/adc128s102_sensor.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +#include "../adc128s102.h" + +namespace esphome { +namespace adc128s102 { + +class ADC128S102Sensor : public PollingComponent, + public Parented, + public sensor::Sensor, + public voltage_sampler::VoltageSampler { + public: + ADC128S102Sensor(uint8_t channel); + + void update() override; + void dump_config() override; + float get_setup_priority() const override; + float sample() override; + + protected: + uint8_t channel_; +}; +} // namespace adc128s102 +} // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index 8fc66f4918..8150f43e02 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -789,6 +789,11 @@ sensor: voltage: name: Voltage update_interval: 60s + + - platform: adc128s102 + id: adc128s102_channel_0 + channel: 0 + time: - platform: homeassistant @@ -1540,3 +1545,6 @@ cd74hc4067: pin_s1: GPIO13 pin_s2: GPIO14 pin_s3: GPIO15 + +adc128s102: + cs_pin: GPIO12 From d7576f67e85701d2ec35786b1c4c25432b7e31d1 Mon Sep 17 00:00:00 2001 From: hagak <9702199+hagak@users.noreply.github.com> Date: Wed, 19 Oct 2022 03:29:22 -0400 Subject: [PATCH 157/838] Added component Daikin BRC to support ceiling cassette heatpumps (#3743) --- CODEOWNERS | 1 + esphome/components/daikin_brc/__init__.py | 1 + esphome/components/daikin_brc/climate.py | 24 ++ esphome/components/daikin_brc/daikin_brc.cpp | 273 +++++++++++++++++++ esphome/components/daikin_brc/daikin_brc.h | 82 ++++++ tests/test1.yaml | 3 + 6 files changed, 384 insertions(+) create mode 100644 esphome/components/daikin_brc/__init__.py create mode 100644 esphome/components/daikin_brc/climate.py create mode 100644 esphome/components/daikin_brc/daikin_brc.cpp create mode 100644 esphome/components/daikin_brc/daikin_brc.h diff --git a/CODEOWNERS b/CODEOWNERS index 688c58b2a4..78624ecdbe 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -58,6 +58,7 @@ esphome/components/cse7761/* @berfenger esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx esphome/components/dac7678/* @NickB1 +esphome/components/daikin_brc/* @hagak esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter diff --git a/esphome/components/daikin_brc/__init__.py b/esphome/components/daikin_brc/__init__.py new file mode 100644 index 0000000000..69002c015f --- /dev/null +++ b/esphome/components/daikin_brc/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@hagak"] diff --git a/esphome/components/daikin_brc/climate.py b/esphome/components/daikin_brc/climate.py new file mode 100644 index 0000000000..3468b6533c --- /dev/null +++ b/esphome/components/daikin_brc/climate.py @@ -0,0 +1,24 @@ +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"] + +daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc") +DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR) + +CONF_USE_FAHRENHEIT = "use_fahrenheit" + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DaikinBrcClimate), + cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) + cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/daikin_brc/daikin_brc.cpp b/esphome/components/daikin_brc/daikin_brc.cpp new file mode 100644 index 0000000000..6683d70f80 --- /dev/null +++ b/esphome/components/daikin_brc/daikin_brc.cpp @@ -0,0 +1,273 @@ +#include "daikin_brc.h" +#include "esphome/components/remote_base/remote_base.h" + +namespace esphome { +namespace daikin_brc { + +static const char *const TAG = "daikin_brc.climate"; + +void DaikinBrcClimate::control(const climate::ClimateCall &call) { + this->mode_button_ = 0x00; + if (call.get_mode().has_value()) { + // Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte + this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON; + } + ClimateIR::control(call); +} + +void DaikinBrcClimate::transmit_state() { + uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11, + 0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00}; + + remote_state[12] = this->alt_mode_(); + remote_state[13] = this->mode_button_; + remote_state[14] = this->operation_mode_(); + remote_state[17] = this->temperature_(); + remote_state[18] = this->fan_speed_swing_(); + + // Calculate checksum + for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) { + remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i]; + } + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY); + + data->mark(DAIKIN_BRC_HEADER_MARK); + data->space(DAIKIN_BRC_HEADER_SPACE); + for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DAIKIN_BRC_BIT_MARK); + bool bit = remote_state[i] & mask; + data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE); + } + } + data->mark(DAIKIN_BRC_BIT_MARK); + data->space(DAIKIN_BRC_MESSAGE_SPACE); + data->mark(DAIKIN_BRC_HEADER_MARK); + data->space(DAIKIN_BRC_HEADER_SPACE); + + for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DAIKIN_BRC_BIT_MARK); + bool bit = remote_state[i] & mask; + data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE); + } + } + + data->mark(DAIKIN_BRC_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +uint8_t DaikinBrcClimate::alt_mode_() { + uint8_t alt_mode = 0x00; + switch (this->mode) { + case climate::CLIMATE_MODE_DRY: + alt_mode = 0x23; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + alt_mode = 0x63; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + case climate::CLIMATE_MODE_COOL: + case climate::CLIMATE_MODE_HEAT: + default: + alt_mode = 0x73; + break; + } + return alt_mode; +} + +uint8_t DaikinBrcClimate::operation_mode_() { + uint8_t operating_mode = DAIKIN_BRC_MODE_ON; + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + operating_mode |= DAIKIN_BRC_MODE_COOL; + break; + case climate::CLIMATE_MODE_DRY: + operating_mode |= DAIKIN_BRC_MODE_DRY; + break; + case climate::CLIMATE_MODE_HEAT: + operating_mode |= DAIKIN_BRC_MODE_HEAT; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + operating_mode |= DAIKIN_BRC_MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + operating_mode |= DAIKIN_BRC_MODE_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + operating_mode = DAIKIN_BRC_MODE_OFF; + break; + } + + return operating_mode; +} + +uint8_t DaikinBrcClimate::fan_speed_swing_() { + uint16_t fan_speed; + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + fan_speed = DAIKIN_BRC_FAN_1; + break; + case climate::CLIMATE_FAN_MEDIUM: + fan_speed = DAIKIN_BRC_FAN_2; + break; + case climate::CLIMATE_FAN_HIGH: + fan_speed = DAIKIN_BRC_FAN_3; + break; + default: + fan_speed = DAIKIN_BRC_FAN_1; + } + + // If swing is enabled switch first 4 bits to 1111 + switch (this->swing_mode) { + case climate::CLIMATE_SWING_BOTH: + fan_speed |= DAIKIN_BRC_IR_SWING_ON; + break; + default: + fan_speed |= DAIKIN_BRC_IR_SWING_OFF; + break; + } + return fan_speed; +} + +uint8_t DaikinBrcClimate::temperature_() { + // Force special temperatures depending on the mode + switch (this->mode) { + case climate::CLIMATE_MODE_FAN_ONLY: + case climate::CLIMATE_MODE_DRY: + if (this->fahrenheit_) { + return DAIKIN_BRC_IR_DRY_FAN_TEMP_F; + } + return DAIKIN_BRC_IR_DRY_FAN_TEMP_C; + case climate::CLIMATE_MODE_HEAT_COOL: + default: + uint8_t temperature; + // Temperature in remote is in F + if (this->fahrenheit_) { + temperature = (uint8_t) roundf( + clamp(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F)); + } else { + temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1; + } + return temperature; + } +} + +bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) { + uint8_t checksum = 0; + for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) { + checksum += frame[i]; + } + if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) { + ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum); + return false; + } + + uint8_t mode = frame[7]; + if (mode & DAIKIN_BRC_MODE_ON) { + switch (mode & 0xF0) { + case DAIKIN_BRC_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case DAIKIN_BRC_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case DAIKIN_BRC_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case DAIKIN_BRC_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case DAIKIN_BRC_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + + uint8_t temperature = frame[10]; + float temperature_c; + if (this->fahrenheit_) { + temperature_c = clamp(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C); + } else { + temperature_c = (temperature >> 1) + 9; + } + + this->target_temperature = temperature_c; + + uint8_t fan_mode = frame[11]; + uint8_t swing_mode = frame[11]; + switch (swing_mode & 0xF) { + case DAIKIN_BRC_IR_SWING_ON: + this->swing_mode = climate::CLIMATE_SWING_BOTH; + break; + case DAIKIN_BRC_IR_SWING_OFF: + this->swing_mode = climate::CLIMATE_SWING_OFF; + break; + } + + switch (fan_mode & 0xF0) { + case DAIKIN_BRC_FAN_1: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case DAIKIN_BRC_FAN_2: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case DAIKIN_BRC_FAN_3: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + } + this->publish_state(); + return true; +} + +bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {}; + if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) { + return false; + } + for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) { + return false; + } + } + state_frame[pos] = byte; + if (pos == 0) { + // frame header + if (byte != 0x11) + return false; + } else if (pos == 1) { + // frame header + if (byte != 0xDA) + return false; + } else if (pos == 2) { + // frame header + if (byte != 0x17) + return false; + } else if (pos == 3) { + // frame header + if (byte != 0x18) + return false; + } else if (pos == 4) { + // frame type + if (byte != 0x00) + return false; + } + } + return this->parse_state_frame_(state_frame); +} + +} // namespace daikin_brc +} // namespace esphome diff --git a/esphome/components/daikin_brc/daikin_brc.h b/esphome/components/daikin_brc/daikin_brc.h new file mode 100644 index 0000000000..bdc6384809 --- /dev/null +++ b/esphome/components/daikin_brc/daikin_brc.h @@ -0,0 +1,82 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace daikin_brc { + +// Values for Daikin BRC4CXXX IR Controllers +// Temperature +const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60; // fahrenheit +const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90; // fahrenheit +const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8; // fahrenheit +const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8; // fahrenheit + +// Modes +const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30; +const uint8_t DAIKIN_BRC_MODE_COOL = 0x20; +const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10; +const uint8_t DAIKIN_BRC_MODE_DRY = 0x70; +const uint8_t DAIKIN_BRC_MODE_FAN = 0x00; +const uint8_t DAIKIN_BRC_MODE_OFF = 0x00; +const uint8_t DAIKIN_BRC_MODE_ON = 0x01; + +// Fan Speed +const uint8_t DAIKIN_BRC_FAN_1 = 0x10; +const uint8_t DAIKIN_BRC_FAN_2 = 0x30; +const uint8_t DAIKIN_BRC_FAN_3 = 0x50; +const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0; + +// IR Transmission +const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000; +const uint32_t DAIKIN_BRC_HEADER_MARK = 5070; +const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140; +const uint32_t DAIKIN_BRC_BIT_MARK = 370; +const uint32_t DAIKIN_BRC_ONE_SPACE = 1780; +const uint32_t DAIKIN_BRC_ZERO_SPACE = 710; +const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410; + +const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72; // Dry/Fan mode is always 17 Celsius. +const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2; // Dry/Fan mode is always 17 Celsius. +const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5; +const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6; +const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4; // This is set after a mode action + +// State Frame size +const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15; +// Preamble size +const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7; +// Transmit Frame size - includes a preamble +const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE; + +class DaikinBrcClimate : public climate_ir::ClimateIR { + public: + DaikinBrcClimate() + : climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true, + {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {} + + /// Set use of Fahrenheit units + void set_fahrenheit(bool value) { + this->fahrenheit_ = value; + this->temperature_step_ = value ? 0.5f : 1.0f; + } + + protected: + uint8_t mode_button_ = 0x00; + // Capture if the MODE was changed + void control(const climate::ClimateCall &call) override; + // Transmit via IR the state of this climate controller. + void transmit_state() override; + uint8_t alt_mode_(); + uint8_t operation_mode_(); + uint8_t fan_speed_swing_(); + uint8_t temperature_(); + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(const uint8_t frame[]); + bool fahrenheit_{false}; +}; + +} // namespace daikin_brc +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 82cdac4623..b286c37deb 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1862,6 +1862,9 @@ climate: name: Fujitsu General Climate - platform: daikin name: Daikin Climate + - platform: daikin_brc + name: Daikin BRC Climate + use_fahrenheit: true - platform: delonghi name: Delonghi Climate - platform: yashima From a21c3e8e2db3dddd55c9a56ee348a6a76a6daeb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:49:11 +1300 Subject: [PATCH 158/838] Bump platformio from 6.0.2 to 6.1.4 (#3711) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index f4652bc513..05b518ca74 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -46,7 +46,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.37.1 \ - platformio==6.0.2 \ + platformio==6.1.4 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_platformio_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 00e6e0ba67..af7ac0cab4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==4.2 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==6.0.2 # When updating platformio, also update Dockerfile +platformio==6.1.4 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 esphome-dashboard==20221007.0 From e87edcc77a4e4c83624cad270eebdd19b30669d5 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Thu, 20 Oct 2022 05:39:34 +0200 Subject: [PATCH 159/838] Add API interface to request a complete device config as JSON. (#3911) Co-authored-by: Paulus Schoutsen --- esphome/dashboard/dashboard.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 1a51f3056f..0367101023 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -29,6 +29,7 @@ import tornado.web import tornado.websocket from esphome import const, platformio_api, util, yaml_util +from esphome.core import EsphomeError from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -927,6 +928,27 @@ class SecretKeysRequestHandler(BaseHandler): self.write(json.dumps(secret_keys)) +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 + + try: + content = yaml_util.load_yaml(filename, clear_secrets=False) + json_content = json.dumps( + content, default=lambda o: {"__type": str(type(o)), "repr": repr(o)} + ) + self.set_header("content-type", "application/json") + self.write(json_content) + except EsphomeError as err: + _LOGGER.warning("Error translating file %s to JSON: %s", filename, err) + self.send_error(500) + + def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -1031,6 +1053,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (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), ], From 6153bcc6ad818c8ece3fc149614d7aa391bd483f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:50:39 +1300 Subject: [PATCH 160/838] Initial Support for RP2040 platform (#3284) Co-authored-by: Paulus Schoutsen --- .github/workflows/ci.yml | 4 + CODEOWNERS | 2 + esphome/__main__.py | 72 ++++--- esphome/components/logger/__init__.py | 17 +- esphome/components/logger/logger.cpp | 6 +- esphome/components/logger/logger.h | 12 +- esphome/components/md5/md5.cpp | 15 +- esphome/components/md5/md5.h | 5 + esphome/components/mdns/mdns_component.cpp | 3 + esphome/components/mdns/mdns_rp2040.cpp | 40 ++++ esphome/components/ota/__init__.py | 5 +- .../ota/ota_backend_arduino_rp2040.cpp | 55 +++++ .../ota/ota_backend_arduino_rp2040.h | 27 +++ esphome/components/ota/ota_component.cpp | 4 + esphome/components/ota/ota_component.h | 1 + esphome/components/rp2040/__init__.py | 157 ++++++++++++++ esphome/components/rp2040/boards.py | 7 + esphome/components/rp2040/const.py | 6 + esphome/components/rp2040/core.cpp | 32 +++ esphome/components/rp2040/core.h | 14 ++ esphome/components/rp2040/gpio.cpp | 103 +++++++++ esphome/components/rp2040/gpio.h | 38 ++++ esphome/components/rp2040/gpio.py | 91 ++++++++ esphome/components/rp2040/preferences.cpp | 49 +++++ esphome/components/rp2040/preferences.h | 13 ++ esphome/components/rp2040_pwm/__init__.py | 0 esphome/components/rp2040_pwm/output.py | 55 +++++ esphome/components/rp2040_pwm/rp2040_pwm.cpp | 45 ++++ esphome/components/rp2040_pwm/rp2040_pwm.h | 57 +++++ esphome/components/socket/__init__.py | 1 + esphome/components/socket/headers.h | 2 +- esphome/components/spi/__init__.py | 4 +- esphome/components/spi/spi.h | 20 ++ esphome/components/wifi/__init__.py | 4 +- esphome/components/wifi/wifi_component.cpp | 2 + esphome/components/wifi/wifi_component.h | 17 ++ .../components/wifi/wifi_component_pico_w.cpp | 204 ++++++++++++++++++ esphome/config_validation.py | 4 + esphome/const.py | 3 +- esphome/core/__init__.py | 4 + esphome/core/helpers.cpp | 8 + esphome/dashboard/dashboard.py | 28 ++- esphome/storage_json.py | 6 +- esphome/wizard.py | 20 +- platformio.ini | 25 +++ script/ci-custom.py | 1 + tests/README.md | 1 + tests/dummy_main.cpp | 3 +- tests/test6.yaml | 39 ++++ 49 files changed, 1270 insertions(+), 61 deletions(-) create mode 100644 esphome/components/mdns/mdns_rp2040.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_rp2040.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_rp2040.h create mode 100644 esphome/components/rp2040/__init__.py create mode 100644 esphome/components/rp2040/boards.py create mode 100644 esphome/components/rp2040/const.py create mode 100644 esphome/components/rp2040/core.cpp create mode 100644 esphome/components/rp2040/core.h create mode 100644 esphome/components/rp2040/gpio.cpp create mode 100644 esphome/components/rp2040/gpio.h create mode 100644 esphome/components/rp2040/gpio.py create mode 100644 esphome/components/rp2040/preferences.cpp create mode 100644 esphome/components/rp2040/preferences.h create mode 100644 esphome/components/rp2040_pwm/__init__.py create mode 100644 esphome/components/rp2040_pwm/output.py create mode 100644 esphome/components/rp2040_pwm/rp2040_pwm.cpp create mode 100644 esphome/components/rp2040_pwm/rp2040_pwm.h create mode 100644 esphome/components/wifi/wifi_component_pico_w.cpp create mode 100644 tests/test6.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 671b74d118..8e09fef069 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,10 @@ jobs: file: tests/test5.yaml name: Test tests/test5.yaml pio_cache_key: test5 + - id: test + file: tests/test6.yaml + name: Test tests/test6.yaml + pio_cache_key: test6 - id: pytest name: Run pytest - id: clang-format diff --git a/CODEOWNERS b/CODEOWNERS index 78624ecdbe..ac793e19af 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -184,6 +184,8 @@ esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz +esphome/components/rp2040/* @jesserockz +esphome/components/rp2040_pwm/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/scd4x/* @martgras @sjtrny diff --git a/esphome/__main__.py b/esphome/__main__.py index c336336f18..cf2d161d04 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -4,6 +4,7 @@ import logging import os import re import sys +import time from datetime import datetime from esphome import const, writer, yaml_util @@ -22,6 +23,9 @@ from esphome.const import ( CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, CONF_SUBSTITUTIONS, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine @@ -101,11 +105,11 @@ def run_miniterm(config, port): if CONF_LOGGER not in config: _LOGGER.info("Logger is not enabled. Not starting UART logs.") - return + return 1 baud_rate = config["logger"][CONF_BAUD_RATE] if baud_rate == 0: _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") - return + return 1 _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) backtrace_state = False @@ -119,25 +123,34 @@ def run_miniterm(config, port): ser.dtr = False ser.rts = False - with ser: - while True: - try: - raw = ser.readline() - except serial.SerialException: - _LOGGER.error("Serial port closed!") - return - line = ( - raw.replace(b"\r", b"") - .replace(b"\n", b"") - .decode("utf8", "backslashreplace") - ) - time = datetime.now().time().strftime("[%H:%M:%S]") - message = time + line - safe_print(message) + tries = 0 + while tries < 5: + try: + with ser: + while True: + try: + raw = ser.readline() + except serial.SerialException: + _LOGGER.error("Serial port closed!") + return 0 + line = ( + raw.replace(b"\r", b"") + .replace(b"\n", b"") + .decode("utf8", "backslashreplace") + ) + time_str = datetime.now().time().strftime("[%H:%M:%S]") + message = time_str + line + safe_print(message) - backtrace_state = platformio_api.process_stacktrace( - config, line, backtrace_state=backtrace_state - ) + backtrace_state = platformio_api.process_stacktrace( + config, line, backtrace_state=backtrace_state + ) + except serial.SerialException: + tries += 1 + time.sleep(1) + if tries >= 5: + _LOGGER.error("Could not connect to serial port %s", port) + return 1 def wrap_to_code(name, comp): @@ -258,9 +271,21 @@ def upload_using_esptool(config, port): def upload_program(config, args, host): - # if upload is to a serial port use platformio, otherwise assume ota if get_port_type(host) == "SERIAL": - return upload_using_esptool(config, host) + if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): + return upload_using_esptool(config, host) + + if CORE.target_platform in (PLATFORM_RP2040): + from esphome import platformio_api + + upload_args = ["-t", "upload"] + if args.device is not None: + upload_args += ["--upload-port", args.device] + return platformio_api.run_platformio_cli_run( + config, CORE.verbose, *upload_args + ) + + return 1 # Unknown target platform from esphome import espota2 @@ -280,8 +305,7 @@ def show_logs(config, args, port): if "logger" not in config: raise EsphomeError("Logger is not configured!") if get_port_type(port) == "SERIAL": - run_miniterm(config, port) - return 0 + return run_miniterm(config, port) if get_port_type(port) == "NETWORK" and "api" in config: from esphome.components.api.client import run_logs diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 43d87bcefe..333e109379 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] +UART_SELECTION_RP2040 = [UART0, UART1] + HARDWARE_UART_TO_UART_SELECTION = { UART0: logger_ns.UART_SELECTION_UART0, UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP, @@ -106,6 +108,8 @@ def uart_selection(value): return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) + if CORE.is_rp2040: + return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value) raise NotImplementedError @@ -158,12 +162,13 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(90.0) async def to_code(config): baud_rate = config[CONF_BAUD_RATE] - rhs = Logger.new( - baud_rate, - config[CONF_TX_BUFFER_SIZE], - HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]], - ) - log = cg.Pvariable(config[CONF_ID], rhs) + log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE]) + if CONF_HARDWARE_UART in config: + cg.add( + log.set_uart_selection( + HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]] + ) + ) cg.add(log.pre_setup()) for tag, level in config[CONF_LOGS].items(): diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index c97677c887..271f99ba58 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->log_callback_.call(level, tag, msg); } -Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) - : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) { +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 } @@ -270,6 +269,9 @@ const char *const UART_SELECTIONS[] = { #endif // USE_ESP32 #ifdef USE_ESP8266 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; +#endif +#ifdef USE_RP2040 +const char *const UART_SELECTIONS[] = {"UART0", "UART1"}; #endif // USE_ESP8266 void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 307c13a91f..0251faf987 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -7,8 +7,15 @@ #include #ifdef USE_ARDUINO +#if defined(USE_ESP8266) || defined(USE_ESP32) #include -#endif +#endif // USE_ESP8266 || USE_ESP32 +#ifdef USE_RP2040 +#include +#include +#endif // USE_RP2040 +#endif // USE_ARDUINO + #ifdef USE_ESP_IDF #include #endif @@ -44,7 +51,7 @@ enum UARTSelection { class Logger : public Component { public: - explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart); + explicit Logger(uint32_t baud_rate, size_t tx_buffer_size); /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); @@ -56,6 +63,7 @@ class Logger : public Component { uart_port_t get_uart_num() const { return uart_num_; } #endif + void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. UARTSelection get_uart() const; diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 8d4bac1fd2..620b6749f3 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace md5 { -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_RP2040) void MD5Digest::init() { memset(this->digest_, 0, 16); MD5Init(&this->ctx_); @@ -15,7 +15,7 @@ void MD5Digest::init() { void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_RP2040 #ifdef USE_ESP_IDF void MD5Digest::init() { @@ -28,6 +28,17 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this- void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); } #endif // USE_ESP_IDF +#ifdef USE_RP2040 +void MD5Digest::init() { + memset(this->digest_, 0, 16); + br_md5_init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); } +#endif // USE_RP2040 + void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } void MD5Digest::get_hex(char *output) { diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index a9628c9242..738a312267 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -17,6 +17,11 @@ #define MD5_CTX_TYPE md5_context_t #endif +#ifdef USE_RP2040 +#include +#define MD5_CTX_TYPE br_md5_context +#endif + namespace esphome { namespace md5 { diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index e1fcf320ed..a3f38322b3 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() { #endif #ifdef USE_ESP32 platform = "ESP32"; +#endif +#ifdef USE_RP2040 + platform = "RP2040"; #endif if (platform != nullptr) { service.txt_records.push_back({"platform", platform}); diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp new file mode 100644 index 0000000000..b153ececcc --- /dev/null +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -0,0 +1,40 @@ +#ifdef USE_RP2040 + +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include "mdns_component.h" + +namespace esphome { +namespace mdns { + +void MDNSComponent::setup() { + this->compile_records_(); + + network::IPAddress addr = network::get_ip_address(); + // MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + + // for (const auto &service : this->services_) { + // // Strip the leading underscore from the proto and service_type. While it is + // // part of the wire protocol to have an underscore, and for example ESP-IDF + // // expects the underscore to be there, the ESP8266 implementation always adds + // // the underscore itself. + // auto *proto = service.proto.c_str(); + // while (*proto == '_') { + // proto++; + // } + // auto *service_type = service.service_type.c_str(); + // while (*service_type == '_') { + // service_type++; + // } + // MDNS.addService(service_type, proto, service.port); + // for (const auto &record : service.txt_records) { + // MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); + // } + // } +} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 1bc4012ce2..32ea1fd363 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, - cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, + cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port, cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" @@ -94,6 +94,9 @@ async def to_code(config): if CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) + if CORE.is_rp2040 and CORE.using_arduino: + cg.add_library("Updater", None) + use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp new file mode 100644 index 0000000000..5a46b8f07e --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -0,0 +1,55 @@ +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_RP2040 + +#include "esphome/components/rp2040/preferences.h" +#include "ota_backend.h" +#include "ota_backend_arduino_rp2040.h" +#include "ota_component.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoRP2040OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoRP2040OTABackend::abort() { Update.end(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.h b/esphome/components/ota/ota_backend_arduino_rp2040.h new file mode 100644 index 0000000000..5aa2ec9435 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.h @@ -0,0 +1,27 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_RP2040 + +#include "esphome/core/macros.h" +#include "ota_backend.h" +#include "ota_component.h" + +namespace esphome { +namespace ota { + +class ArduinoRP2040OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index a02d64cd08..1f1ecd9867 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -2,6 +2,7 @@ #include "ota_backend.h" #include "ota_backend_arduino_esp32.h" #include "ota_backend_arduino_esp8266.h" +#include "ota_backend_arduino_rp2040.h" #include "ota_backend_esp_idf.h" #include "esphome/core/log.h" @@ -35,6 +36,9 @@ std::unique_ptr make_ota_backend() { #ifdef USE_ESP_IDF return make_unique(); #endif // USE_ESP_IDF +#ifdef USE_RP2040 + return make_unique(); +#endif // USE_RP2040 } OTAComponent::OTAComponent() { global_ota_component = this; } diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 9a1c92f727..50d095be6c 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -33,6 +33,7 @@ enum OTAResponseTypes { 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, }; diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py new file mode 100644 index 0000000000..d526228f41 --- /dev/null +++ b/esphome/components/rp2040/__init__.py @@ -0,0 +1,157 @@ +import logging + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_BOARD, + CONF_FRAMEWORK, + CONF_SOURCE, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE, coroutine_with_priority + +from .const import KEY_BOARD, KEY_RP2040, rp2040_ns + +# force import gpio to register pin schema +from .gpio import rp2040_pin_to_code # noqa + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@jesserockz"] +AUTO_LOAD = [] + + +def set_core_data(config): + CORE.data[KEY_RP2040] = {} + CORE.data[KEY_CORE][KEY_TARGET_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] + ) + CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD] + + return config + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/earlephilhower/arduino-pico/releases) version to + # a PIO earlephilhower/framework-arduinopico value + # List of package versions: https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico + return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# 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(2, 4, 0) + +# 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, 7, 0) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": (cv.Version(2, 4, 0), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(2, 4, 0), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), + } + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] + else: + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) + + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) + ) + + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected Arduino framework version is not the recommended one." + ) + + return value + + +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/raspberrypi @ {value}" + except cv.Invalid: + return value + + +CONF_PLATFORM_VERSION = "platform_version" + +ARDUINO_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_PLATFORM_VERSION): _parse_platform_version, + } + ), + _arduino_check_versions, +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, + } + ), + set_core_data, +) + + +@coroutine_with_priority(1000) +async def to_code(config): + cg.add(rp2040_ns.setup_preferences()) + + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_RP2040") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + cg.add_define("ESPHOME_VARIANT", "RP2040") + + conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_RP2040_FRAMEWORK_ARDUINO") + # cg.add_build_flag("-DPICO_BOARD=pico_w") + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + cg.add_platformio_option( + "platform_packages", + [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"], + ) + + cg.add_platformio_option("board_build.core", "earlephilhower") + cg.add_platformio_option("board_build.filesystem_size", "0.5m") + + ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"), + ) diff --git a/esphome/components/rp2040/boards.py b/esphome/components/rp2040/boards.py new file mode 100644 index 0000000000..be3d05369a --- /dev/null +++ b/esphome/components/rp2040/boards.py @@ -0,0 +1,7 @@ +RP2040_BASE_PINS = {} + +RP2040_BOARD_PINS = { + "pico": {"LED": 25}, + "rpipico": "pico", + "rpipicow": {}, +} diff --git a/esphome/components/rp2040/const.py b/esphome/components/rp2040/const.py new file mode 100644 index 0000000000..e09016ca31 --- /dev/null +++ b/esphome/components/rp2040/const.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +KEY_BOARD = "board" +KEY_RP2040 = "rp2040" + +rp2040_ns = cg.esphome_ns.namespace("rp2040") diff --git a/esphome/components/rp2040/core.cpp b/esphome/components/rp2040/core.cpp new file mode 100644 index 0000000000..2a1ce5a4d3 --- /dev/null +++ b/esphome/components/rp2040/core.cpp @@ -0,0 +1,32 @@ +#ifdef USE_RP2040 + +#include "core.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" + +#include "hardware/watchdog.h" + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::yield(); } +uint32_t IRAM_ATTR HOT millis() { return ::millis(); } +void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } +uint32_t IRAM_ATTR HOT micros() { return ::micros(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } +void arch_restart() { + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void arch_init() { watchdog_enable(0x7fffff, false); } +void IRAM_ATTR HOT arch_feed_wdt() { watchdog_update(); } + +uint8_t progmem_read_byte(const uint8_t *addr) { + return pgm_read_byte(addr); // NOLINT +} +uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); } +uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); } + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/core.h b/esphome/components/rp2040/core.h new file mode 100644 index 0000000000..92fc4f824e --- /dev/null +++ b/esphome/components/rp2040/core.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include + +extern "C" unsigned long ulMainGetRunTimeCounterValue(); + +namespace esphome { +namespace rp2040 {} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.cpp b/esphome/components/rp2040/gpio.cpp new file mode 100644 index 0000000000..e32b93b5c2 --- /dev/null +++ b/esphome/components/rp2040/gpio.cpp @@ -0,0 +1,103 @@ +#ifdef USE_RP2040 + +#include "gpio.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rp2040 { + +static const char *const TAG = "rp2040"; + +static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + // } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + // return OpenDrain; + } else { + return 0; + } +} + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin RP2040GPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void RP2040GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + PinStatus arduino_mode = LOW; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? HIGH : LOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? LOW : HIGH; + break; + } + + attachInterrupt(pin_, func, arduino_mode, arg); +} +void RP2040GPIOPin::pin_mode(gpio::Flags flags) { + pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT +} + +std::string RP2040GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool RP2040GPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void RP2040GPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } + +} // namespace rp2040 + +using namespace rp2040; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + // TODO: implement + // auto *arg = reinterpret_cast(arg_); + // GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); +} +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT +} + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.h b/esphome/components/rp2040/gpio.h new file mode 100644 index 0000000000..ef9500d5dd --- /dev/null +++ b/esphome/components/rp2040/gpio.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include "esphome/core/hal.h" + +namespace esphome { +namespace rp2040 { + +class RP2040GPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.py b/esphome/components/rp2040/gpio.py new file mode 100644 index 0000000000..4bc6dbee1c --- /dev/null +++ b/esphome/components/rp2040/gpio.py @@ -0,0 +1,91 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome.core import CORE +from esphome import pins + +from . import boards +from .const import KEY_BOARD, KEY_RP2040, rp2040_ns + +RP2040GPIOPin = rp2040_ns.class_("RP2040GPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_RP2040][KEY_BOARD] + board_pins = boards.RP2040_BOARD_PINS.get(board, {}) + + while isinstance(board_pins, str): + board_pins = boards.RP2040_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.RP2040_BASE_PINS: + return boards.RP2040_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 29: + raise cv.Invalid(f"RP2040: Invalid pin number: {value}") + return value + + +CONF_ANALOG = "analog" + +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.PIN_SCHEMA_REGISTRY.register("rp2040", RP2040_PIN_SCHEMA) +async def rp2040_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + 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/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp new file mode 100644 index 0000000000..58ec57df2a --- /dev/null +++ b/esphome/components/rp2040/preferences.cpp @@ -0,0 +1,49 @@ +#ifdef USE_RP2040 + +#include "preferences.h" + +#include +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace rp2040 { + +static const char *const TAG = "rp2040.preferences"; + +class RP2040PreferenceBackend : public ESPPreferenceBackend { + public: + bool save(const uint8_t *data, size_t len) override { return true; } + bool load(uint8_t *data, size_t len) override { return false; } +}; + +class RP2040Preferences : public ESPPreferences { + public: + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + return ESPPreferenceObject(pref); + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + return ESPPreferenceObject(pref); + } + + bool sync() override { return true; } + + bool reset() override { return true; } +}; + +void setup_preferences() { + auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + global_preferences = prefs; +} + +} // namespace rp2040 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.h b/esphome/components/rp2040/preferences.h new file mode 100644 index 0000000000..3c740a41c1 --- /dev/null +++ b/esphome/components/rp2040/preferences.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef USE_RP2040 + +namespace esphome { +namespace rp2040 { + +void setup_preferences(); + +} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040_pwm/__init__.py b/esphome/components/rp2040_pwm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/rp2040_pwm/output.py b/esphome/components/rp2040_pwm/output.py new file mode 100644 index 0000000000..8f2972d4a0 --- /dev/null +++ b/esphome/components/rp2040_pwm/output.py @@ -0,0 +1,55 @@ +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, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["rp2040"] + + +rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm") +RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component) +SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action) +validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(RP2040PWM), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await output.register_output(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) + + cg.add(var.set_frequency(config[CONF_FREQUENCY])) + + +@automation.register_action( + "output.rp2040_pwm.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(RP2040PWM), + cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), + } + ), +) +async def rp2040_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/rp2040_pwm/rp2040_pwm.cpp b/esphome/components/rp2040_pwm/rp2040_pwm.cpp new file mode 100644 index 0000000000..bf2a446edf --- /dev/null +++ b/esphome/components/rp2040_pwm/rp2040_pwm.cpp @@ -0,0 +1,45 @@ +#ifdef USE_RP2040 + +#include "rp2040_pwm.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/macros.h" + +#include + +namespace esphome { +namespace rp2040_pwm { + +static const char *const TAG = "rp2040_pwm"; + +void RP2040PWM::setup() { + ESP_LOGCONFIG(TAG, "Setting up RP2040 PWM Output..."); + this->pin_->setup(); + this->pwm_ = new mbed::PwmOut((PinName) this->pin_->get_pin()); + this->turn_off(); +} +void RP2040PWM::dump_config() { + ESP_LOGCONFIG(TAG, "RP2040 PWM:"); + LOG_PIN(" Pin: ", this->pin_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); + LOG_FLOAT_OUTPUT(this); +} +void HOT RP2040PWM::write_state(float state) { + this->last_output_ = state; + + // Also check pin inversion + if (this->pin_->is_inverted()) { + state = 1.0f - state; + } + + auto total_time_us = static_cast(roundf(1e6f / this->frequency_)); + + this->pwm_->period_us(total_time_us); + this->pwm_->write(state); +} + +} // namespace rp2040_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.h b/esphome/components/rp2040_pwm/rp2040_pwm.h new file mode 100644 index 0000000000..e348f131c2 --- /dev/null +++ b/esphome/components/rp2040_pwm/rp2040_pwm.h @@ -0,0 +1,57 @@ +#pragma once + +#ifdef USE_RP2040 + +#include "esphome/components/output/float_output.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +#include "drivers/PwmOut.h" + +namespace esphome { +namespace rp2040_pwm { + +class RP2040PWM : public output::FloatOutput, public Component { + public: + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } + void set_frequency(float frequency) { this->frequency_ = frequency; } + /// Dynamically update frequency + void update_frequency(float frequency) override { + this->set_frequency(frequency); + this->write_state(this->last_output_); + } + + /// Initialize pin + void setup() override; + void dump_config() override; + /// HARDWARE setup_priority + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + void write_state(float state) override; + + InternalGPIOPin *pin_; + mbed::PwmOut *pwm_; + float frequency_{1000.0}; + /// Cache last output level for dynamic frequency updating + float last_output_{0.0}; +}; + +template class SetFrequencyAction : public Action { + public: + SetFrequencyAction(RP2040PWM *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, frequency); + + void play(Ts... x) { + float freq = this->frequency_.value(x...); + this->parent_->update_frequency(freq); + } + + RP2040PWM *parent_; +}; + +} // namespace rp2040_pwm +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 8e9502be6d..81203fdc31 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -13,6 +13,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_IMPLEMENTATION, esp8266=IMPLEMENTATION_LWIP_TCP, esp32=IMPLEMENTATION_BSD_SOCKETS, + rp2040=IMPLEMENTATION_LWIP_TCP, ): cv.one_of( IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" ), diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index a383c0071d..1e79c8a1ab 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -91,7 +91,7 @@ struct iovec { size_t iov_len; }; -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_RP2040) // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY #undef INADDR_ANY diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index c917fe1ad8..eeaf37985b 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -46,9 +46,7 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) - if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("SPI", None) - if CORE.is_esp8266: + if CORE.using_arduino: cg.add_library("SPI", None) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 7f0b0f481a..bc183be54b 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -105,7 +105,11 @@ class SPIComponent : public Component { 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; } #endif // USE_SPI_ARDUINO_BACKEND @@ -116,7 +120,11 @@ class SPIComponent : public Component { 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; } #endif // USE_SPI_ARDUINO_BACKEND @@ -130,7 +138,11 @@ class SPIComponent : public Component { #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; } @@ -145,7 +157,11 @@ class SPIComponent : public Component { #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 @@ -178,7 +194,11 @@ class SPIComponent : public Component { 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; } diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 93ea6b92a4..f5684f06f7 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -267,7 +267,7 @@ CONFIG_SCHEMA = cv.All( CONF_REBOOT_TIMEOUT, default="15min" ): cv.positive_time_period_milliseconds, cv.SplitDefault( - CONF_POWER_SAVE_MODE, esp8266="none", esp32="light" + CONF_POWER_SAVE_MODE, esp8266="none", esp32="light", rp2040="light" ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, @@ -386,6 +386,8 @@ async def to_code(config): cg.add_library("ESP8266WiFi", None) elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFi", None) + elif CORE.is_rp2040: + cg.add_library("WiFi", None) if CORE.is_esp32 and CORE.using_esp_idf: if config[CONF_ENABLE_BTM] or config[CONF_ENABLE_RRM]: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0103106222..1ed9fd060f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -710,6 +710,8 @@ int8_t WiFiScanResult::get_rssi() const { return this->rssi_; } bool WiFiScanResult::get_with_auth() const { return this->with_auth_; } bool WiFiScanResult::get_is_hidden() const { return this->is_hidden_; } +bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->bssid_ == rhs.bssid_; } + WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace wifi diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 6c5202ed7a..dba0d48724 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -24,6 +24,16 @@ extern "C" { #endif #endif +#ifdef USE_RP2040 +extern "C" { +#include "cyw43.h" +#include "cyw43_country.h" +#include "pico/cyw43_arch.h" +} + +#include +#endif + namespace esphome { namespace wifi { @@ -138,6 +148,8 @@ class WiFiScanResult { float get_priority() const { return priority_; } void set_priority(float priority) { priority_ = priority; } + bool operator==(const WiFiScanResult &rhs) const; + protected: bool matches_{false}; bssid_t bssid_; @@ -310,6 +322,11 @@ class WiFiComponent : public Component { void wifi_process_event_(IDFWiFiEvent *data); #endif +#ifdef USE_RP2040 + static int s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result); + void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result); +#endif + std::string use_address_; std::vector sta_; std::vector sta_priorities_; diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp new file mode 100644 index 0000000000..0ab143a9de --- /dev/null +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -0,0 +1,204 @@ + +#include "wifi_component.h" + +#ifdef USE_RP2040 + +#include "lwip/dns.h" +#include "lwip/err.h" +#include "lwip/netif.h" + +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace wifi { + +static const char *const TAG = "wifi_pico_w"; + +bool WiFiComponent::wifi_mode_(optional sta, optional ap) { + if (sta.has_value()) { + if (sta.value()) { + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_WORLDWIDE); + } + } + return true; +} + +bool WiFiComponent::wifi_apply_power_save_() { + uint32_t pm; + switch (this->power_save_) { + case WIFI_POWER_SAVE_NONE: + pm = CYW43_PERFORMANCE_PM; + break; + case WIFI_POWER_SAVE_LIGHT: + pm = CYW43_DEFAULT_PM; + break; + case WIFI_POWER_SAVE_HIGH: + pm = CYW43_AGGRESSIVE_PM; + break; + } + int ret = cyw43_wifi_pm(&cyw43_state, pm); + return ret == 0; +} + +// TODO: The driver doesnt seem to have an API for this +bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; } + +bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { + if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) + return false; + + auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str()); + if (ret != WL_CONNECTED) + return false; + + return true; +} + +bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); } + +bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { + if (!manual_ip.has_value()) { + return true; + } + + IPAddress ip_address = IPAddress(manual_ip->static_ip); + IPAddress gateway = IPAddress(manual_ip->gateway); + IPAddress subnet = IPAddress(manual_ip->subnet); + + IPAddress dns = IPAddress(manual_ip->dns1); + + WiFi.config(ip_address, dns, gateway, subnet); + return true; +} + +bool WiFiComponent::wifi_apply_hostname_() { + WiFi.setHostname(App.get_name().c_str()); + return true; +} +const char *get_auth_mode_str(uint8_t mode) { + // TODO: + return "UNKNOWN"; +} +const char *get_disconnect_reason_str(uint8_t reason) { + // TODO: + return "UNKNOWN"; +} + +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + int status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA); + switch (status) { + case CYW43_LINK_JOIN: + case CYW43_LINK_NOIP: + return WiFiSTAConnectStatus::CONNECTING; + case CYW43_LINK_UP: + return WiFiSTAConnectStatus::CONNECTED; + case CYW43_LINK_FAIL: + case CYW43_LINK_BADAUTH: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case CYW43_LINK_NONET: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + return WiFiSTAConnectStatus::IDLE; +} + +int WiFiComponent::s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result) { + global_wifi_component->wifi_scan_result(env, result); + return 0; +} + +void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result) { + bssid_t bssid; + std::copy(result->bssid, result->bssid + 6, bssid.begin()); + std::string ssid(reinterpret_cast(result->ssid)); + WiFiScanResult res(bssid, ssid, result->channel, result->rssi, result->auth_mode != CYW43_AUTH_OPEN, ssid.empty()); + if (std::find(this->scan_result_.begin(), this->scan_result_.end(), res) == this->scan_result_.end()) { + this->scan_result_.push_back(res); + } +} + +bool WiFiComponent::wifi_scan_start_() { + this->scan_result_.clear(); + this->scan_done_ = false; + cyw43_wifi_scan_options_t scan_options = {0}; + int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result); + if (err) { + ESP_LOGV(TAG, "cyw43_wifi_scan failed!"); + } + return err == 0; + return true; +} + +bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { + // TODO: + return false; +} + +bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { + if (!this->wifi_mode_({}, true)) + return false; + + if (ap.get_channel().has_value()) { + cyw43_wifi_ap_set_channel(&cyw43_state, ap.get_channel().value()); + } + + const char *ssid = ap.get_ssid().c_str(); + + cyw43_wifi_ap_set_ssid(&cyw43_state, strlen(ssid), (const uint8_t *) ssid); + + if (!ap.get_password().empty()) { + const char *password = ap.get_password().c_str(); + cyw43_wifi_ap_set_password(&cyw43_state, strlen(password), (const uint8_t *) password); + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_WPA2_MIXED_PSK); + } else { + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_OPEN); + } + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, CYW43_COUNTRY_WORLDWIDE); + + return true; +} +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.localIP()}; } + +bool WiFiComponent::wifi_disconnect_() { + int err = cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA); + return err == 0; +} +// NOTE: The driver does not provide an interface to get this +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t raw_bssid[6]; + WiFi.BSSID(raw_bssid); + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + return bssid; +} +// NOTE: The driver does not provide an interface to get this +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID(); } +// NOTE: The driver does not provide an interface to get this +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +// NOTE: The driver does not provide an interface to get this +int32_t WiFiComponent::wifi_channel_() { return 0; } +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::IPAddress WiFiComponent::wifi_dns_ip_(int num) { + const ip_addr_t *dns_ip = dns_getserver(num); + return {dns_ip->addr}; +} + +void WiFiComponent::wifi_loop_() { + if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { + this->scan_done_ = true; + ESP_LOGV(TAG, "Scan done!"); + } +} + +void WiFiComponent::wifi_pre_setup_() {} + +} // namespace wifi +} // namespace esphome + +#endif diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 09436c1fbf..19d4747575 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1440,6 +1440,7 @@ class SplitDefault(Optional): esp32=vol.UNDEFINED, esp32_arduino=vol.UNDEFINED, esp32_idf=vol.UNDEFINED, + rp2040=vol.UNDEFINED, ): super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) @@ -1449,6 +1450,7 @@ class SplitDefault(Optional): self._esp32_idf_default = vol.default_factory( esp32_idf if esp32 is vol.UNDEFINED else esp32 ) + self._rp2040_default = vol.default_factory(rp2040) @property def default(self): @@ -1458,6 +1460,8 @@ class SplitDefault(Optional): return self._esp32_arduino_default if CORE.is_esp32 and CORE.using_esp_idf: return self._esp32_idf_default + if CORE.is_rp2040: + return self._rp2040_default raise NotImplementedError @default.setter diff --git a/esphome/const.py b/esphome/const.py index f2d31bd21f..f8551f81ce 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -6,8 +6,9 @@ ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" +PLATFORM_RP2040 = "rp2040" -TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040] SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index a422cd9507..ef44b31fa3 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -594,6 +594,10 @@ class EsphomeCore: def is_esp32(self): return self.target_platform == "esp32" + @property + def is_rp2040(self): + return self.target_platform == "rp2040" + @property def target_framework(self): return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3aca944a36..79e706cf0d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -21,6 +21,8 @@ #include "esp_system.h" #include #include +#elif defined(USE_RP2040) && defined(USE_WIFI) +#include #endif #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC @@ -91,6 +93,8 @@ uint32_t random_uint32() { return esp_random(); #elif defined(USE_ESP8266) return os_random(); +#elif defined(USE_RP2040) + return ((uint32_t) rand()) << 16 + ((uint32_t) rand()); #else #error "No random source available for this configuration." #endif @@ -102,6 +106,8 @@ bool random_bytes(uint8_t *data, size_t len) { return true; #elif defined(USE_ESP8266) return os_get_random(data, len) == 0; +#elif defined(USE_RP2040) + return false; #else #error "No random source available for this configuration." #endif @@ -409,6 +415,8 @@ void get_mac_address_raw(uint8_t *mac) { #endif #elif defined(USE_ESP8266) wifi_get_macaddr(STATION_IF, mac); +#elif defined(USE_RP2040) && defined(USE_WIFI) + WiFi.macAddress(mac); #endif } std::string get_mac_address() { diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0367101023..d551c9da4b 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -428,21 +428,27 @@ class DownloadBinaryRequestHandler(BaseHandler): def get(self, configuration=None): type = self.get_argument("type", "firmware.bin") - if type == "firmware.bin": - 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 + 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 + + if storage_json.target_platform.lower() == const.PLATFORM_RP2040: + 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": - 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 filename = f"{storage_json.name}-factory.bin" path = storage_json.firmware_bin_path.replace( "firmware.bin", "firmware-factory.bin" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index af71d4583c..1cc2180747 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -119,16 +119,16 @@ class StorageJSON: ) @staticmethod - def from_wizard(name: str, address: str, esp_platform: str) -> "StorageJSON": + def from_wizard(name: str, address: str, platform: str) -> "StorageJSON": return StorageJSON( storage_version=1, name=name, comment=None, - esphome_version=const.__version__, + esphome_version=None, src_version=1, address=address, web_port=None, - target_platform=esp_platform, + target_platform=platform, build_path=None, firmware_bin_path=None, loaded_integrations=[], diff --git a/esphome/wizard.py b/esphome/wizard.py index 602f4ecf04..6273eec25d 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -81,11 +81,20 @@ esp32: type: esp-idf """ +RP2040_CONFIG = """ +rp2040: + board: {board} + framework: + # Required until https://github.com/platformio/platform-raspberrypi/pull/36 is merged + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git +""" + HARDWARE_BASE_CONFIGS = { "ESP8266": ESP8266_CONFIG, "ESP32": ESP32_CONFIG, "ESP32S2": ESP32S2_CONFIG, "ESP32C3": ESP32C3_CONFIG, + "RP2040": RP2040_CONFIG, } @@ -164,6 +173,7 @@ captive_portal: def wizard_write(path, **kwargs): from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.rp2040 import boards as rp2040_boards name = kwargs["name"] board = kwargs["board"] @@ -173,9 +183,13 @@ def wizard_write(path, **kwargs): kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: - kwargs["platform"] = ( - "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" - ) + if board in esp8266_boards.ESP8266_BOARD_PINS: + platform = "ESP8266" + elif board in rp2040_boards.RP2040_BOARD_PINS: + platform = "RP2040" + else: + platform = "ESP32" + kwargs["platform"] = platform hardware = kwargs["platform"] write_file(path, wizard_file(**kwargs)) diff --git a/platformio.ini b/platformio.ini index 9b943242f7..86617586ae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,6 +146,24 @@ build_flags = -DUSE_ESP32_FRAMEWORK_ESP_IDF 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.20400.0 + +framework = arduino +lib_deps = + ${common:arduino.lib_deps} +build_flags = + ${common:arduino.build_flags} + -DUSE_RP2040 + -DUSE_RP2040_FRAMEWORK_ARDUINO + ; All the actual environments are defined below. [env:esp8266-arduino] extends = common:esp8266-arduino @@ -222,3 +240,10 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy build_flags = ${common:esp32-idf.build_flags} ${flags:clangtidy.build_flags} + +[env:rp2040-pico-arduino] +extends = common:rp2040-arduino +board = pico +build_flags = + ${common:rp2040-arduino.build_flags} + ${flags:runtime.build_flags} diff --git a/script/ci-custom.py b/script/ci-custom.py index 6f69b55d2c..f95039576b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -534,6 +534,7 @@ def lint_relative_py_import(fname): "esphome/components/socket/headers.h", "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", + "esphome/components/rp2040/core.cpp", ], ) def lint_namespace(fname, content): diff --git a/tests/README.md b/tests/README.md index 546025526f..ed78b3e7d1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,3 +24,4 @@ Current test_.yaml file contents. | test3.yaml | ESP8266 | wifi | N/A | test4.yaml | ESP32 | ethernet | None | test5.yaml | ESP32 | wifi | ble_server +| test6.yaml | RP2040 | wifi | N/A diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index d956387665..8cc1838d94 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -13,8 +13,9 @@ using namespace esphome; void setup() { App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); - auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); // NOLINT + auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); + log->set_uart_selection(logger::UART_SELECTION_UART0); App.register_component(log); auto *wifi = new wifi::WiFiComponent(); // NOLINT diff --git a/tests/test6.yaml b/tests/test6.yaml new file mode 100644 index 0000000000..264773331e --- /dev/null +++ b/tests/test6.yaml @@ -0,0 +1,39 @@ +--- +esphome: + name: test6 + project: + name: esphome.test6_project + version: "1.0.0" + +rp2040: + board: rpipicow + framework: + # Waiting for https://github.com/platformio/platform-raspberrypi/pull/36 + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + + +wifi: + networks: + - ssid: "MySSID" + password: "password1" + +api: + +ota: + +logger: + +binary_sensor: + - platform: gpio + pin: GPIO5 + id: pin_5_button + +output: + - platform: gpio + pin: GPIO4 + id: pin_4 + +switch: + - platform: output + output: pin_4 + id: pin_4_switch From 615288c151ae83c364b1ade26de0c31c8e2efd64 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:59:41 +1300 Subject: [PATCH 161/838] Bump esphome-dashboard to 20221020.0 (#3920) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index af7ac0cab4..3b6d444c22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.1.4 # When updating platformio, also update Dockerfile esptool==3.3.1 click==8.1.3 -esphome-dashboard==20221007.0 +esphome-dashboard==20221020.0 aioesphomeapi==10.13.0 zeroconf==0.39.1 From 96e8cb66b69be4233dd6b1d9ae2498ba6888c683 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:12:55 +1300 Subject: [PATCH 162/838] Fix missing dependencies for heatpumpir (#3933) --- esphome/components/heatpumpir/climate.py | 4 ++++ platformio.ini | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 2e78db8356..cc8e75dcbd 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_PROTOCOL, CONF_VISUAL, ) +from esphome.core import CORE CODEOWNERS = ["@rob-deutsch"] @@ -115,3 +116,6 @@ def to_code(config): cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add_library("tonia/HeatpumpIR", "1.0.20") + + if CORE.is_esp8266 or CORE.is_esp32: + cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12") diff --git a/platformio.ini b/platformio.ini index 86617586ae..ccfee97207 100644 --- a/platformio.ini +++ b/platformio.ini @@ -92,6 +92,7 @@ lib_deps = ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) + crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir build_flags = ${common:arduino.build_flags} -Wno-nonnull-compare @@ -111,16 +112,17 @@ board = nodemcu-32s lib_deps = ; order matters with lib-deps; some of the libs in common:arduino.lib_deps ; don't declare built-in libraries as dependencies, so they have to be declared first - FS ; web_server_base (Arduino built-in) - WiFi ; wifi,web_server_base,ethernet (Arduino built-in) - Update ; ota,web_server_base (Arduino built-in) + FS ; web_server_base (Arduino built-in) + 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 - 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.1.0 ; i2s_audio + esphome/AsyncTCP-esphome@1.2.2 ; 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.1.0 ; i2s_audio + crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir build_flags = ${common:arduino.build_flags} -DUSE_ESP32 From 3a134ef009357235da786fa448dad1c930f0ef2c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:53:01 +1300 Subject: [PATCH 163/838] Update the PR template (#3934) --- .github/PULL_REQUEST_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f9c8cce0af..3221b8ac5c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ # What does this implement/fix? -Quick description and explanation of changes + ## Types of changes @@ -18,6 +18,7 @@ Quick description and explanation of changes - [ ] ESP32 - [ ] ESP32 IDF - [ ] ESP8266 +- [ ] RP2040 ## Example entry for `config.yaml`: \r\n"); ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, @@ -197,7 +197,7 @@ QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t * wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33) wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23) wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04) - ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0 + ret = (QMP6988_S16_t) ((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0 return ret; } @@ -332,13 +332,13 @@ void QMP6988Component::calculate_pressure_() { ESP_LOGE(TAG, "Error reading raw pressure/temp values"); return; } - p_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) | - (((QMP6988_U16_t)(a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2])); - p_raw = (QMP6988_S32_t)(p_read - SUBTRACTOR); + p_read = (QMP6988_U32_t) ((((QMP6988_U32_t) (a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) | + (((QMP6988_U16_t) (a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2])); + p_raw = (QMP6988_S32_t) (p_read - SUBTRACTOR); - t_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) | - (((QMP6988_U16_t)(a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5])); - t_raw = (QMP6988_S32_t)(t_read - SUBTRACTOR); + t_read = (QMP6988_U32_t) ((((QMP6988_U32_t) (a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) | + (((QMP6988_U16_t) (a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5])); + t_raw = (QMP6988_S32_t) (t_read - SUBTRACTOR); t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw); p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int); diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index 47a85cda57..cb6eed4c6c 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -78,7 +78,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { out_data |= 1; } - out.command = (uint8_t)(out_data & 0x3F) + (1 - field_bit) * 64u; + out.command = (uint8_t) (out_data & 0x3F) + (1 - field_bit) * 64u; out.address = (out_data >> 6) & 0x1F; return out; } diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index c25ed107b7..01abca0a1f 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -43,7 +43,7 @@ void SCD30Component::setup() { uint16_t(raw_firmware_version[0] & 0xFF)); if (this->temperature_offset_ != 0) { - if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) { + if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t) (temperature_offset_ * 100.0))) { ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index 4a4ca832bf..40f075e673 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -16,7 +16,7 @@ class SCD30Component : public Component, public sensirion_common::SensirionI2CDe void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } void set_ambient_pressure_compensation(float pressure) { - ambient_pressure_compensation_ = (uint16_t)(pressure * 1000); + ambient_pressure_compensation_ = (uint16_t) (pressure * 1000); } void set_temperature_offset(float offset) { temperature_offset_ = offset; } void set_update_interval(uint16_t interval) { update_interval_ = interval; } diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index 117b92b901..a8a4129b48 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -50,7 +50,7 @@ void SCD4XComponent::setup() { uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET, - (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + (uint16_t) (temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { ESP_LOGE(TAG, "Error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 7fe46b9518..52f9adc808 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -234,8 +234,8 @@ bool SGP4xComponent::measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw) { response_words = 2; } } - uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100)); - uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175); + uint16_t rhticks = llround((uint16_t) ((humidity * 65535) / 100)); + uint16_t tempticks = (uint16_t) (((temperature + 45) * 65535) / 175); // first parameter are the relative humidity ticks data[0] = rhticks; // secomd parameter are the temperature ticks diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index bdc3e62d2f..0f9123434d 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -19,7 +19,7 @@ void SHT4XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up sht4x..."); if (this->duty_cycle_ > 0.0) { - uint32_t heater_interval = (uint32_t)(this->heater_time_ / this->duty_cycle_); + uint32_t heater_interval = (uint32_t) (this->heater_time_ / this->duty_cycle_); ESP_LOGD(TAG, "Heater interval: %i", heater_interval); if (this->heater_power_ == SHT4X_HEATERPOWER_HIGH) { diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index 63fa0ba72f..f336d24e24 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -74,7 +74,7 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) { void HOT ST7920::write_display_data() { uint8_t i, j, b; - for (j = 0; j < (uint8_t)(this->get_height_internal() / 2); j++) { + for (j = 0; j < (uint8_t) (this->get_height_internal() / 2); j++) { this->goto_xy_(0, j); this->enable(); for (i = 0; i < 16; i++) { // 16 bytes from line #0+ diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 113c14d431..54aaf0942b 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -269,7 +269,7 @@ struct SunAtLocation { num_t jd = julian_day(date) + added_d; num_t eot = SunAtTime(jd).equation_of_time() * 240; - time_t new_timestamp = (time_t)(date.timestamp + added_d * 86400 - eot); + time_t new_timestamp = (time_t) (date.timestamp + added_d * 86400 - eot); return time::ESPTime::from_epoch_utc(new_timestamp); } }; diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 276bf65ebf..88c59eb761 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -287,7 +287,7 @@ void TCS34725Component::update() { } // calculate register value from timing - uint8_t regval_atime = (uint8_t)(256.f - integration_time_next / 2.4f); + uint8_t regval_atime = (uint8_t) (256.f - integration_time_next / 2.4f); ESP_LOGD(TAG, "Integration time: %.1fms, ideal: %.1fms regval_new %d Gain: %.f Clear channel raw: %d gain reg: %d", this->integration_time_, integration_time_next, regval_atime, this->gain_, raw_c, this->gain_reg_); diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index be2192ea22..5b8cbc6004 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -168,7 +168,7 @@ uint8_t TM1637Display::get_keys() { // Bit | 7 6 5 4 3 2 1 0 // ------+------------------------ // To | 0 0 0 0 K2 S2 S1 S0 - key_code = (uint8_t)((key_code & 0x80) >> 7 | (key_code & 0x40) >> 5 | (key_code & 0x20) >> 3 | (key_code & 0x08)); + key_code = (uint8_t) ((key_code & 0x80) >> 7 | (key_code & 0x40) >> 5 | (key_code & 0x20) >> 3 | (key_code & 0x08)); } return key_code; } diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp index 526b53f601..24cb4122bf 100644 --- a/esphome/components/tm1638/tm1638.cpp +++ b/esphome/components/tm1638/tm1638.cpp @@ -140,7 +140,7 @@ void TM1638Component::set_intensity(uint8_t brightness_level) { this->send_command_(TM1638_REGISTER_FIXEDADDRESS); if (brightness_level > 0) { - this->send_command_((uint8_t)(TM1638_REGISTER_DISPLAYON | intensity_)); + this->send_command_((uint8_t) (TM1638_REGISTER_DISPLAYON | intensity_)); } else { this->send_command_(TM1638_REGISTER_DISPLAYOFF); } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 79a9049b04..040b9b7ed5 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -381,8 +381,8 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { } void Tuya::send_raw_command_(TuyaCommand command) { - uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); - uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); + uint8_t len_hi = (uint8_t) (command.payload.size() >> 8); + uint8_t len_lo = (uint8_t) (command.payload.size() & 0xFF); uint8_t version = 0; this->last_command_timestamp_ = millis(); diff --git a/esphome/components/vbus/sensor/vbus_sensor.cpp b/esphome/components/vbus/sensor/vbus_sensor.cpp index 57d5a355ad..8261773431 100644 --- a/esphome/components/vbus/sensor/vbus_sensor.cpp +++ b/esphome/components/vbus/sensor/vbus_sensor.cpp @@ -12,7 +12,7 @@ static inline uint16_t get_u16(std::vector &message, int start) { } static inline int16_t get_i16(std::vector &message, int start) { - return (int16_t)((message[start + 1] << 8) + message[start]); + return (int16_t) ((message[start + 1] << 8) + message[start]); } void DeltaSolBSPlusSensor::dump_config() { diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index f851cf6d73..b07779a653 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -88,7 +88,7 @@ void VL53L0XSensor::setup() { this->timeout_start_us_ = micros(); while (reg(0x83).get() == 0x00) { - if (this->timeout_us_ > 0 && ((uint16_t)(micros() - this->timeout_start_us_) > this->timeout_us_)) { + if (this->timeout_us_ > 0 && ((uint16_t) (micros() - this->timeout_start_us_) > this->timeout_us_)) { ESP_LOGE(TAG, "'%s' - setup timeout", this->name_.c_str()); this->mark_failed(); return; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6c74c79ce6..80a53a7515 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -867,7 +867,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url } // Longest: HORIZONTAL -#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15) +#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P) ((mode_s)), 15) std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { return json::build_json([obj, start_config](JsonObject root) { diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index f354ab070d..225423b4db 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -78,7 +78,7 @@ void WhirlpoolClimate::transmit_state() { // Temperature auto temp = (uint8_t) roundf(clamp(this->target_temperature, this->temperature_min_(), this->temperature_max_())); - remote_state[3] |= (uint8_t)(temp - this->temperature_min_()) << 4; + remote_state[3] |= (uint8_t) (temp - this->temperature_min_()) << 4; // Fan speed switch (this->fan_mode.value()) { diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 95d97defe2..95faea0446 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -239,12 +239,12 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c } uint8_t mac_reverse[6] = {0}; - mac_reverse[5] = (uint8_t)(address >> 40); - mac_reverse[4] = (uint8_t)(address >> 32); - mac_reverse[3] = (uint8_t)(address >> 24); - mac_reverse[2] = (uint8_t)(address >> 16); - mac_reverse[1] = (uint8_t)(address >> 8); - mac_reverse[0] = (uint8_t)(address >> 0); + mac_reverse[5] = (uint8_t) (address >> 40); + mac_reverse[4] = (uint8_t) (address >> 32); + mac_reverse[3] = (uint8_t) (address >> 24); + mac_reverse[2] = (uint8_t) (address >> 16); + mac_reverse[1] = (uint8_t) (address >> 8); + mac_reverse[0] = (uint8_t) (address >> 0); XiaomiAESVector vector{.key = {0}, .plaintext = {0}, diff --git a/esphome/components/xpt2046/xpt2046.cpp b/esphome/components/xpt2046/xpt2046.cpp index e3317e0fd5..6c7c55a995 100644 --- a/esphome/components/xpt2046/xpt2046.cpp +++ b/esphome/components/xpt2046/xpt2046.cpp @@ -104,8 +104,8 @@ void XPT2046Component::check_touch_() { 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); + 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); @@ -185,7 +185,7 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va } else if (val >= max_val) { ret = 0xfff; } else { - ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val)); + ret = (int16_t) ((int) 0xfff * (val - min_val) / (max_val - min_val)); } return ret; diff --git a/esphome/core/log.h b/esphome/core/log.h index bef5e5c633..6775aa5ac5 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -167,7 +167,7 @@ struct LogString; #include #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) -#define LOG_STR_ARG(s) ((PGM_P)(s)) +#define LOG_STR_ARG(s) ((PGM_P) (s)) #else // Pre-Arduino 2.5, we can't pass a PSTR() to printf(). Emulate support by copying the message to a // local buffer first. String length is limited to 63 characters. @@ -176,7 +176,7 @@ struct LogString; ({ \ char __buf[64]; \ __buf[63] = '\0'; \ - strncpy_P(__buf, (PGM_P)(s), 63); \ + strncpy_P(__buf, (PGM_P) (s), 63); \ __buf; \ }) #endif diff --git a/script/clang-format b/script/clang-format index ae807262f1..165fbd269f 100755 --- a/script/clang-format +++ b/script/clang-format @@ -17,7 +17,7 @@ def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ["clang-format-11"] + invocation = ["clang-format-13"] if args.inplace: invocation.append("-i") else: @@ -59,14 +59,14 @@ def main(): args = parser.parse_args() try: - get_output("clang-format-11", "-version") + get_output("clang-format-13", "-version") except: print( """ Oops. It looks like clang-format is not installed. - Please check you can run "clang-format-11 -version" in your terminal and install - clang-format (v11) if necessary. + 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. From d98d6ff45f7d6019ee3b5e073d7cca798af9f39e Mon Sep 17 00:00:00 2001 From: "Aaron S. Jackson" Date: Mon, 20 Mar 2023 03:45:50 +0000 Subject: [PATCH 717/838] B/W support for GooDisplay GDEY029T94 (as used on Adafruit MagTag) (#4222) * B/W support for GooDisplay GDEY029T94 * Fix python style ci * linter recommendations --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/waveshare_epaper/display.py | 2 + .../waveshare_epaper/waveshare_epaper.cpp | 84 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 20 +++++ 3 files changed, 106 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 46da3f6fb4..747794b631 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -29,6 +29,7 @@ WaveshareEPaper2P7In = waveshare_epaper_ns.class_( WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( "WaveshareEPaper2P9InB", WaveshareEPaper ) +GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper) WaveshareEPaper4P2In = waveshare_epaper_ns.class_( "WaveshareEPaper4P2In", WaveshareEPaper ) @@ -73,6 +74,7 @@ MODELS = { "2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74), "2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), + "gdey029t94": ("c", GDEY029T94), "2.70in": ("b", WaveshareEPaper2P7In), "2.90in-b": ("b", WaveshareEPaper2P9InB), "4.20in": ("b", WaveshareEPaper4P2In), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 5580674c34..8fd5c2e1f3 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -663,6 +663,90 @@ void WaveshareEPaper2P9InB::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// Good Display 2.9in black/white/grey +// Datasheet: +// - https://v4.cecdn.yun300.cn/100001_1909185148/SSD1680.pdf +// - https://github.com/adafruit/Adafruit_EPD/blob/master/src/panels/ThinkInk_290_Grayscale4_T5.h +// ======================================================== + +void GDEY029T94::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 POWER SETTINGS + this->command(0x00); + this->data(0x03); + this->data(0x00); + this->data(0x2b); + this->data(0x2b); + this->data(0x03); /* for b/w */ + + // 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_(); + + // Not sure what this does but it's in the Adafruit EPD library + this->command(0xFF); + 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(0b10011111); + + // 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 GDEY029T94::display() { + // COMMAND DATA START TRANSMISSION 2 (B/W only) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + this->write_byte(this->buffer_[i]); + } + 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 GDEY029T94::get_width_internal() { return 128; } +int GDEY029T94::get_height_internal() { return 296; } +void GDEY029T94::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper (Good Display)", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEY029T94"); + 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); +} + static const uint8_t LUT_VCOM_DC_4_2[] = { 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index e613899149..848b293c45 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -146,6 +146,26 @@ class WaveshareEPaper2P7In : public WaveshareEPaper { int get_height_internal() override; }; +class GDEY029T94 : 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 WaveshareEPaper2P9InB : public WaveshareEPaper { public: void initialize() override; From cd57469e06aaf223c518c72b17c07e80722982fc Mon Sep 17 00:00:00 2001 From: jerome992 <35580081+jerome992@users.noreply.github.com> Date: Mon, 20 Mar 2023 05:22:22 +0100 Subject: [PATCH 718/838] Fix negative sqrt root in ct_clamp_sensor.cpp (#2701) (#4236) Co-authored-by: Jerome --- esphome/components/ct_clamp/ct_clamp_sensor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 51b0f1318c..d555befcde 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -33,7 +33,10 @@ void CTClampSensor::update() { const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_; const float rms_dc = this->sample_sum_ / this->num_samples_; - const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc); + const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc; + float rms_ac = 0; + if (rms_ac_squared > 0) + rms_ac = std::sqrt(rms_ac_squared); ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); this->publish_state(rms_ac); From d42f35de5d5416825a02fd4457e32ac781e6cd9d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:24:14 +1300 Subject: [PATCH 719/838] Wrap ipv6 code a bit more (#4574) * Wrap ipv6 code a bit more for when ipv6 support should not be compiled in * More checks * More uses * Fix --- esphome/components/api/api_server.cpp | 2 +- .../ethernet/ethernet_component.cpp | 13 ++++++++ esphome/components/mqtt/mqtt_client.cpp | 14 ++++++-- esphome/components/network/__init__.py | 8 +++-- esphome/components/ota/ota_component.cpp | 2 +- .../components/socket/bsd_sockets_impl.cpp | 5 ++- esphome/components/socket/headers.h | 15 +++++++-- esphome/components/socket/socket.cpp | 32 +++++++++++++++++-- esphome/components/socket/socket.h | 5 ++- .../wifi/wifi_component_esp32_arduino.cpp | 20 +++++++++--- .../wifi/wifi_component_esp_idf.cpp | 16 +++++++++- 11 files changed, 112 insertions(+), 20 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 6e28637241..acde0966ba 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -45,7 +45,7 @@ void APIServer::setup() { struct sockaddr_storage server; - socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); if (sl == 0) { ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); this->mark_failed(); diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 7120223cc9..4792728a71 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -255,14 +255,22 @@ void EthernetComponent::start_connect_() { if (this->manual_ip_.has_value()) { if (uint32_t(this->manual_ip_->dns1) != 0) { 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 dns_setserver(0, &d); } if (uint32_t(this->manual_ip_->dns1) != 0) { 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 dns_setserver(1, &d); } } else { @@ -289,8 +297,13 @@ void EthernetComponent::dump_connect_params_() { 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 esp_err_t err; diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index acb863244e..af2828ff15 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -2,16 +2,16 @@ #ifdef USE_MQTT +#include +#include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/components/network/util.h" -#include #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif -#include "lwip/err.h" #include "lwip/dns.h" +#include "lwip/err.h" #include "mqtt_component.h" namespace esphome { @@ -104,7 +104,11 @@ void MQTTClientComponent::start_dnslookup_() { // 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; @@ -160,8 +164,12 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * 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 diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 40d420c48c..96cfc51ff5 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -22,6 +22,8 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): - if CONF_ENABLE_IPV6 in config and config[CONF_ENABLE_IPV6]: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) + if CONF_ENABLE_IPV6 in config: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) + add_idf_sdkconfig_option( + "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] + ) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0195cb4616..39ba3dbed4 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -65,7 +65,7 @@ void OTAComponent::setup() { struct sockaddr_storage server; - socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); if (sl == 0) { ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); this->mark_failed(); diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index b21341e4d6..3a08e3ea52 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -20,7 +20,9 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { char buf[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) != nullptr) return std::string{buf}; - } else if (storage.ss_family == AF_INET6) { + } +#if LWIP_IPV6 + else if (storage.ss_family == AF_INET6) { const struct sockaddr_in6 *addr = reinterpret_cast(&storage); char buf[INET6_ADDRSTRLEN]; // Format IPv4-mapped IPv6 addresses as regular IPv4 addresses @@ -32,6 +34,7 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) != nullptr) return std::string{buf}; } +#endif return {}; } diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index 1e79c8a1ab..20d8fdb8c9 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -15,19 +15,28 @@ /* Address families. */ #define AF_UNSPEC 0 #define AF_INET 2 -#define AF_INET6 10 #define PF_INET AF_INET -#define PF_INET6 AF_INET6 #define PF_UNSPEC AF_UNSPEC + #define IPPROTO_IP 0 #define IPPROTO_TCP 6 + +#if LWIP_IPV6 +#define AF_INET6 10 +#define PF_INET6 AF_INET6 + #define IPPROTO_IPV6 41 #define IPPROTO_ICMPV6 58 +#endif #define TCP_NODELAY 0x01 #define F_GETFL 3 #define F_SETFL 4 + +#ifdef O_NONBLOCK +#undef O_NONBLOCK +#endif #define O_NONBLOCK 1 #define SHUT_RD 0 @@ -58,6 +67,7 @@ struct sockaddr_in { char sin_zero[SIN_ZERO_LEN]; }; +#if LWIP_IPV6 // NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr_in6 { uint8_t sin6_len; /* length of this structure */ @@ -67,6 +77,7 @@ struct sockaddr_in6 { struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Set of interfaces for scope */ }; +#endif // NOLINTNEXTLINE(readability-identifier-naming) struct sockaddr { diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 22a4c11df8..3fe7491d57 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -14,6 +14,34 @@ std::unique_ptr socket_ip(int type, int protocol) { #endif } +socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) { +#if LWIP_IPV6 + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in6)); + server->sin6_family = AF_INET6; + server->sin6_port = htons(port); + ip6_addr_t ip6; + inet6_aton(ip_address, &ip6); + memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr)); + return sizeof(sockaddr_in6); +#else + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in)); + server->sin_family = AF_INET; + server->sin_addr.s_addr = inet_addr(ip_address); + server->sin_port = htons(port); + return sizeof(sockaddr_in); +#endif +} + socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { #if LWIP_IPV6 if (addrlen < sizeof(sockaddr_in6)) { @@ -23,7 +51,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po auto *server = reinterpret_cast(addr); memset(server, 0, sizeof(sockaddr_in6)); server->sin6_family = AF_INET6; - server->sin6_port = port; + server->sin6_port = htons(port); server->sin6_addr = in6addr_any; return sizeof(sockaddr_in6); #else @@ -35,7 +63,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po memset(server, 0, sizeof(sockaddr_in)); server->sin_family = AF_INET; server->sin_addr.s_addr = ESPHOME_INADDR_ANY; - server->sin_port = port; + server->sin_port = htons(port); return sizeof(sockaddr_in); #endif } diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index ecf117deeb..175cb21a4a 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -44,7 +44,10 @@ std::unique_ptr socket(int domain, int type, int protocol); /// Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol. std::unique_ptr socket_ip(int type, int protocol); -/// Set a sockaddr to the any address for the IP version used by socket_ip(). +/// Set a sockaddr to the specified address and port for the IP version used by socket_ip(). +socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port); + +/// Set a sockaddr to the any address and specified port for the IP version used by socket_ip(). socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); } // namespace socket diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 54ed7638d2..ab04224161 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -4,19 +4,19 @@ #include -#include #include +#include #ifdef USE_WIFI_WPA2_EAP #include #endif -#include "lwip/err.h" -#include "lwip/dns.h" #include "lwip/apps/sntp.h" +#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/application.h" #include "esphome/core/util.h" namespace esphome { @@ -128,13 +128,23 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } ip_addr_t dns; +#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 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 dns_setserver(1, &dns); } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 2883164495..0f3c3a0ca8 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -451,13 +451,23 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { } ip_addr_t dns; +#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 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 dns_setserver(1, &dns); } @@ -639,7 +649,7 @@ 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; -#ifdef LWIP_IPV6_AUTOCONFIG +#if LWIP_IPV6_AUTOCONFIG tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); #endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), @@ -912,7 +922,11 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { } 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 } } // namespace wifi From d70e7da0ef231124504d1902d453eb5b3d0e2943 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:25:19 +1300 Subject: [PATCH 720/838] rp2040: Use fake Mutex lock (#4602) --- esphome/core/helpers.cpp | 4 ++-- esphome/core/helpers.h | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 4ac9303b20..7f5c3ad333 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -393,13 +393,13 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } // System APIs -#if defined(USE_ESP8266) +#if defined(USE_ESP8266) || defined(USE_RP2040) // ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. Mutex::Mutex() {} void Mutex::lock() {} bool Mutex::try_lock() { return true; } void Mutex::unlock() {} -#elif defined(USE_ESP32) || defined(USE_RP2040) +#elif defined(USE_ESP32) Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); } void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); } bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 0d2a7e298a..a107b1b849 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -17,9 +17,6 @@ #if defined(USE_ESP32) #include #include -#elif defined(USE_RP2040) -#include -#include #endif #define HOT __attribute__((hot)) @@ -539,7 +536,7 @@ class Mutex { Mutex &operator=(const Mutex &) = delete; private: -#if defined(USE_ESP32) || defined(USE_RP2040) +#if defined(USE_ESP32) SemaphoreHandle_t handle_; #endif }; From a3875af4b4786777afa6f0fb86bd88cd9ed185b5 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Sun, 19 Mar 2023 18:54:00 +0000 Subject: [PATCH 721/838] climate: brown paper bag fix for on_configure (#4573) I forgot this hunk in https://github.com/esphome/esphome/pull/4511 . I'm sorry for the noise. --- esphome/components/climate/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 4a16c3fb7d..6734917bf3 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -324,6 +324,10 @@ async def setup_climate_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CONTROL, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): From db5988bbe1b1984f05bc8e63391d1230231cdb13 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:25:19 +1300 Subject: [PATCH 722/838] rp2040: Use fake Mutex lock (#4602) --- esphome/core/helpers.cpp | 4 ++-- esphome/core/helpers.h | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 4ac9303b20..7f5c3ad333 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -393,13 +393,13 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } // System APIs -#if defined(USE_ESP8266) +#if defined(USE_ESP8266) || defined(USE_RP2040) // ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. Mutex::Mutex() {} void Mutex::lock() {} bool Mutex::try_lock() { return true; } void Mutex::unlock() {} -#elif defined(USE_ESP32) || defined(USE_RP2040) +#elif defined(USE_ESP32) Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); } void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); } bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 0d2a7e298a..a107b1b849 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -17,9 +17,6 @@ #if defined(USE_ESP32) #include #include -#elif defined(USE_RP2040) -#include -#include #endif #define HOT __attribute__((hot)) @@ -539,7 +536,7 @@ class Mutex { Mutex &operator=(const Mutex &) = delete; private: -#if defined(USE_ESP32) || defined(USE_RP2040) +#if defined(USE_ESP32) SemaphoreHandle_t handle_; #endif }; From bc427de16a2d86a9bd8069d6e7932c8f06016fdc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 12:03:34 +1300 Subject: [PATCH 723/838] Bump version to 2023.3.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f175524796..35d1d3fa03 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.3.0" +__version__ = "2023.3.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d52e425ba28d4875bd3ffe7303cd16245e6690c5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:35:16 +1300 Subject: [PATCH 724/838] Remove EntityBase from sprinkler (#4606) * Remove EntityBase form sprinkler * remove unneeded method * Set name correctly Move some timers to `setup` that rely on this->name_ * Fix SprinklerControllerSwitch setup * Update esphome/components/sprinkler/__init__.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: Samuel Sieb Co-authored-by: Keith Burzinski --- esphome/components/sprinkler/__init__.py | 25 +++++++++++++--------- esphome/components/sprinkler/sprinkler.cpp | 21 ++++++------------ esphome/components/sprinkler/sprinkler.h | 15 ++++++------- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index cf3f471234..d49b1ba381 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -330,6 +330,7 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Sprinkler), + cv.Optional(CONF_NAME): cv.string, cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value( switch.switch_schema( SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG @@ -424,7 +425,8 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( ): cv.positive_time_period_seconds, cv.Required(CONF_VALVES): cv.ensure_list(SPRINKLER_VALVE_SCHEMA), } -).extend(cv.ENTITY_BASE_SCHEMA) +).extend(cv.COMPONENT_SCHEMA) + CONFIG_SCHEMA = cv.All( cv.ensure_list(SPRINKLER_CONTROLLER_SCHEMA), @@ -559,16 +561,19 @@ async def sprinkler_simple_action_to_code(config, action_id, template_arg, args) async def to_code(config): for sprinkler_controller in config: - if len(sprinkler_controller[CONF_VALVES]) > 1: - var = cg.new_Pvariable( - sprinkler_controller[CONF_ID], - sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME], - ) + var = cg.new_Pvariable(sprinkler_controller[CONF_ID]) + + if CONF_NAME in sprinkler_controller: + cg.add(var.set_name(sprinkler_controller[CONF_NAME])) else: - var = cg.new_Pvariable( - sprinkler_controller[CONF_ID], - sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][CONF_NAME], - ) + 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)) + await cg.register_component(var, sprinkler_controller) if len(sprinkler_controller[CONF_VALVES]) > 1: diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index d73d8d8fbf..6169185d60 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -138,15 +138,7 @@ float SprinklerControllerSwitch::get_setup_priority() const { return setup_prior Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } -void SprinklerControllerSwitch::setup() { - this->state = this->get_initial_state_with_restore_mode().value_or(false); - - if (this->state) { - this->turn_on(); - } else { - this->turn_off(); - } -} +void SprinklerControllerSwitch::setup() { this->state = this->get_initial_state_with_restore_mode().value_or(false); } void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); } @@ -378,10 +370,11 @@ SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; } -Sprinkler::Sprinkler() {} -Sprinkler::Sprinkler(const std::string &name) : EntityBase(name) {} - -void Sprinkler::setup() { this->all_valves_off_(true); } +void Sprinkler::setup() { + 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::loop() { for (auto &p : this->pump_) { @@ -1218,8 +1211,6 @@ SprinklerSwitch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) { return nullptr; } -uint32_t Sprinkler::hash_base() { return 3129891955UL; } - bool Sprinkler::valve_is_enabled_(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { if (this->valve_[valve_number].enable_switch != nullptr) { diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 1cde60321d..7952c4533f 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -201,15 +201,14 @@ class SprinklerValveRunRequest { SprinklerValveRunRequestOrigin origin_{USER}; }; -class Sprinkler : public Component, public EntityBase { +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); @@ -423,8 +422,6 @@ class Sprinkler : public Component, public EntityBase { SprinklerSwitch *valve_pump_switch_by_pump_index(size_t pump_index); protected: - uint32_t hash_base() override; - /// returns true if valve number is enabled bool valve_is_enabled_(size_t valve_number); @@ -528,6 +525,8 @@ class Sprinkler : public Component, public EntityBase { uint32_t start_delay_{0}; uint32_t stop_delay_{0}; + std::string name_; + /// Sprinkler controller state SprinklerState state_{IDLE}; @@ -574,9 +573,7 @@ class Sprinkler : public Component, public EntityBase { std::vector valve_op_{2}; /// Valve control timers - std::vector timer_{ - {this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)}, - {this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}}; + std::vector timer_{}; /// Other Sprinkler instances we should be aware of (used to check if pumps are in use) std::vector other_controllers_; From a1eb3b8475c92f4477d64fc3cce5e4e3275d33c4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:56:02 +1300 Subject: [PATCH 725/838] Swap curly brackets for round on LockGuard (#4610) --- esphome/core/helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a107b1b849..8950cc3b8a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -547,7 +547,7 @@ class Mutex { */ class LockGuard { public: - LockGuard(Mutex &mutex) : mutex_{mutex} { mutex_.lock(); } + LockGuard(Mutex &mutex) : mutex_(mutex) { mutex_.lock(); } ~LockGuard() { mutex_.unlock(); } private: From e13eaf6706b094c35b00ede1d86ab2d716456c1f Mon Sep 17 00:00:00 2001 From: guillempages Date: Wed, 22 Mar 2023 09:05:09 +0100 Subject: [PATCH 726/838] Fix animation resizing (#4608) Animation resizing in RGB24 format is causing an error "Image cannot be resized to a bigger size". Other image types do not show the issue, and the only difference is the "image.thumbnail" call. Removed the call and tested; the animation is shown with the desired size. --- esphome/components/animation/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index ce9f057496..68c3eee132 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -76,8 +76,6 @@ async def to_code(config): pos = 0 for frameIndex in range(frames): image.seek(frameIndex) - if CONF_RESIZE in config: - image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") if CONF_RESIZE in config: frame = frame.resize([width, height]) From c16709ed952b166f4e4c079d6bb227dc3847b3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Skaln=C3=ADk?= Date: Thu, 23 Mar 2023 19:37:40 +0100 Subject: [PATCH 727/838] fix wrong port multiplexer name in dump GPIO function (#4592) --- esphome/components/sx1509/sx1509_gpio_pin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index 2c6e0b0c32..56b51ae311 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -13,7 +13,7 @@ bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pi void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } std::string SX1509GPIOPin::dump_summary() const { char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); + snprintf(buffer, sizeof(buffer), "%u via sx1509", pin_); return buffer; } From cc317d27f569aacb3553d65db63a3bd02f53f197 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Mar 2023 18:38:20 +0000 Subject: [PATCH 728/838] Bump zeroconf from 0.47.3 to 0.47.4 (#4597) Bumps [zeroconf](https://github.com/python-zeroconf/python-zeroconf) from 0.47.3 to 0.47.4. - [Release notes](https://github.com/python-zeroconf/python-zeroconf/releases) - [Changelog](https://github.com/python-zeroconf/python-zeroconf/blob/master/CHANGELOG.md) - [Commits](https://github.com/python-zeroconf/python-zeroconf/compare/0.47.3...0.47.4) --- updated-dependencies: - dependency-name: zeroconf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 93a3592aed..5b37be15ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.5.1 click==8.1.3 esphome-dashboard==20230214.0 aioesphomeapi==13.5.1 -zeroconf==0.47.3 +zeroconf==0.47.4 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From be69b498804bdf8071832991bfc1ed484eae1c82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Mar 2023 18:38:47 +0000 Subject: [PATCH 729/838] Bump pytest-asyncio from 0.20.3 to 0.21.0 (#4599) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.20.3 to 0.21.0. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.20.3...v0.21.0) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 771086421e..3e59023d20 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==7.2.2 pytest-cov==4.0.0 pytest-mock==3.10.0 -pytest-asyncio==0.20.3 +pytest-asyncio==0.21.0 asyncmock==0.4.2 hypothesis==5.49.0 From e4ba3ff1db9a7689ad0967da3ddc9fa640f3563b Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Thu, 23 Mar 2023 19:41:14 +0100 Subject: [PATCH 730/838] Limit range on filter time period for remote_receiver (#4604) * Limit range on filter time period for remote_receiver * pylint --- esphome/components/remote_receiver/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 1ed9161ec7..d59ad5c7f1 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -12,7 +12,7 @@ from esphome.const import ( CONF_TOLERANCE, CONF_MEMORY_BLOCKS, ) -from esphome.core import CORE +from esphome.core import CORE, TimePeriod AUTO_LOAD = ["remote_base"] remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") @@ -33,9 +33,10 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.SplitDefault( CONF_BUFFER_SIZE, esp32="10000b", esp8266="1000b" ): cv.validate_bytes, - cv.Optional( - CONF_FILTER, default="50us" - ): cv.positive_time_period_microseconds, + cv.Optional(CONF_FILTER, default="50us"): cv.All( + cv.positive_time_period_microseconds, + cv.Range(max=TimePeriod(microseconds=255)), + ), cv.Optional( CONF_IDLE, default="10ms" ): cv.positive_time_period_microseconds, From 36c0e2416d63e44f3876410fa668676e41347fe1 Mon Sep 17 00:00:00 2001 From: Regev Brody Date: Sun, 26 Mar 2023 23:01:35 +0300 Subject: [PATCH 731/838] add select_schema to select component (#4545) * add select_schema to select component * add select_schema to select component * fix cr --- esphome/components/copy/select/__init__.py | 15 ++++++---- .../modbus_controller/select/__init__.py | 5 ++-- esphome/components/select/__init__.py | 29 +++++++++++++++++-- .../components/template/select/__init__.py | 7 +++-- esphome/components/tuya/select/__init__.py | 21 ++++++++------ 5 files changed, 55 insertions(+), 22 deletions(-) diff --git a/esphome/components/copy/select/__init__.py b/esphome/components/copy/select/__init__.py index 7d4c1c7705..6254ed03e1 100644 --- a/esphome/components/copy/select/__init__.py +++ b/esphome/components/copy/select/__init__.py @@ -14,12 +14,15 @@ from .. import copy_ns CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) -CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(CopySelect), - cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + select.select_schema(CopySelect) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = cv.All( inherit_property_from(CONF_ICON, CONF_SOURCE_ID), diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index f8ef61ddc4..5692fea3e3 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -64,9 +64,10 @@ INTEGER_SENSOR_VALUE_TYPE = { } CONFIG_SCHEMA = cv.All( - select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + select.select_schema(ModbusSelect) + .extend(cv.COMPONENT_SCHEMA) + .extend( { - cv.GenerateID(): cv.declare_id(ModbusSelect), cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), cv.Required(CONF_ADDRESS): cv.positive_int, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index b505d89c6f..760f7600b7 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -3,6 +3,8 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, CONF_ID, CONF_ON_VALUE, CONF_OPTION, @@ -14,6 +16,7 @@ from esphome.const import ( CONF_INDEX, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] @@ -43,8 +46,6 @@ SELECT_OPERATION_OPTIONS = { "LAST": SelectOperation.SELECT_OP_LAST, } -icon = cv.icon - SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { @@ -58,6 +59,30 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e } ) +_UNDEF = object() + + +def select_schema( + class_: MockObjClass = _UNDEF, + *, + entity_category: str = _UNDEF, + icon: str = _UNDEF, +): + schema = cv.Schema({}) + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + return SELECT_SCHEMA.extend(schema) + async def setup_select_core_(var, config, *, options: list[str]): await setup_entity(var, config) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 4eba77119d..d116cbb8ae 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -43,9 +43,9 @@ def validate(config): CONFIG_SCHEMA = cv.All( - select.SELECT_SCHEMA.extend( + select.select_schema(TemplateSelect) + .extend( { - cv.GenerateID(): cv.declare_id(TemplateSelect), cv.Required(CONF_OPTIONS): cv.All( cv.ensure_list(cv.string_strict), cv.Length(min=1) ), @@ -55,7 +55,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INITIAL_OPTION): cv.string_strict, cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } - ).extend(cv.polling_component_schema("60s")), + ) + .extend(cv.polling_component_schema("60s")), validate, ) diff --git a/esphome/components/tuya/select/__init__.py b/esphome/components/tuya/select/__init__.py index 3d65eda301..dc78b2c3db 100644 --- a/esphome/components/tuya/select/__init__.py +++ b/esphome/components/tuya/select/__init__.py @@ -25,15 +25,18 @@ def ensure_option_map(value): return value -CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TuyaSelect), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t, - cv.Required(CONF_OPTIONS): ensure_option_map, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + select.select_schema(TuyaSelect) + .extend( + { + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t, + cv.Required(CONF_OPTIONS): ensure_option_map, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): From 9ee661c1e4efb2bcf11f3490942332d0a5519535 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 26 Mar 2023 11:48:56 -1000 Subject: [PATCH 732/838] Add ability to clear the gatt cache (#4621) * Add ability to clear the gatt cache With BlueZ we can fully clear the cache when something goes wrong with the services, however since this is also a cache on the ESP32 we need to be able to clear the on device cache as well for the proxies since if something goes wrong with the service resolution it can cache the bad resolution on NVS forever. Our current client implementation is limited to clearing the memory cache in Home Assistant https://github.com/home-assistant/core/blob/89355e087952417a6824507fd3b197f9d0520e19/homeassistant/components/esphome/bluetooth/client.py#L512 related issue https://github.com/esphome/issues/issues/4156 https://github.com/esphome/aioesphomeapi/pull/410 * naming * lint * lint * naming * naming * naming * 88 now that 87 is taken * make const * Update esphome/components/api/api_frame_helper.cpp --- esphome/components/api/api.proto | 11 +++++ esphome/components/api/api_connection.cpp | 4 +- esphome/components/api/api_pb2.cpp | 45 +++++++++++++++++++ esphome/components/api/api_pb2.h | 14 ++++++ esphome/components/api/api_pb2_service.cpp | 8 ++++ esphome/components/api/api_pb2_service.h | 3 ++ esphome/components/api/api_server.cpp | 11 +++++ esphome/components/api/api_server.h | 1 + .../bluetooth_proxy/bluetooth_proxy.cpp | 7 +++ .../bluetooth_proxy/bluetooth_proxy.h | 8 ++++ 10 files changed, 111 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 1cebdd0cbe..a64aac52e2 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1156,6 +1156,7 @@ enum BluetoothDeviceRequestType { BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3; BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4; BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5; + BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6; } message BluetoothDeviceRequest { @@ -1359,3 +1360,13 @@ message BluetoothDeviceUnpairingResponse { bool success = 2; int32 error = 3; } + +message BluetoothDeviceClearCacheResponse { + option (id) = 88; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_BLUETOOTH_PROXY"; + + uint64 address = 1; + bool success = 2; + int32 error = 3; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 40a5a230a5..e607f45e3f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -953,7 +953,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.webserver_port = USE_WEBSERVER_PORT; #endif #ifdef USE_BLUETOOTH_PROXY - resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; + resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() + ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION + : bluetooth_proxy::PASSIVE_ONLY_VERSION; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 381f8b3c46..43587469af 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -400,6 +400,8 @@ const char *proto_enum_to_string(enums::Bluet return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; + case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: + return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE"; default: return "UNKNOWN"; } @@ -6060,6 +6062,49 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->address = value.as_uint64(); + return true; + } + case 2: { + this->success = value.as_bool(); + return true; + } + case 3: { + this->error = value.as_int32(); + return true; + } + default: + return false; + } +} +void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint64(1, this->address); + buffer.encode_bool(2, this->success); + buffer.encode_int32(3, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("BluetoothDeviceClearCacheResponse {\n"); + out.append(" address: "); + sprintf(buffer, "%llu", this->address); + out.append(buffer); + out.append("\n"); + + out.append(" success: "); + out.append(YESNO(this->success)); + out.append("\n"); + + out.append(" error: "); + sprintf(buffer, "%d", this->error); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e9025142e9..ff581cac6f 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -163,6 +163,7 @@ enum BluetoothDeviceRequestType : uint32_t { BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, + BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, }; } // namespace enums @@ -1554,6 +1555,19 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class BluetoothDeviceClearCacheResponse : public ProtoMessage { + public: + uint64_t address{0}; + bool success{false}; + int32_t error{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 7ee9e56192..73015fa914 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -441,6 +441,14 @@ bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const Blu return this->send_message_(msg, 86); } #endif +#ifdef USE_BLUETOOTH_PROXY +bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 88); +} +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index f1879b2dba..7f19292ff3 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -215,6 +215,9 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); +#endif +#ifdef USE_BLUETOOTH_PROXY + bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index acde0966ba..c60766b364 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -331,6 +331,17 @@ void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, } } +void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) { + BluetoothDeviceClearCacheResponse call; + call.address = address; + call.success = success; + call.error = error; + + for (auto &client : this->clients_) { + client->send_bluetooth_device_clear_cache_response(call); + } +} + void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { BluetoothConnectionsFreeResponse call; call.free = free; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 5f92e6b058..db87affdb8 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -80,6 +80,7 @@ class APIServer : public Component, public Controller { void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); + void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK); void send_bluetooth_connections_free(uint8_t free, uint8_t limit); void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 55fabf05ef..76950c944e 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -306,6 +306,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); break; } + case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: { + esp_bd_addr_t address; + uint64_to_bd_addr(msg.address, address); + esp_err_t ret = esp_ble_gattc_cache_clean(address); + api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret); + break; + } } } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index b99e9a8527..a582abc8a3 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -68,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Version 1: Initial version without active connections +// Version 2: Support for active connections +// Version 3: New connection API +// Version 4: Pairing support +// Version 5: Cache clear support +static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5; +static const uint32_t PASSIVE_ONLY_VERSION = 1; + } // namespace bluetooth_proxy } // namespace esphome From 29e7d00894cad7086dd7910ce3a216972c3d015c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 10:51:37 +1300 Subject: [PATCH 733/838] Bump actions/stale from 7 to 8 (#4615) Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a2ba086394..f5d291b49f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: days-before-pr-stale: 90 days-before-pr-close: 7 @@ -38,7 +38,7 @@ jobs: close-issues: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: days-before-pr-stale: -1 days-before-pr-close: -1 From 806e43c34c09fe28a3cc0d7dcf1609a30781d4f0 Mon Sep 17 00:00:00 2001 From: tracestep <16390082+tracestep@users.noreply.github.com> Date: Sun, 26 Mar 2023 18:59:57 -0300 Subject: [PATCH 734/838] SX1509 minimum loop period (fixes esphome/issues#4325) (#4613) * Minimum loop period (fixes esphome/issues#4325) * clang-tidy suggestions * More clang-tidy suggestions --- esphome/components/sx1509/sx1509.cpp | 3 +++ esphome/components/sx1509/sx1509.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 60cbae6aa6..d0a84b99ff 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -42,6 +42,9 @@ void SX1509Component::dump_config() { void SX1509Component::loop() { if (this->has_keypad_) { + if (millis() - this->last_loop_timestamp_ < min_loop_period_) + return; + this->last_loop_timestamp_ = millis(); uint16_t key_data = this->read_key_data(); for (auto *binary_sensor : this->keypad_binary_sensors_) binary_sensor->process(key_data); diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 50230b1086..8e3b41e233 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -69,6 +69,9 @@ class SX1509Component : public Component, public i2c::I2CDevice { uint8_t debounce_time_ = 1; std::vector keypad_binary_sensors_; + uint32_t last_loop_timestamp_ = 0; + const uint32_t min_loop_period_ = 15; // ms + void setup_keypad_(); void set_debounce_config_(uint8_t config_value); void set_debounce_time_(uint8_t time); From e542e75b9e60bf285428ef14c9e87134ba5b68e5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:44:56 +1300 Subject: [PATCH 735/838] Require step to be set when calling register_number (#4622) --- esphome/components/number/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 70c53cace3..7db973612b 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -204,14 +204,13 @@ def number_schema( async def setup_number_core_( - var, config, *, min_value: float, max_value: float, step: Optional[float] + var, config, *, min_value: float, max_value: float, step: float ): await setup_entity(var, config) cg.add(var.traits.set_min_value(min_value)) cg.add(var.traits.set_max_value(max_value)) - if step is not None: - cg.add(var.traits.set_step(step)) + cg.add(var.traits.set_step(step)) cg.add(var.traits.set_mode(config[CONF_MODE])) @@ -239,7 +238,7 @@ async def setup_number_core_( async def register_number( - var, config, *, min_value: float, max_value: float, step: Optional[float] = None + var, config, *, min_value: float, max_value: float, step: float ): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) From 56504692af70d34a9b2f1219fac0890d64f1f84a Mon Sep 17 00:00:00 2001 From: Berend Haan Date: Mon, 27 Mar 2023 00:48:17 +0200 Subject: [PATCH 736/838] Lower range of CONF_FREQUENCY (#4619) --- esphome/components/esp32_camera/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d6a744d24d..4cbdf7ca5c 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -156,7 +156,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( - cv.frequency, cv.Range(min=10e6, max=20e6) + cv.frequency, cv.Range(min=8e6, max=20e6) ), } ), From c2756d57d8878dc7d91f36bcba8a0cf3d0b9d7a1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:49:09 +1300 Subject: [PATCH 737/838] Allow entity names to be set to None (#4607) * Allow entity names to be set to None so they take on the device friendly_name automatically * Use empty --- esphome/components/api/api_connection.cpp | 39 ++++++++++++++------- esphome/components/esp32_improv/__init__.py | 10 +----- esphome/config_validation.py | 29 ++++++++++++++- esphome/core/entity_base.cpp | 9 ++++- esphome/core/entity_base.h | 4 +++ 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index e607f45e3f..77ba96291a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -180,7 +180,8 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ ListEntitiesBinarySensorResponse msg; msg.object_id = binary_sensor->get_object_id(); msg.key = binary_sensor->get_object_id_hash(); - msg.name = binary_sensor->get_name(); + if (binary_sensor->has_own_name()) + msg.name = binary_sensor->get_name(); msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor); msg.device_class = binary_sensor->get_device_class(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); @@ -212,7 +213,8 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { ListEntitiesCoverResponse msg; msg.key = cover->get_object_id_hash(); msg.object_id = cover->get_object_id(); - msg.name = cover->get_name(); + if (cover->has_own_name()) + msg.name = cover->get_name(); msg.unique_id = get_default_unique_id("cover", cover); msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); @@ -275,7 +277,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) { ListEntitiesFanResponse msg; msg.key = fan->get_object_id_hash(); msg.object_id = fan->get_object_id(); - msg.name = fan->get_name(); + if (fan->has_own_name()) + msg.name = fan->get_name(); msg.unique_id = get_default_unique_id("fan", fan); msg.supports_oscillation = traits.supports_oscillation(); msg.supports_speed = traits.supports_speed(); @@ -337,7 +340,8 @@ bool APIConnection::send_light_info(light::LightState *light) { ListEntitiesLightResponse msg; msg.key = light->get_object_id_hash(); msg.object_id = light->get_object_id(); - msg.name = light->get_name(); + if (light->has_own_name()) + msg.name = light->get_name(); msg.unique_id = get_default_unique_id("light", light); msg.disabled_by_default = light->is_disabled_by_default(); @@ -418,7 +422,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { ListEntitiesSensorResponse msg; msg.key = sensor->get_object_id_hash(); msg.object_id = sensor->get_object_id(); - msg.name = sensor->get_name(); + if (sensor->has_own_name()) + msg.name = sensor->get_name(); msg.unique_id = sensor->unique_id(); if (msg.unique_id.empty()) msg.unique_id = get_default_unique_id("sensor", sensor); @@ -448,7 +453,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { ListEntitiesSwitchResponse msg; msg.key = a_switch->get_object_id_hash(); msg.object_id = a_switch->get_object_id(); - msg.name = a_switch->get_name(); + if (a_switch->has_own_name()) + msg.name = a_switch->get_name(); msg.unique_id = get_default_unique_id("switch", a_switch); msg.icon = a_switch->get_icon(); msg.assumed_state = a_switch->assumed_state(); @@ -533,7 +539,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { ListEntitiesClimateResponse msg; msg.key = climate->get_object_id_hash(); msg.object_id = climate->get_object_id(); - msg.name = climate->get_name(); + if (climate->has_own_name()) + msg.name = climate->get_name(); msg.unique_id = get_default_unique_id("climate", climate); msg.disabled_by_default = climate->is_disabled_by_default(); @@ -611,7 +618,8 @@ bool APIConnection::send_number_info(number::Number *number) { ListEntitiesNumberResponse msg; msg.key = number->get_object_id_hash(); msg.object_id = number->get_object_id(); - msg.name = number->get_name(); + if (number->has_own_name()) + msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); @@ -652,7 +660,8 @@ bool APIConnection::send_select_info(select::Select *select) { ListEntitiesSelectResponse msg; msg.key = select->get_object_id_hash(); msg.object_id = select->get_object_id(); - msg.name = select->get_name(); + if (select->has_own_name()) + msg.name = select->get_name(); msg.unique_id = get_default_unique_id("select", select); msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); @@ -679,7 +688,8 @@ bool APIConnection::send_button_info(button::Button *button) { ListEntitiesButtonResponse msg; msg.key = button->get_object_id_hash(); msg.object_id = button->get_object_id(); - msg.name = button->get_name(); + if (button->has_own_name()) + msg.name = button->get_name(); msg.unique_id = get_default_unique_id("button", button); msg.icon = button->get_icon(); msg.disabled_by_default = button->is_disabled_by_default(); @@ -710,7 +720,8 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) { ListEntitiesLockResponse msg; msg.key = a_lock->get_object_id_hash(); msg.object_id = a_lock->get_object_id(); - msg.name = a_lock->get_name(); + if (a_lock->has_own_name()) + msg.name = a_lock->get_name(); msg.unique_id = get_default_unique_id("lock", a_lock); msg.icon = a_lock->get_icon(); msg.assumed_state = a_lock->traits.get_assumed_state(); @@ -755,7 +766,8 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play ListEntitiesMediaPlayerResponse msg; msg.key = media_player->get_object_id_hash(); msg.object_id = media_player->get_object_id(); - msg.name = media_player->get_name(); + if (media_player->has_own_name()) + msg.name = media_player->get_name(); msg.unique_id = get_default_unique_id("media_player", media_player); msg.icon = media_player->get_icon(); msg.disabled_by_default = media_player->is_disabled_by_default(); @@ -799,7 +811,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { ListEntitiesCameraResponse msg; msg.key = camera->get_object_id_hash(); msg.object_id = camera->get_object_id(); - msg.name = camera->get_name(); + if (camera->has_own_name()) + msg.name = camera->get_name(); msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 7170a6dabf..ae7f0b6427 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -22,20 +22,12 @@ ESP32ImprovComponent = esp32_improv_ns.class_( ) -def validate_none_(value): - if value in ("none", "None"): - return None - if cv.boolean(value) is False: - return None - raise cv.Invalid("Must be none") - - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), cv.Required(CONF_AUTHORIZER): cv.Any( - validate_none_, cv.use_id(binary_sensor.BinarySensor) + cv.none, cv.use_id(binary_sensor.BinarySensor) ), cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput), cv.Optional( diff --git a/esphome/config_validation.py b/esphome/config_validation.py index f0bbc368b8..4b822b46c9 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1514,6 +1514,8 @@ def _entity_base_validator(config): config[CONF_NAME] = id.id config[CONF_INTERNAL] = True return config + if config[CONF_NAME] is None: + config[CONF_NAME] = "" return config @@ -1573,6 +1575,23 @@ def validate_registry_entry(name, registry): return validator +def none(value): + if value in ("none", "None"): + return None + if boolean(value) is False: + return None + raise Invalid("Must be none") + + +def requires_friendly_name(message): + def validate(value): + if CORE.friendly_name is None: + raise Invalid(message) + return value + + return validate + + def validate_registry(name, registry): return ensure_list(validate_registry_entry(name, registry)) @@ -1632,7 +1651,15 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( ENTITY_BASE_SCHEMA = Schema( { - Optional(CONF_NAME): string, + Optional(CONF_NAME): Any( + All( + none, + requires_friendly_name( + "Name cannot be None when esphome->friendly_name is not set!" + ), + ), + string, + ), Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 6f88f069b3..49a73854a1 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -1,4 +1,5 @@ #include "esphome/core/entity_base.h" +#include "esphome/core/application.h" #include "esphome/core/helpers.h" namespace esphome { @@ -10,7 +11,13 @@ EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_o // Entity Name const std::string &EntityBase::get_name() const { return this->name_; } void EntityBase::set_name(const std::string &name) { - this->name_ = name; + if (name.empty()) { + this->name_ = App.get_friendly_name(); + this->has_own_name_ = false; + } else { + this->name_ = name; + this->has_own_name_ = true; + } this->calc_object_id_(); } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index f6aae4e978..5ab1f7b424 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -21,6 +21,9 @@ class EntityBase { const std::string &get_name() const; void set_name(const std::string &name); + // Get whether this Entity has its own name or it should use the device friendly_name. + bool has_own_name() const { return this->has_own_name_; } + // Get the sanitized name of this Entity as an ID. Caching it internally. const std::string &get_object_id(); @@ -52,6 +55,7 @@ class EntityBase { void calc_object_id_(); std::string name_; + bool has_own_name_{false}; std::string object_id_; const char *icon_c_str_{nullptr}; uint32_t object_id_hash_; From 06f83bf1c00a7c07f1fd1981bfca7a2663b4596c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:50:33 +1300 Subject: [PATCH 738/838] Fix platform restriction for bme680_bsec (#4616) * Fix platform restriction for bme680_bsec * Fix * revert spi change * Add comment back * Dont crash on rp2040 platform --- esphome/components/bme680_bsec/__init__.py | 41 +++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index c9813c4974..085d2a574b 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c +from esphome.components import i2c, esp32 from esphome.const import CONF_ID CODEOWNERS = ["@trvrnrth"] @@ -32,22 +32,31 @@ BME680BSECComponent = bme680_bsec_ns.class_( "BME680BSECComponent", cg.Component, i2c.I2CDevice ) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(BME680BSECComponent), - cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, - cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( - IAQ_MODE_OPTIONS, upper=True - ), - cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( - SAMPLE_RATE_OPTIONS, upper=True - ), - cv.Optional( - CONF_STATE_SAVE_INTERVAL, default="6hours" - ): cv.positive_time_period_minutes, - }, +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BME680BSECComponent), + cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature, + cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( + IAQ_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( + SAMPLE_RATE_OPTIONS, upper=True + ), + cv.Optional( + CONF_STATE_SAVE_INTERVAL, default="6hours" + ): cv.positive_time_period_minutes, + } + ).extend(i2c.i2c_device_schema(0x76)), cv.only_with_arduino, -).extend(i2c.i2c_device_schema(0x76)) + cv.Any( + cv.only_on_esp8266, + cv.All( + cv.only_on_esp32, + esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]), + ), + ), +) async def to_code(config): From 9d9725144dd5d16ec7cde58f0afa50218242da1a Mon Sep 17 00:00:00 2001 From: Kai Gerken Date: Sun, 19 Mar 2023 19:31:05 +0100 Subject: [PATCH 739/838] Fix compile error on pzemdc.h (#4583) --- esphome/components/pzemdc/pzemdc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pzemdc/pzemdc.h b/esphome/components/pzemdc/pzemdc.h index 21676e3422..b91ab4c0a5 100644 --- a/esphome/components/pzemdc/pzemdc.h +++ b/esphome/components/pzemdc/pzemdc.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/modbus/modbus.h" From 8d3896172d8a0fe3c7384b42b40f7b31599ccc05 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:56:02 +1300 Subject: [PATCH 740/838] Swap curly brackets for round on LockGuard (#4610) --- esphome/core/helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a107b1b849..8950cc3b8a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -547,7 +547,7 @@ class Mutex { */ class LockGuard { public: - LockGuard(Mutex &mutex) : mutex_{mutex} { mutex_.lock(); } + LockGuard(Mutex &mutex) : mutex_(mutex) { mutex_.lock(); } ~LockGuard() { mutex_.unlock(); } private: From 74fe135c9ca5a58e331b3eeffda67b50b5b47005 Mon Sep 17 00:00:00 2001 From: guillempages Date: Wed, 22 Mar 2023 09:05:09 +0100 Subject: [PATCH 741/838] Fix animation resizing (#4608) Animation resizing in RGB24 format is causing an error "Image cannot be resized to a bigger size". Other image types do not show the issue, and the only difference is the "image.thumbnail" call. Removed the call and tested; the animation is shown with the desired size. --- esphome/components/animation/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index ce9f057496..68c3eee132 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -76,8 +76,6 @@ async def to_code(config): pos = 0 for frameIndex in range(frames): image.seek(frameIndex) - if CONF_RESIZE in config: - image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") if CONF_RESIZE in config: frame = frame.resize([width, height]) From 358c59bd8d63dc94bb1aeb22d90ad7dddefd731c Mon Sep 17 00:00:00 2001 From: tracestep <16390082+tracestep@users.noreply.github.com> Date: Sun, 26 Mar 2023 18:59:57 -0300 Subject: [PATCH 742/838] SX1509 minimum loop period (fixes esphome/issues#4325) (#4613) * Minimum loop period (fixes esphome/issues#4325) * clang-tidy suggestions * More clang-tidy suggestions --- esphome/components/sx1509/sx1509.cpp | 3 +++ esphome/components/sx1509/sx1509.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 60cbae6aa6..d0a84b99ff 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -42,6 +42,9 @@ void SX1509Component::dump_config() { void SX1509Component::loop() { if (this->has_keypad_) { + if (millis() - this->last_loop_timestamp_ < min_loop_period_) + return; + this->last_loop_timestamp_ = millis(); uint16_t key_data = this->read_key_data(); for (auto *binary_sensor : this->keypad_binary_sensors_) binary_sensor->process(key_data); diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 50230b1086..8e3b41e233 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -69,6 +69,9 @@ class SX1509Component : public Component, public i2c::I2CDevice { uint8_t debounce_time_ = 1; std::vector keypad_binary_sensors_; + uint32_t last_loop_timestamp_ = 0; + const uint32_t min_loop_period_ = 15; // ms + void setup_keypad_(); void set_debounce_config_(uint8_t config_value); void set_debounce_time_(uint8_t time); From f862b479e71e4cd0766960cf1adbd69567dd8b69 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:58:29 +1300 Subject: [PATCH 743/838] Bump version to 2023.3.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 35d1d3fa03..bca0973bb9 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.3.1" +__version__ = "2023.3.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 1f50bd064973da339ead15d9b35cea789f1426d7 Mon Sep 17 00:00:00 2001 From: Alfredo Date: Tue, 28 Mar 2023 00:08:26 +0200 Subject: [PATCH 744/838] Fix EzoCommandType enum (#4593) * Fix EzoCommandType enum Assign explicit value to EZO_CALIBRATION, and rescale all subsequent values. * Remove enum values --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ezo/ezo.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/ezo/ezo.h b/esphome/components/ezo/ezo.h index 72e82a574f..28b46643e9 100644 --- a/esphome/components/ezo/ezo.h +++ b/esphome/components/ezo/ezo.h @@ -12,14 +12,14 @@ static const char *const TAG = "ezo.sensor"; enum EzoCommandType : uint8_t { EZO_READ = 0, - EZO_LED = 1, - EZO_DEVICE_INFORMATION = 2, - EZO_SLOPE = 3, + EZO_LED, + EZO_DEVICE_INFORMATION, + EZO_SLOPE, EZO_CALIBRATION, - EZO_SLEEP = 4, - EZO_I2C = 5, - EZO_T = 6, - EZO_CUSTOM = 7 + EZO_SLEEP, + EZO_I2C, + EZO_T, + EZO_CUSTOM }; enum EzoCalibrationType : uint8_t { EZO_CAL_LOW = 0, EZO_CAL_MID = 1, EZO_CAL_HIGH = 2 }; From cb2fcaa9b1e076bce939700e8cc9e3d437c938a7 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 28 Mar 2023 08:38:56 +0200 Subject: [PATCH 745/838] `EntityBase` Name can stay in flash. (#4594) * `EntityBase`can stay in flash. * Trying to please the CI. --------- Co-authored-by: Your Name --- esphome/components/button/button.cpp | 3 - esphome/components/button/button.h | 3 - esphome/components/climate/climate.cpp | 5 - esphome/components/climate/climate.h | 5 - esphome/components/cover/cover.cpp | 3 +- esphome/components/cover/cover.h | 1 - .../components/esp32_camera/esp32_camera.cpp | 3 +- .../components/esp32_camera/esp32_camera.h | 1 - esphome/components/fan/fan.cpp | 3 - esphome/components/fan/fan.h | 4 - esphome/components/fan/fan_state.h | 1 - esphome/components/light/light_state.cpp | 1 - esphome/components/light/light_state.h | 3 - esphome/components/lock/lock.cpp | 3 +- esphome/components/lock/lock.h | 1 - esphome/components/partition/light.py | 2 +- esphome/components/sensor/sensor.cpp | 3 +- esphome/components/sensor/sensor.h | 1 - esphome/components/switch/switch.cpp | 3 +- esphome/components/switch/switch.h | 1 - .../components/text_sensor/text_sensor.cpp | 3 - esphome/components/text_sensor/text_sensor.h | 3 - esphome/core/entity_base.cpp | 12 +- esphome/core/entity_base.h | 10 +- esphome/core/string_ref.cpp | 12 ++ esphome/core/string_ref.h | 134 ++++++++++++++++++ 26 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 esphome/core/string_ref.cpp create mode 100644 esphome/core/string_ref.h diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 3d58780d6d..dfa417de7b 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -6,9 +6,6 @@ namespace button { static const char *const TAG = "button"; -Button::Button(const std::string &name) : EntityBase(name) {} -Button::Button() : Button("") {} - void Button::press() { ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); this->press_action(); diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 3b5f338887..a4902810b2 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -28,9 +28,6 @@ namespace button { */ class Button : public EntityBase { public: - explicit Button(); - explicit Button(const std::string &name); - /** Press this button. This is called by the front-end. * * For implementing buttons, please override press_action. diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 37572ae913..b4d5ee9685 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -453,12 +453,7 @@ void Climate::set_visual_temperature_step_override(float target, float current) this->visual_target_temperature_step_override_ = target; this->visual_current_temperature_step_override_ = current; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -Climate::Climate(const std::string &name) : EntityBase(name) {} -#pragma GCC diagnostic pop -Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 520036f718..43bd71657d 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -166,11 +166,6 @@ struct ClimateDeviceRestoreState { */ class Climate : public EntityBase { public: - /// Construct a climate device with empty name (will be set later). - Climate(); - /// Construct a climate device with a name. - Climate(const std::string &name); - /// The active mode of the climate device. ClimateMode mode{CLIMATE_MODE_OFF}; /// The active state of the climate device. diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 902e9bca94..24dd88b698 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) { } } -Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {} +Cover::Cover() : position{COVER_OPEN} {} CoverCall::CoverCall(Cover *parent) : parent_(parent) {} CoverCall &CoverCall::set_command(const char *command) { @@ -204,7 +204,6 @@ optional Cover::restore_state_() { return {}; return recovered; } -Cover::Cover() : Cover("") {} std::string Cover::get_device_class() { if (this->device_class_override_.has_value()) return *this->device_class_override_; diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8ae9cfc8dd..c6a420fa97 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -111,7 +111,6 @@ const char *cover_operation_to_str(CoverOperation op); class Cover : public EntityBase { public: explicit Cover(); - explicit Cover(const std::string &name); /// The current operation of the cover (idle, opening, closing). CoverOperation current_operation{COVER_OPERATION_IDLE}; diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 4a53748213..e4020a902e 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -202,7 +202,7 @@ void ESP32Camera::loop() { float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } /* ---------------- constructors ---------------- */ -ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { +ESP32Camera::ESP32Camera() { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; this->config_.pin_xclk = -1; @@ -215,7 +215,6 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } -ESP32Camera::ESP32Camera() : ESP32Camera("") {} /* ---------------- setters ---------------- */ /* set pin assignment */ diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 62fdbabd06..5f88c6fda8 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -103,7 +103,6 @@ class CameraImageReader { /* ---------------- ESP32Camera class ---------------- */ class ESP32Camera : public Component, public EntityBase { public: - ESP32Camera(const std::string &name); ESP32Camera(); /* setters */ diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index c8a3064f6c..87566bad4a 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -80,9 +80,6 @@ void FanRestoreState::apply(Fan &fan) { fan.publish_state(); } -Fan::Fan() : EntityBase("") {} -Fan::Fan(const std::string &name) : EntityBase(name) {} - FanCall Fan::turn_on() { return this->make_call().set_state(true); } FanCall Fan::turn_off() { return this->make_call().set_state(false); } FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 337bb3935a..f9d317e675 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -99,10 +99,6 @@ struct FanRestoreState { class Fan : public EntityBase { public: - Fan(); - /// Construct the fan with name. - explicit Fan(const std::string &name); - /// The current on/off state of the fan. bool state{false}; /// The current oscillation state of the fan. diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index 044ee59736..5926e700b0 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -15,7 +15,6 @@ enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component { public: FanState() = default; - explicit FanState(const std::string &name) : Fan(name) {} /// Get the traits of this fan. FanTraits get_traits() override { return this->traits_; } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 64c29a346b..50ebd8882b 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -8,7 +8,6 @@ namespace light { static const char *const TAG = "light"; -LightState::LightState(const std::string &name, LightOutput *output) : EntityBase(name), output_(output) {} LightState::LightState(LightOutput *output) : output_(output) {} LightTraits LightState::get_traits() { return this->output_->get_traits(); } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 81f8be7207..ac4718ade5 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -33,9 +33,6 @@ enum LightRestoreMode { */ class LightState : public EntityBase, public Component { public: - /// Construct this LightState using the provided traits and name. - LightState(const std::string &name, LightOutput *output); - LightState(LightOutput *output); LightTraits get_traits(); diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index dbb8a941ad..ddc5445349 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -24,8 +24,7 @@ const char *lock_state_to_string(LockState state) { } } -Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {} -Lock::Lock() : Lock("") {} +Lock::Lock() : state(LOCK_STATE_NONE) {} LockCall Lock::make_call() { return LockCall(this); } void Lock::lock() { diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 52256691f6..7a98187a4f 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -103,7 +103,6 @@ class LockCall { class Lock : public EntityBase { public: explicit Lock(); - explicit Lock(const std::string &name); /** Make a lock device control call, this is used to control the lock device, see the LockCall description * for more info. diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 73cda2c926..8e45915cf3 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -102,7 +102,7 @@ async def to_code(config): conf[CONF_ADDRESSABLE_LIGHT_ID], await cg.get_variable(conf[CONF_SINGLE_LIGHT_ID]), ) - light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) + light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], wrapper) await cg.register_component(light_state, conf) segments.append(AddressableSegment(light_state, 0, 1, False)) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 0de452b784..fc66e03d6b 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -20,8 +20,7 @@ std::string state_class_to_string(StateClass state_class) { } } -Sensor::Sensor(const std::string &name) : EntityBase(name), state(NAN), raw_state(NAN) {} -Sensor::Sensor() : Sensor("") {} +Sensor::Sensor() : state(NAN), raw_state(NAN) {} std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index e3651752cf..4308eafb12 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -57,7 +57,6 @@ std::string state_class_to_string(StateClass state_class); class Sensor : public EntityBase { public: explicit Sensor(); - explicit Sensor(const std::string &name); /// Get the unit of measurement, using the manual override if set. std::string get_unit_of_measurement(); diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index caa072b1ea..72e7add158 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -6,8 +6,7 @@ namespace switch_ { static const char *const TAG = "switch"; -Switch::Switch(const std::string &name) : EntityBase(name), state(false) {} -Switch::Switch() : Switch("") {} +Switch::Switch() : state(false) {} void Switch::turn_on() { ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str()); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index b89d8db6a5..8bea3b36db 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -32,7 +32,6 @@ enum SwitchRestoreMode { class Switch : public EntityBase { public: explicit Switch(); - explicit Switch(const std::string &name); /** Publish a state to the front-end from the back-end. * diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index d76ab7e27d..f10cd50267 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -6,9 +6,6 @@ namespace text_sensor { static const char *const TAG = "text_sensor"; -TextSensor::TextSensor() : TextSensor("") {} -TextSensor::TextSensor(const std::string &name) : EntityBase(name) {} - void TextSensor::publish_state(const std::string &state) { this->raw_state = state; this->raw_callback_.call(state); diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index cf7b6b86ef..996af02f7e 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -30,9 +30,6 @@ namespace text_sensor { class TextSensor : public EntityBase { public: - explicit TextSensor(); - explicit TextSensor(const std::string &name); - /// Getter-syntax for .state. std::string get_state() const; /// Getter-syntax for .raw_state diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 49a73854a1..19ab57349d 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -6,16 +6,14 @@ namespace esphome { static const char *const TAG = "entity_base"; -EntityBase::EntityBase(std::string name) : name_(std::move(name)) { this->calc_object_id_(); } - // Entity Name -const std::string &EntityBase::get_name() const { return this->name_; } -void EntityBase::set_name(const std::string &name) { - if (name.empty()) { - this->name_ = App.get_friendly_name(); +const StringRef &EntityBase::get_name() const { return this->name_; } +void EntityBase::set_name(const char *name) { + this->name_ = StringRef(name); + if (this->name_.empty()) { + this->name_ = StringRef(App.get_friendly_name()); this->has_own_name_ = false; } else { - this->name_ = name; this->has_own_name_ = true; } this->calc_object_id_(); diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 5ab1f7b424..e25aab21a9 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -2,6 +2,7 @@ #include #include +#include "string_ref.h" namespace esphome { @@ -14,12 +15,9 @@ enum EntityCategory : uint8_t { // The generic Entity base class that provides an interface common to all Entities. class EntityBase { public: - EntityBase() : EntityBase("") {} - explicit EntityBase(std::string name); - // Get/set the name of this Entity - const std::string &get_name() const; - void set_name(const std::string &name); + const StringRef &get_name() const; + void set_name(const char *name); // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->has_own_name_; } @@ -54,7 +52,7 @@ class EntityBase { virtual uint32_t hash_base() { return 0L; } void calc_object_id_(); - std::string name_; + StringRef name_; bool has_own_name_{false}; std::string object_id_; const char *icon_c_str_{nullptr}; diff --git a/esphome/core/string_ref.cpp b/esphome/core/string_ref.cpp new file mode 100644 index 0000000000..ce1e33cbb7 --- /dev/null +++ b/esphome/core/string_ref.cpp @@ -0,0 +1,12 @@ +#include "string_ref.h" + +namespace esphome { + +#ifdef USE_JSON + +// NOLINTNEXTLINE(readability-identifier-naming) +void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); } + +#endif // USE_JSON + +} // namespace esphome diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h new file mode 100644 index 0000000000..5940a7ee65 --- /dev/null +++ b/esphome/core/string_ref.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include "esphome/core/defines.h" + +#ifdef USE_JSON +#include "esphome/components/json/json_util.h" +#endif // USE_JSON + +namespace esphome { + +/** + * StringRef is a reference to a string owned by something else. So it behaves like simple string, but it does not own + * pointer. When it is default constructed, it has empty string. You can freely copy or move around this struct, but + * never free its pointer. str() function can be used to export the content as std::string. StringRef is adopted from + * + */ +class StringRef { + public: + using traits_type = std::char_traits; + using value_type = traits_type::char_type; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = std::allocator_traits::difference_type; + using const_reference = const value_type &; + using const_pointer = const value_type *; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + constexpr StringRef() : base_(""), len_(0) {} + explicit StringRef(const std::string &s) : base_(s.c_str()), len_(s.size()) {} + explicit StringRef(const char *s) : base_(s), len_(strlen(s)) {} + constexpr StringRef(const char *s, size_t n) : base_(s), len_(n) {} + template + constexpr StringRef(const CharT *s, size_t n) : base_(reinterpret_cast(s)), len_(n) {} + template + StringRef(InputIt first, InputIt last) + : base_(reinterpret_cast(&*first)), len_(std::distance(first, last)) {} + template + StringRef(InputIt *first, InputIt *last) + : base_(reinterpret_cast(first)), len_(std::distance(first, last)) {} + template constexpr static StringRef from_lit(const CharT (&s)[N]) { + return StringRef{s, N - 1}; + } + static StringRef from_maybe_nullptr(const char *s) { + if (s == nullptr) { + return StringRef(); + } + + return StringRef(s); + } + + constexpr const_iterator begin() const { return base_; }; + constexpr const_iterator cbegin() const { return base_; }; + + constexpr const_iterator end() const { return base_ + len_; }; + constexpr const_iterator cend() const { return base_ + len_; }; + + const_reverse_iterator rbegin() const { return const_reverse_iterator{base_ + len_}; } + const_reverse_iterator crbegin() const { return const_reverse_iterator{base_ + len_}; } + + const_reverse_iterator rend() const { return const_reverse_iterator{base_}; } + const_reverse_iterator crend() const { return const_reverse_iterator{base_}; } + + constexpr const char *c_str() const { return base_; } + constexpr size_type size() const { return len_; } + constexpr bool empty() const { return len_ == 0; } + constexpr const_reference operator[](size_type pos) const { return *(base_ + pos); } + + std::string str() const { return std::string(base_, len_); } + const uint8_t *byte() const { return reinterpret_cast(base_); } + + operator std::string() const { return str(); } + + private: + const char *base_; + size_type len_; +}; + +inline bool operator==(const StringRef &lhs, const StringRef &rhs) { + return lhs.size() == rhs.size() && std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const StringRef &lhs, const std::string &rhs) { + return lhs.size() == rhs.size() && std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const std::string &lhs, const StringRef &rhs) { return rhs == lhs; } + +inline bool operator==(const StringRef &lhs, const char *rhs) { + return lhs.size() == strlen(rhs) && std::equal(std::begin(lhs), std::end(lhs), rhs); +} + +inline bool operator==(const char *lhs, const StringRef &rhs) { return rhs == lhs; } + +inline bool operator!=(const StringRef &lhs, const StringRef &rhs) { return !(lhs == rhs); } + +inline bool operator!=(const StringRef &lhs, const std::string &rhs) { return !(lhs == rhs); } + +inline bool operator!=(const std::string &lhs, const StringRef &rhs) { return !(rhs == lhs); } + +inline bool operator!=(const StringRef &lhs, const char *rhs) { return !(lhs == rhs); } + +inline bool operator!=(const char *lhs, const StringRef &rhs) { return !(rhs == lhs); } + +inline bool operator<(const StringRef &lhs, const StringRef &rhs) { + return std::lexicographical_compare(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs)); +} + +inline std::string &operator+=(std::string &lhs, const StringRef &rhs) { + lhs.append(rhs.c_str(), rhs.size()); + return lhs; +} + +inline std::string operator+(const char *lhs, const StringRef &rhs) { + auto str = std::string(lhs); + str.append(rhs.c_str(), rhs.size()); + return str; +} + +inline std::string operator+(const StringRef &lhs, const char *rhs) { + auto str = lhs.str(); + str.append(rhs); + return str; +} + +#ifdef USE_JSON +// NOLINTNEXTLINE(readability-identifier-naming) +void convertToJson(const StringRef &src, JsonVariant dst); +#endif // USE_JSON + +} // namespace esphome From 922344811f66c18b08aff21c804b0ba8c4c1e4b4 Mon Sep 17 00:00:00 2001 From: richardhopton Date: Tue, 28 Mar 2023 01:54:58 -0700 Subject: [PATCH 746/838] feat: Add support to unsubscribe from BLE advertisements (#4620) * feat: Add support to unsubscribe from BLE advertisements * Fix tests & clang --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 7 +++++++ esphome/components/api/api_connection.h | 3 +++ esphome/components/api/api_pb2.cpp | 6 ++++++ esphome/components/api/api_pb2.h | 9 +++++++++ esphome/components/api/api_pb2_service.cpp | 21 +++++++++++++++++++++ esphome/components/api/api_pb2_service.h | 6 ++++++ 6 files changed, 52 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index a64aac52e2..fd00317282 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -53,6 +53,8 @@ service APIConnection { rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {} rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {} rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} + rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} + } @@ -1361,6 +1363,11 @@ message BluetoothDeviceUnpairingResponse { int32 error = 3; } +message UnsubscribeBluetoothLEAdvertisementsRequest { + option (id) = 87; + option (source) = SOURCE_CLIENT; +} + message BluetoothDeviceClearCacheResponse { option (id) = 88; option (source) = SOURCE_SERVER; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index b2076635b0..bc6ae5d7c3 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -153,6 +153,9 @@ class APIConnection : public APIServerConnection { void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { this->bluetooth_le_advertisement_subscription_ = true; } + void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { + this->bluetooth_le_advertisement_subscription_ = false; + } bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } bool is_connection_setup() override { return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 43587469af..6260020064 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -6062,6 +6062,12 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { out.append("}"); } #endif +void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { + out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); +} +#endif bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 1: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ff581cac6f..ade9b9cc8f 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1555,6 +1555,15 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; class BluetoothDeviceClearCacheResponse : public ProtoMessage { public: uint64_t address{0}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 73015fa914..e613cbcad9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -813,6 +813,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, #endif break; } + case 87: { + UnsubscribeBluetoothLEAdvertisementsRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); +#endif + this->on_unsubscribe_bluetooth_le_advertisements_request(msg); + break; + } default: return false; } @@ -1193,6 +1202,18 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request( } } #endif +void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( + const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->unsubscribe_bluetooth_le_advertisements(msg); +} } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 7f19292ff3..ad6696176c 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -216,6 +216,8 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); #endif + virtual void on_unsubscribe_bluetooth_le_advertisements_request( + const UnsubscribeBluetoothLEAdvertisementsRequest &value){}; #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); #endif @@ -296,6 +298,8 @@ class APIServerConnection : public APIServerConnectionBase { virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; #endif + virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0; + protected: void on_hello_request(const HelloRequest &msg) override; void on_connect_request(const ConnectRequest &msg) override; @@ -367,6 +371,8 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_BLUETOOTH_PROXY void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; #endif + void on_unsubscribe_bluetooth_le_advertisements_request( + const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; }; } // namespace api From 3ac7bf3761aa050a6eec24a65a06160c88f490d1 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 28 Mar 2023 11:00:34 +0200 Subject: [PATCH 747/838] EntityBase: Move ObjectId to Flash (#4569) * Move EntityBase Object Id from memory to flash. * Sprinkler use common `setup_entity` method. * Remove `EntityBase` from Sprinkler. * Support for entity names set to None * change so gh PR picks up commit. --------- Co-authored-by: Your Name Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/core/entity_base.cpp | 33 ++++++++++++++++++++++++++----- esphome/core/entity_base.h | 7 ++++--- esphome/cpp_helpers.py | 5 +++++ esphome/helpers.py | 11 +++++++++++ tests/unit_tests/test_helpers.py | 34 ++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 19ab57349d..3d61e36fd1 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -16,7 +16,6 @@ void EntityBase::set_name(const char *name) { } else { this->has_own_name_ = true; } - this->calc_object_id_(); } // Entity Internal @@ -41,13 +40,37 @@ EntityCategory EntityBase::get_entity_category() const { return this->entity_cat void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } // Entity Object ID -const std::string &EntityBase::get_object_id() { return this->object_id_; } +std::string EntityBase::get_object_id() const { + // Check if `App.get_friendly_name()` is constant or dynamic. + if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) { + // `App.get_friendly_name()` is dynamic. + return str_sanitize(str_snake_case(App.get_friendly_name())); + } else { + // `App.get_friendly_name()` is constant. + if (this->object_id_c_str_ == nullptr) { + return ""; + } + return this->object_id_c_str_; + } +} +void EntityBase::set_object_id(const char *object_id) { + this->object_id_c_str_ = object_id; + this->calc_object_id_(); +} // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { - this->object_id_ = str_sanitize(str_snake_case(this->name_)); - // FNV-1 hash - this->object_id_hash_ = fnv1_hash(this->object_id_); + // Check if `App.get_friendly_name()` is constant or dynamic. + if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) { + // `App.get_friendly_name()` is dynamic. + const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name())); + // FNV-1 hash + this->object_id_hash_ = fnv1_hash(object_id); + } else { + // `App.get_friendly_name()` is constant. + // FNV-1 hash + this->object_id_hash_ = fnv1_hash(this->object_id_c_str_); + } } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e25aab21a9..0a53c3fccc 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -22,8 +22,9 @@ class EntityBase { // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->has_own_name_; } - // Get the sanitized name of this Entity as an ID. Caching it internally. - const std::string &get_object_id(); + // Get the sanitized name of this Entity as an ID. + std::string get_object_id() const; + void set_object_id(const char *object_id); // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); @@ -54,7 +55,7 @@ class EntityBase { StringRef name_; bool has_own_name_{false}; - std::string object_id_; + const char *object_id_c_str_{nullptr}; const char *icon_c_str_{nullptr}; uint32_t object_id_hash_; bool internal_{false}; diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index ab5231e055..cc53f491f5 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -20,6 +20,7 @@ from esphome.types import ConfigType, ConfigFragmentType from esphome.cpp_generator import add, get_variable from esphome.cpp_types import App from esphome.util import Registry, RegistryEntry +from esphome.helpers import snake_case, sanitize _LOGGER = logging.getLogger(__name__) @@ -101,6 +102,10 @@ async def register_parented(var, value): async def setup_entity(var, config): """Set up generic properties of an Entity""" add(var.set_name(config[CONF_NAME])) + if not config[CONF_NAME]: + add(var.set_object_id(sanitize(snake_case(CORE.friendly_name)))) + else: + add(var.set_object_id(sanitize(snake_case(config[CONF_NAME])))) add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) if CONF_INTERNAL in config: add(var.set_internal(config[CONF_INTERNAL])) diff --git a/esphome/helpers.py b/esphome/helpers.py index b5a6306342..884f640d7b 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Union import tempfile from urllib.parse import urlparse +import re _LOGGER = logging.getLogger(__name__) @@ -334,3 +335,13 @@ def add_class_to_obj(value, cls): if type(value) is type_: # pylint: disable=unidiomatic-typecheck return add_class_to_obj(func(value), cls) raise + + +def snake_case(value): + """Same behaviour as `helpers.cpp` method `str_snake_case`.""" + return value.replace(" ", "_").lower() + + +def sanitize(value): + """Same behaviour as `helpers.cpp` method `str_sanitize`.""" + return re.sub("[^-_0-9a-zA-Z]", r"", value) diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index f883b8b44f..b98838024f 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -229,3 +229,37 @@ def test_file_compare(fixture_path, file1, file2, expected): actual = helpers.file_compare(path1, path2) assert actual == expected + + +@pytest.mark.parametrize( + "text, expected", + ( + ("foo", "foo"), + ("foo bar", "foo_bar"), + ("foo Bar", "foo_bar"), + ("foo BAR", "foo_bar"), + ("foo.bar", "foo.bar"), + ("fooBAR", "foobar"), + ("Foo-bar_EEK", "foo-bar_eek"), + (" foo", "__foo"), + ), +) +def test_snake_case(text, expected): + actual = helpers.snake_case(text) + + assert actual == expected + + +@pytest.mark.parametrize( + "text, expected", + ( + ("foo_bar", "foo_bar"), + ('!"§$%&/()=?foo_bar', "foo_bar"), + ('foo_!"§$%&/()=?bar', "foo_bar"), + ('foo_bar!"§$%&/()=?', "foo_bar"), + ), +) +def test_sanitize(text, expected): + actual = helpers.sanitize(text) + + assert actual == expected From a2931b6774a141462b92b78bbf1e1294dd422cf5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 29 Mar 2023 12:31:07 +1300 Subject: [PATCH 748/838] Add workflow to sync device classes with HA dev (#4629) --- .github/workflows/sync-device-classes.yml | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/sync-device-classes.yml diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml new file mode 100644 index 0000000000..82eead332e --- /dev/null +++ b/.github/workflows/sync-device-classes.yml @@ -0,0 +1,60 @@ +--- +name: Synchronise Device Classes from Home Assistant + +on: + workflow_dispatch: + schedule: + - cron: '45 6 * * *' + +permissions: + contents: write + pull-requests: write + +jobs: + sync: + name: Sync Device Classes + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Checkout Home Assistant + uses: actions/checkout@v3 + with: + repository: home-assistant/core + path: home-assistant + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install Home Assistant + run: | + python -m pip install --upgrade pip + pip install -e home-assistant + + - name: Sync + run: | + python ./script/sync-device_class.py + + - name: Get PR template + id: pr-template-body + run: | + body=$(cat .github/PULL_REQUEST_TEMPLATE.md) + delimiter="$(openssl rand -hex 8)" + echo "body<<$delimiter" >> $GITHUB_OUTPUT + echo "$body" >> $GITHUB_OUTPUT + echo "$delimiter" >> $GITHUB_OUTPUT + + - name: Commit changes + uses: peter-evans/create-pull-request@v4 + with: + commit-message: "Synchronise Device Classes from Home Assistant" + committer: esphomebot + author: esphomebot + branch: sync/device-classes/ + branch-suffix: timestamp + delete-branch: true + title: "Synchronise Device Classes from Home Assistant" + body: ${{ steps.pr-template-body.outputs.body }} From c4ddf7697d36fe25eefa9458e1e329a0d1f48f54 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:02:34 +1300 Subject: [PATCH 749/838] Update sync-device-classes.yml --- .github/workflows/sync-device-classes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 82eead332e..671fe1f21a 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v3 with: repository: home-assistant/core - path: home-assistant + path: lib/home-assistant - name: Setup Python uses: actions/setup-python@v4 @@ -32,7 +32,7 @@ jobs: - name: Install Home Assistant run: | python -m pip install --upgrade pip - pip install -e home-assistant + pip install -e lib/home-assistant - name: Sync run: | From a014d853a41c01e095390ec0351714c94a66c105 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Mar 2023 15:47:00 +1300 Subject: [PATCH 750/838] Synchronise Device Classes from Home Assistant (#4633) * Synchronise Device Classes from Home Assistant * Remove count do the `DEVICE_CLASSES` list is also updated * Format file --------- Co-authored-by: esphomebot Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/number/__init__.py | 4 ++++ esphome/components/sensor/__init__.py | 4 ++++ esphome/const.py | 2 ++ script/sync-device_class.py | 8 ++++---- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 7db973612b..7dce625389 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -31,6 +31,7 @@ from esphome.const import ( DEVICE_CLASS_DISTANCE, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ENERGY_STORAGE, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, @@ -59,6 +60,7 @@ from esphome.const import ( DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WIND_SPEED, @@ -81,6 +83,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_DISTANCE, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ENERGY_STORAGE, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, @@ -109,6 +112,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WIND_SPEED, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index b63e157efb..ac25884697 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -43,6 +43,7 @@ from esphome.const import ( DEVICE_CLASS_DURATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ENERGY_STORAGE, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, @@ -72,6 +73,7 @@ from esphome.const import ( DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WIND_SPEED, @@ -97,6 +99,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_DURATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ENERGY_STORAGE, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, @@ -126,6 +129,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLUME, + DEVICE_CLASS_VOLUME_STORAGE, DEVICE_CLASS_WATER, DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WIND_SPEED, diff --git a/esphome/const.py b/esphome/const.py index 7b2fdfc3a4..f627b5abd4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -946,6 +946,7 @@ DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_ENERGY = "energy" +DEVICE_CLASS_ENERGY_STORAGE = "energy_storage" DEVICE_CLASS_FREQUENCY = "frequency" DEVICE_CLASS_GARAGE = "garage" DEVICE_CLASS_GARAGE_DOOR = "garage_door" @@ -1000,6 +1001,7 @@ DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" DEVICE_CLASS_VOLUME = "volume" +DEVICE_CLASS_VOLUME_STORAGE = "volume_storage" DEVICE_CLASS_WATER = "water" DEVICE_CLASS_WEIGHT = "weight" DEVICE_CLASS_WINDOW = "window" diff --git a/script/sync-device_class.py b/script/sync-device_class.py index 882655561b..ae6f4be0c8 100755 --- a/script/sync-device_class.py +++ b/script/sync-device_class.py @@ -25,9 +25,9 @@ DOMAINS = { def sub(path, pattern, repl): - with open(path, "r") as handle: + with open(path) as handle: content = handle.read() - content = re.sub(pattern, repl, content, flags=re.MULTILINE, count=1) + content = re.sub(pattern, repl, content, flags=re.MULTILINE) with open(path, "w") as handle: handle.write(content) @@ -48,7 +48,7 @@ def main(): out = "" for cls in sorted(classes): out += f'DEVICE_CLASS_{cls.upper()} = "{classes[cls]}"\n' - sub("esphome/const.py", '(DEVICE_CLASS_\w+ = "\w*"\r?\n)+', out) + sub("esphome/const.py", '(DEVICE_CLASS_\\w+ = "\\w*"\r?\n)+', out) for domain in sorted(allowed): # replace imports @@ -58,7 +58,7 @@ def main(): sub( f"esphome/components/{domain}/__init__.py", - "( DEVICE_CLASS_\w+,\r?\n)+", + "( DEVICE_CLASS_\\w+,\r?\n)+", out, ) From b5d0aede381d21ea6683575603805a56fddaf1a8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:08:31 +1300 Subject: [PATCH 751/838] Remove AUTO_LOAD from as3935 (#4630) --- esphome/components/as3935/__init__.py | 1 - esphome/components/as3935/as3935.cpp | 14 ++++++++++++-- esphome/components/as3935/as3935.h | 23 +++++++++++++++-------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/esphome/components/as3935/__init__.py b/esphome/components/as3935/__init__.py index 0951d01e68..cf0580ca62 100644 --- a/esphome/components/as3935/__init__.py +++ b/esphome/components/as3935/__init__.py @@ -12,7 +12,6 @@ from esphome.const import ( CONF_CAPACITANCE, ) -AUTO_LOAD = ["sensor", "binary_sensor"] MULTI_CONF = True CONF_AS3935_ID = "as3935_id" diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index b36856218a..c5651caee3 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -26,9 +26,13 @@ void AS3935Component::setup() { void AS3935Component::dump_config() { ESP_LOGCONFIG(TAG, "AS3935:"); LOG_PIN(" Interrupt Pin: ", this->irq_pin_); +#ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_); +#endif +#ifdef USE_SENSOR LOG_SENSOR(" ", "Distance", this->distance_sensor_); LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_); +#endif } float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } @@ -44,16 +48,22 @@ void AS3935Component::loop() { ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!"); } else if (int_value == LIGHTNING_INT) { ESP_LOGI(TAG, "Lightning has been detected!"); - if (this->thunder_alert_binary_sensor_ != nullptr) +#ifdef USE_BINARY_SENSOR + if (this->thunder_alert_binary_sensor_ != nullptr) { this->thunder_alert_binary_sensor_->publish_state(true); + this->set_timeout(10, [this]() { this->thunder_alert_binary_sensor_->publish_state(false); }); + } +#endif +#ifdef USE_SENSOR uint8_t distance = this->get_distance_to_storm_(); if (this->distance_sensor_ != nullptr) this->distance_sensor_->publish_state(distance); + uint32_t energy = this->get_lightning_energy_(); if (this->energy_sensor_ != nullptr) this->energy_sensor_->publish_state(energy); +#endif } - this->thunder_alert_binary_sensor_->publish_state(false); } void AS3935Component::write_indoor(bool indoor) { diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 2cba9b11a0..a8af703a59 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -1,9 +1,14 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" +#ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" +#endif namespace esphome { namespace as3935 { @@ -52,6 +57,15 @@ enum AS3935Values { }; class AS3935Component : public Component { +#ifdef USE_SENSOR + SUB_SENSOR(distance) + SUB_SENSOR(energy) +#endif + +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(thunder_alert) +#endif + public: void setup() override; void dump_config() override; @@ -59,11 +73,7 @@ class AS3935Component : public Component { void loop() override; void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; } - void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; } - void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } - void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) { - thunder_alert_binary_sensor_ = thunder_alert_binary_sensor; - } + void set_indoor(bool indoor) { indoor_ = indoor; } void write_indoor(bool indoor); void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; } @@ -92,9 +102,6 @@ class AS3935Component : public Component { virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0; - sensor::Sensor *distance_sensor_{nullptr}; - sensor::Sensor *energy_sensor_{nullptr}; - binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr}; GPIOPin *irq_pin_; bool indoor_; From a546ffd490d067225b5982e809ff424a54bf3b1d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:08:51 +1300 Subject: [PATCH 752/838] Add ifdef to new bt proxy unsubscribe (#4634) * Add ifdef to new bt proxy unsubscribe * Also add to subscribe message and wrap api conneciton code * Format file --- esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.h | 15 +++++++++------ esphome/components/api/api_pb2_service.cpp | 12 ++++++++++++ esphome/components/api/api_pb2_service.h | 13 ++++++++++++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index fd00317282..e36f0581ca 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1127,6 +1127,7 @@ message MediaPlayerCommandRequest { message SubscribeBluetoothLEAdvertisementsRequest { option (id) = 66; option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; } message BluetoothServiceData { @@ -1366,6 +1367,7 @@ message BluetoothDeviceUnpairingResponse { message UnsubscribeBluetoothLEAdvertisementsRequest { option (id) = 87; option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_BLUETOOTH_PROXY"; } message BluetoothDeviceClearCacheResponse { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index bc6ae5d7c3..c85c69a2b9 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -97,6 +97,12 @@ class APIConnection : public APIServerConnection { this->send_homeassistant_service_response(call); } #ifdef USE_BLUETOOTH_PROXY + void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { + this->bluetooth_le_advertisement_subscription_ = true; + } + void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { + this->bluetooth_le_advertisement_subscription_ = false; + } bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; @@ -150,12 +156,7 @@ class APIConnection : public APIServerConnection { return {}; } void execute_service(const ExecuteServiceRequest &msg) override; - void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { - this->bluetooth_le_advertisement_subscription_ = true; - } - void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override { - this->bluetooth_le_advertisement_subscription_ = false; - } + bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } bool is_connection_setup() override { return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); @@ -200,7 +201,9 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; +#ifdef USE_BLUETOOTH_PROXY bool bluetooth_le_advertisement_subscription_{false}; +#endif bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index e613cbcad9..7d019e1d3d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -329,6 +329,8 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer #ifdef USE_MEDIA_PLAYER #endif #ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) { #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str()); @@ -442,6 +444,8 @@ bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const Blu } #endif #ifdef USE_BLUETOOTH_PROXY +#endif +#ifdef USE_BLUETOOTH_PROXY bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) { #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str()); @@ -717,12 +721,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, break; } case 66: { +#ifdef USE_BLUETOOTH_PROXY SubscribeBluetoothLEAdvertisementsRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); #endif this->on_subscribe_bluetooth_le_advertisements_request(msg); +#endif break; } case 68: { @@ -814,12 +820,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, break; } case 87: { +#ifdef USE_BLUETOOTH_PROXY UnsubscribeBluetoothLEAdvertisementsRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); #endif this->on_unsubscribe_bluetooth_le_advertisements_request(msg); +#endif break; } default: @@ -1082,6 +1090,7 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma this->media_player_command(msg); } #endif +#ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( const SubscribeBluetoothLEAdvertisementsRequest &msg) { if (!this->is_connection_setup()) { @@ -1094,6 +1103,7 @@ void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( } this->subscribe_bluetooth_le_advertisements(msg); } +#endif #ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { if (!this->is_connection_setup()) { @@ -1202,6 +1212,7 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request( } } #endif +#ifdef USE_BLUETOOTH_PROXY void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { if (!this->is_connection_setup()) { @@ -1214,6 +1225,7 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( } this->unsubscribe_bluetooth_le_advertisements(msg); } +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index ad6696176c..457a3d28a9 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -154,8 +154,10 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_MEDIA_PLAYER virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; #endif +#ifdef USE_BLUETOOTH_PROXY virtual void on_subscribe_bluetooth_le_advertisements_request( const SubscribeBluetoothLEAdvertisementsRequest &value){}; +#endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); #endif @@ -216,8 +218,10 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); #endif +#ifdef USE_BLUETOOTH_PROXY virtual void on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &value){}; +#endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); #endif @@ -272,7 +276,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_MEDIA_PLAYER virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; #endif +#ifdef USE_BLUETOOTH_PROXY virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#endif #ifdef USE_BLUETOOTH_PROXY virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0; #endif @@ -298,8 +304,9 @@ class APIServerConnection : public APIServerConnectionBase { virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; #endif +#ifdef USE_BLUETOOTH_PROXY virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0; - +#endif protected: void on_hello_request(const HelloRequest &msg) override; void on_connect_request(const ConnectRequest &msg) override; @@ -346,7 +353,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_MEDIA_PLAYER void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; #endif +#ifdef USE_BLUETOOTH_PROXY void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; +#endif #ifdef USE_BLUETOOTH_PROXY void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; #endif @@ -371,8 +380,10 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_BLUETOOTH_PROXY void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override; #endif +#ifdef USE_BLUETOOTH_PROXY void on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; +#endif }; } // namespace api From 616e0a21d8929672dba62472db3e57089f34f762 Mon Sep 17 00:00:00 2001 From: felixlungu Date: Thu, 30 Mar 2023 04:12:06 +0300 Subject: [PATCH 753/838] add bluetooth mac address in dump_config() (#4628) * add bluetooth mac address in dump_config() * Update ble.cpp * Update ble.cpp * Update ble.cpp * Update ble.cpp * Update ble.cpp * Update ble.cpp * Update ble.cpp --- esphome/components/esp32_ble/ble.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 160084b913..502399f97a 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -211,7 +212,16 @@ void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } -void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); } +void ESP32BLE::dump_config() { + const uint8_t *mac_address = esp_bt_dev_get_address(); + if (mac_address) { + ESP_LOGCONFIG(TAG, "ESP32 BLE:"); + ESP_LOGCONFIG(TAG, " MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]); + } else { + ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); + } +} ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) From 28534ecc61852068727e8c081b0afabbf888b62f Mon Sep 17 00:00:00 2001 From: kahrendt Date: Fri, 31 Mar 2023 00:27:24 -0400 Subject: [PATCH 754/838] Binary map bugfixes (#4636) * limit configuration to 64 binary sensors to match code limitation * remove superfluous debug logging * improve state publishing logic eliminating NAN being sent on boot in certain cases * adjust type for bitmask shift to match mask variable type --- .../binary_sensor_map/binary_sensor_map.cpp | 29 +++++++------------ .../components/binary_sensor_map/sensor.py | 4 +-- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.cpp b/esphome/components/binary_sensor_map/binary_sensor_map.cpp index b2dffaf74d..3934e0a99c 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.cpp +++ b/esphome/components/binary_sensor_map/binary_sensor_map.cpp @@ -30,7 +30,7 @@ void BinarySensorMap::process_group_() { if (bs.binary_sensor->state) { num_active_sensors++; total_current_value += bs.sensor_value; - mask |= 1 << i; + mask |= 1ULL << i; } } // check if the sensor map was touched @@ -38,12 +38,11 @@ void BinarySensorMap::process_group_() { // did the bit_mask change or is it a new sensor touch if (this->last_mask_ != mask) { float publish_value = total_current_value / num_active_sensors; - ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); this->publish_state(publish_value); } } else if (this->last_mask_ != 0ULL) { // is this a new sensor release - ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); + ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); this->publish_state(NAN); } this->last_mask_ = mask; @@ -52,28 +51,22 @@ void BinarySensorMap::process_group_() { void BinarySensorMap::process_sum_() { float total_current_value = 0.0; uint64_t mask = 0x00; - // check all binary_sensors for its state. when active add its value to total_current_value. - // create a bitmask for the binary_sensor status on all channels + // - check all binary_sensor states + // - if active, add its value to total_current_value + // - creates a bitmask for the binary_sensor status on all channels for (size_t i = 0; i < this->channels_.size(); i++) { auto bs = this->channels_[i]; if (bs.binary_sensor->state) { total_current_value += bs.sensor_value; - mask |= 1 << i; + mask |= 1ULL << i; } } - // check if the sensor map was touched - if (mask != 0ULL) { - // did the bit_mask change or is it a new sensor touch - if (this->last_mask_ != mask) { - float publish_value = total_current_value; - ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); - this->publish_state(publish_value); - } - } else if (this->last_mask_ != 0ULL) { - // is this a new sensor release - ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str()); - this->publish_state(0.0); + + // update state only if the binary sensor states have changed or if no state has ever been sent on boot + if ((this->last_mask_ != mask) || (!this->has_state())) { + this->publish_state(total_current_value); } + this->last_mask_ = mask; } diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 025be490cd..573cce9223 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.typed_schema( ).extend( { cv.Required(CONF_CHANNELS): cv.All( - cv.ensure_list(entry), cv.Length(min=1) + cv.ensure_list(entry), cv.Length(min=1, max=64) ), } ), @@ -50,7 +50,7 @@ CONFIG_SCHEMA = cv.typed_schema( ).extend( { cv.Required(CONF_CHANNELS): cv.All( - cv.ensure_list(entry), cv.Length(min=1) + cv.ensure_list(entry), cv.Length(min=1, max=64) ), } ), From 4faa9d109eec8d13cac74f32926237b0087d839a Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 31 Mar 2023 06:28:49 +0200 Subject: [PATCH 755/838] entity_base avoid padding bytes. (#4637) Co-authored-by: Your Name --- esphome/core/entity_base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 0a53c3fccc..e40a7013bf 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -54,10 +54,10 @@ class EntityBase { void calc_object_id_(); StringRef name_; - bool has_own_name_{false}; const char *object_id_c_str_{nullptr}; const char *icon_c_str_{nullptr}; uint32_t object_id_hash_; + bool has_own_name_{false}; bool internal_{false}; bool disabled_by_default_{false}; EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; From 79f861f012c7b68e531124411e474081d3491fd8 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 31 Mar 2023 06:29:17 +0200 Subject: [PATCH 756/838] Avoid sensor padding. (#4638) Co-authored-by: Your Name --- esphome/components/sensor/sensor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 4308eafb12..efcada1411 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -161,7 +161,6 @@ class Sensor : public EntityBase { CallbackManager raw_callback_; ///< Storage for raw state callbacks. CallbackManager callback_; ///< Storage for filtered state callbacks. - bool has_state_{false}; Filter *filter_list_{nullptr}; ///< Store all active filters. optional unit_of_measurement_; ///< Unit of measurement override @@ -169,6 +168,7 @@ class Sensor : public EntityBase { optional device_class_; ///< Device class override optional state_class_{STATE_CLASS_NONE}; ///< State class override bool force_update_{false}; ///< Force update mode + bool has_state_{false}; }; } // namespace sensor From 9922eb83e2851f23992d021ae59adba5d56d73d7 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 31 Mar 2023 06:30:24 +0200 Subject: [PATCH 757/838] Support advanced UART customization (#4465) * Add methods to get hardware uart details. * Fix `setRxBufferSize` error. --------- Co-authored-by: Your Name --- esphome/components/uart/uart_component_esp32_arduino.cpp | 2 +- esphome/components/uart/uart_component_esp32_arduino.h | 3 +++ esphome/components/uart/uart_component_esp_idf.cpp | 7 ++++++- esphome/components/uart/uart_component_esp_idf.h | 4 ++++ esphome/components/uart/uart_component_rp2040.h | 3 +++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 402e41e0b5..8bbbc1a650 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -100,8 +100,8 @@ void ESP32ArduinoUARTComponent::setup() { invert = true; if (rx_pin_ != nullptr && rx_pin_->is_inverted()) invert = true; - this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); } 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 4a000b12d2..f85c709097 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.h +++ b/esphome/components/uart/uart_component_esp32_arduino.h @@ -28,6 +28,9 @@ class ESP32ArduinoUARTComponent : public UARTComponent, public Component { uint32_t get_config(); + HardwareSerial *get_hw_serial() { return this->hw_serial_; } + uint8_t get_hw_serial_number() { return this->number_; } + 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 80255ccddf..1560409772 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -79,7 +79,12 @@ void IDFUARTComponent::setup() { return; } - err = uart_driver_install(this->uart_num_, this->rx_buffer_size_, 0, 0, nullptr, 0); + err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_, + /* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will + block task until all data have been sent out.*/ + 0, + /* UART event queue size/depth. */ 20, &(this->uart_event_queue_), + /* Flags used to allocate the interrupt. */ 0); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); this->mark_failed(); diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 27fb80d2cc..fdaa4da9a7 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -23,9 +23,13 @@ class IDFUARTComponent : public UARTComponent, public Component { int available() override; void flush() override; + uint8_t get_hw_serial_number() { return this->uart_num_; } + QueueHandle_t *get_uart_event_queue() { return &this->uart_event_queue_; } + protected: void check_logger_conflict() override; uart_port_t uart_num_; + QueueHandle_t uart_event_queue_; uart_config_t get_config_(); SemaphoreHandle_t lock_; diff --git a/esphome/components/uart/uart_component_rp2040.h b/esphome/components/uart/uart_component_rp2040.h index 163315dee7..f26c913cff 100644 --- a/esphome/components/uart/uart_component_rp2040.h +++ b/esphome/components/uart/uart_component_rp2040.h @@ -30,6 +30,9 @@ class RP2040UartComponent : public UARTComponent, public Component { uint16_t get_config(); + bool is_hw_serial() { return this->hw_serial_; } + HardwareSerial *get_hw_serial() { return this->serial_; } + protected: void check_logger_conflict() override {} bool hw_serial_{false}; From 878155a03d5dc912125248c6d4b86d6793068326 Mon Sep 17 00:00:00 2001 From: Mikkel Jeppesen <2756925+Duckle29@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:05:28 +0200 Subject: [PATCH 758/838] Log calibration results at level INFO (#4240) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/senseair/senseair.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 7a98584201..e0504eb2b9 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -86,7 +86,7 @@ void SenseAirComponent::background_calibration_result() { } // Check if 5th bit (register CI6) is set - ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)", (response[4] & 0b100000) != 0 ? "OK" : "NOT_OK", + ESP_LOGI(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)", (response[4] & 0b100000) != 0 ? "OK" : "NOT_OK", response[0], response[1], response[2], response[3], response[4], response[5], response[6]); } From d78e9e6aa8929aab3a5d6c600072f99dac831cd9 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:31:11 +0200 Subject: [PATCH 759/838] Number step not optional (#4649) * Number step not optional * Update __init__.py * Update __init__.py --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/number/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 7dce625389..f532f4e405 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -1,4 +1,3 @@ -from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -252,9 +251,7 @@ async def register_number( ) -async def new_number( - config, *, min_value: float, max_value: float, step: Optional[float] = None -): +async def new_number(config, *, min_value: float, max_value: float, step: float): var = cg.new_Pvariable(config[CONF_ID]) await register_number( var, config, min_value=min_value, max_value=max_value, step=step From 99638190cb26a078135d8183825a4317b5a44841 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Apr 2023 07:44:46 +1200 Subject: [PATCH 760/838] VSCode / devcontainer updates (#4647) --- .devcontainer/devcontainer.json | 91 ++++++++++++++++++--------------- .vscode/tasks.json | 15 ++++-- script/setup | 6 +++ 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1f9a98d7ec..b3fa6d4932 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,53 +4,60 @@ "postCreateCommand": [ "script/devcontainer-post-create" ], + "containerEnv": { + "DEVCONTAINER": "1" + }, "runArgs": [ "--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1" ], "appPort": 6052, - "extensions": [ - // python - "ms-python.python", - "visualstudioexptteam.vscodeintellicode", - // yaml - "redhat.vscode-yaml", - // cpp - "ms-vscode.cpptools", - // editorconfig - "editorconfig.editorconfig", - ], - "settings": { - "python.languageServer": "Pylance", - "python.pythonPath": "/usr/bin/python3", - "python.linting.pylintEnabled": true, - "python.linting.enabled": true, - "python.formatting.provider": "black", - "editor.formatOnPaste": false, - "editor.formatOnSave": true, - "editor.formatOnType": true, - "files.trimTrailingWhitespace": true, - "terminal.integrated.defaultProfile.linux": "bash", - "yaml.customTags": [ - "!secret scalar", - "!lambda scalar", - "!include_dir_named scalar", - "!include_dir_list scalar", - "!include_dir_merge_list scalar", - "!include_dir_merge_named scalar" - ], - "files.exclude": { - "**/.git": true, - "**/.DS_Store": true, - "**/*.pyc": { - "when": "$(basename).py" - }, - "**/__pycache__": true - }, - "files.associations": { - "**/.vscode/*.json": "jsonc" - }, - "C_Cpp.clang_format_path": "/usr/bin/clang-format-13", + "customizations": { + "vscode": { + "extensions": [ + // python + "ms-python.python", + "visualstudioexptteam.vscodeintellicode", + // yaml + "redhat.vscode-yaml", + // cpp + "ms-vscode.cpptools", + // editorconfig + "editorconfig.editorconfig", + ], + "settings": { + "python.languageServer": "Pylance", + "python.pythonPath": "/usr/bin/python3", + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true, + "terminal.integrated.defaultProfile.linux": "bash", + "yaml.customTags": [ + "!secret scalar", + "!lambda scalar", + "!include_dir_named scalar", + "!include_dir_list scalar", + "!include_dir_merge_list scalar", + "!include_dir_merge_named scalar" + ], + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/*.pyc": { + "when": "$(basename).py" + }, + "**/__pycache__": true + }, + "files.associations": { + "**/.vscode/*.json": "jsonc" + }, + "C_Cpp.clang_format_path": "/usr/bin/clang-format-13" + } + } } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b6584bc735..307dd496f0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,15 +2,24 @@ "version": "2.0.0", "tasks": [ { - "label": "run", + "label": "Run Dashboard", "type": "shell", - "command": "python3 -m esphome dashboard config/", + "command": "${command:python.interpreterPath}", + "args": [ + "-m", + "esphome", + "dashboard", + "config/" + ], "problemMatcher": [] }, { "label": "clang-tidy", "type": "shell", - "command": "./script/clang-tidy", + "command": "${command:python.interpreterPath}", + "args": [ + "./script/clang-tidy" + ], "problemMatcher": [ { "owner": "clang-tidy", diff --git a/script/setup b/script/setup index 71828deeaa..c650960f05 100755 --- a/script/setup +++ b/script/setup @@ -4,6 +4,12 @@ set -e cd "$(dirname "$0")/.." + +if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ]; then + python3 -m venv venv + source venv/bin/activate +fi + pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip3 install --no-use-pep517 -e . From fbc129ccccbdb8ed9495903c8dc7a7334c909197 Mon Sep 17 00:00:00 2001 From: tracestep <16390082+tracestep@users.noreply.github.com> Date: Mon, 3 Apr 2023 19:23:31 -0300 Subject: [PATCH 761/838] Version retry (fixes esphome/issues#3823) (#4651) --- esphome/components/pn532/pn532.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 7ebf328cff..dc831ef6e0 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -19,9 +19,12 @@ void PN532::setup() { // Get version data if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) { - ESP_LOGE(TAG, "Error sending version command"); - this->mark_failed(); - return; + ESP_LOGW(TAG, "Error sending version command, trying again..."); + if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) { + ESP_LOGE(TAG, "Error sending version command"); + this->mark_failed(); + return; + } } std::vector version_data; From 9c9bc58c16a435fedb46d115a98debe8c35acaf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:55:00 +1200 Subject: [PATCH 762/838] Bump pylint from 2.16.4 to 2.17.2 (#4650) * Bump pylint from 2.16.4 to 2.17.2 Bumps [pylint](https://github.com/PyCQA/pylint) from 2.16.4 to 2.17.2. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Commits](https://github.com/PyCQA/pylint/compare/v2.16.4...v2.17.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Add return 0 to run_miniterm --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/__main__.py | 2 ++ requirements_test.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 24c2ce1d13..78320a05f0 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -152,6 +152,8 @@ def run_miniterm(config, port): _LOGGER.error("Could not connect to serial port %s", port) return 1 + return 0 + def wrap_to_code(name, comp): coro = coroutine(comp.to_code) diff --git a/requirements_test.txt b/requirements_test.txt index 3e59023d20..b2c60b3db3 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.16.4 +pylint==2.17.2 flake8==6.0.0 # also change in .pre-commit-config.yaml when updating black==23.1.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating From 42401775e1920431e71804913dda31d6bcdbe94d Mon Sep 17 00:00:00 2001 From: Ben Hoff Date: Mon, 3 Apr 2023 22:34:14 -0400 Subject: [PATCH 763/838] Added in mmc5603 code (#4175) * added in mmc5603 code * added in codeowner * fix linter errors * whitespace linter errors * added codeowner * clang format * remove clang format from python code * fix whitespace * add tests * fix test * make requested edits * remove status manipulation --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/mmc5603/__init__.py | 1 + esphome/components/mmc5603/mmc5603.cpp | 162 +++++++++++++++++++++++++ esphome/components/mmc5603/mmc5603.h | 43 +++++++ esphome/components/mmc5603/sensor.py | 91 ++++++++++++++ tests/test1.yaml | 9 ++ 6 files changed, 307 insertions(+) create mode 100644 esphome/components/mmc5603/__init__.py create mode 100644 esphome/components/mmc5603/mmc5603.cpp create mode 100644 esphome/components/mmc5603/mmc5603.h create mode 100644 esphome/components/mmc5603/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index c006db2a6a..76156db6e6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -159,6 +159,7 @@ esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/mlx90393/* @functionpointer +esphome/components/mmc5603/* @benhoff esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/number/* @martgras diff --git a/esphome/components/mmc5603/__init__.py b/esphome/components/mmc5603/__init__.py new file mode 100644 index 0000000000..cc88f26231 --- /dev/null +++ b/esphome/components/mmc5603/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@benhoff"] diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp new file mode 100644 index 0000000000..6fbf4810f2 --- /dev/null +++ b/esphome/components/mmc5603/mmc5603.cpp @@ -0,0 +1,162 @@ +#include "mmc5603.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mmc5603 { + +static const char *const TAG = "mmc5603"; +static const uint8_t MMC5603_ADDRESS = 0x30; +static const uint8_t MMC56X3_PRODUCT_ID = 0x39; + +static const uint8_t MMC56X3_DEFAULT_ADDRESS = 0x30; +static const uint8_t MMC56X3_CHIP_ID = 0x10; + +static const uint8_t MMC56X3_ADDR_XOUT0 = 0x00; +static const uint8_t MMC56X3_ADDR_XOUT1 = 0x01; +static const uint8_t MMC56X3_ADDR_XOUT2 = 0x06; + +static const uint8_t MMC56X3_ADDR_YOUT0 = 0x02; +static const uint8_t MMC56X3_ADDR_YOUT1 = 0x03; +static const uint8_t MMC56X3_ADDR_YOUT2 = 0x07; + +static const uint8_t MMC56X3_ADDR_ZOUT0 = 0x04; +static const uint8_t MMC56X3_ADDR_ZOUT1 = 0x05; +static const uint8_t MMC56X3_ADDR_ZOUT2 = 0x08; + +static const uint8_t MMC56X3_OUT_TEMP = 0x09; +static const uint8_t MMC56X3_STATUS_REG = 0x18; +static const uint8_t MMC56X3_CTRL0_REG = 0x1B; +static const uint8_t MMC56X3_CTRL1_REG = 0x1C; +static const uint8_t MMC56X3_CTRL2_REG = 0x1D; +static const uint8_t MMC5603_ODR_REG = 0x1A; + +void MMC5603Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MMC5603..."); + uint8_t id = 0; + if (!this->read_byte(MMC56X3_PRODUCT_ID, &id)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + if (id != MMC56X3_CHIP_ID) { + ESP_LOGCONFIG(TAG, "Chip Wrong"); + this->error_code_ = ID_REGISTERS; + this->mark_failed(); + return; + } + + if (!this->write_byte(MMC56X3_CTRL1_REG, 0x80)) { // turn on set bit + ESP_LOGCONFIG(TAG, "Control 1 Failed for set bit"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + if (!this->write_byte(MMC56X3_CTRL0_REG, 0x08)) { // turn on set bit + ESP_LOGCONFIG(TAG, "Control 0 Failed for set bit"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + if (!this->write_byte(MMC56X3_CTRL0_REG, 0x10)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + uint8_t ctrl_2 = 0; + + ctrl_2 &= ~0x10; // turn off cmm_en bit + if (!this->write_byte(MMC56X3_CTRL2_REG, ctrl_2)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } +} +void MMC5603Component::dump_config() { + ESP_LOGCONFIG(TAG, "MMC5603:"); + LOG_I2C_DEVICE(this); + if (this->error_code_ == COMMUNICATION_FAILED) { + ESP_LOGE(TAG, "Communication with MMC5603 failed!"); + } else if (this->error_code_ == ID_REGISTERS) { + ESP_LOGE(TAG, "The ID registers don't match - Is this really an MMC5603?"); + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "X Axis", this->x_sensor_); + LOG_SENSOR(" ", "Y Axis", this->y_sensor_); + LOG_SENSOR(" ", "Z Axis", this->z_sensor_); + LOG_SENSOR(" ", "Heading", this->heading_sensor_); +} + +float MMC5603Component::get_setup_priority() const { return setup_priority::DATA; } + +void MMC5603Component::update() { + if (!this->write_byte(MMC56X3_CTRL0_REG, 0x01)) { + this->status_set_warning(); + return; + } + uint8_t status = 0; + if (!this->read_byte(MMC56X3_STATUS_REG, &status)) { + this->status_set_warning(); + return; + } + + uint8_t buffer[9] = {0}; + + if (!this->read_byte(MMC56X3_ADDR_XOUT0, &buffer[0]) || !this->read_byte(MMC56X3_ADDR_XOUT1, &buffer[1]) || + !this->read_byte(MMC56X3_ADDR_XOUT2, &buffer[2])) { + this->status_set_warning(); + return; + } + + if (!this->read_byte(MMC56X3_ADDR_YOUT0, &buffer[3]) || !this->read_byte(MMC56X3_ADDR_YOUT1, &buffer[4]) || + !this->read_byte(MMC56X3_ADDR_YOUT2, &buffer[5])) { + this->status_set_warning(); + return; + } + + if (!this->read_byte(MMC56X3_ADDR_ZOUT0, &buffer[6]) || !this->read_byte(MMC56X3_ADDR_ZOUT1, &buffer[7]) || + !this->read_byte(MMC56X3_ADDR_ZOUT2, &buffer[8])) { + this->status_set_warning(); + return; + } + + int32_t raw_x = 0; + raw_x |= buffer[0] << 12; + raw_x |= buffer[1] << 4; + raw_x |= buffer[2] << 0; + + const float x = 0.0625 * (raw_x - 524288); + + int32_t raw_y = 0; + raw_y |= buffer[3] << 12; + raw_y |= buffer[4] << 4; + raw_y |= buffer[5] << 0; + + const float y = 0.0625 * (raw_y - 524288); + + int32_t raw_z = 0; + raw_z |= buffer[6] << 12; + raw_z |= buffer[7] << 4; + raw_z |= buffer[8] << 0; + + const float z = 0.0625 * (raw_z - 524288); + + const 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°", x, y, z, heading); + + if (this->x_sensor_ != nullptr) + this->x_sensor_->publish_state(x); + if (this->y_sensor_ != nullptr) + this->y_sensor_->publish_state(y); + if (this->z_sensor_ != nullptr) + this->z_sensor_->publish_state(z); + if (this->heading_sensor_ != nullptr) + this->heading_sensor_->publish_state(heading); +} + +} // namespace mmc5603 +} // namespace esphome diff --git a/esphome/components/mmc5603/mmc5603.h b/esphome/components/mmc5603/mmc5603.h new file mode 100644 index 0000000000..cd0893053c --- /dev/null +++ b/esphome/components/mmc5603/mmc5603.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mmc5603 { + +enum MMC5603Datarate { + MMC5603_DATARATE_75_0_HZ, + MMC5603_DATARATE_150_0_HZ, + MMC5603_DATARATE_255_0_HZ, +}; + +class MMC5603Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_datarate(MMC5603Datarate datarate) { datarate_ = datarate; } + 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; } + void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } + + protected: + MMC5603Datarate datarate_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *heading_sensor_{nullptr}; + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + ID_REGISTERS, + } error_code_; +}; + +} // namespace mmc5603 +} // namespace esphome diff --git a/esphome/components/mmc5603/sensor.py b/esphome/components/mmc5603/sensor.py new file mode 100644 index 0000000000..348a0e7dcc --- /dev/null +++ b/esphome/components/mmc5603/sensor.py @@ -0,0 +1,91 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + ICON_MAGNET, + STATE_CLASS_MEASUREMENT, + UNIT_MICROTESLA, + UNIT_DEGREES, + ICON_SCREEN_ROTATION, + CONF_UPDATE_INTERVAL, +) + +DEPENDENCIES = ["i2c"] + +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 +) + + +MMC5603Datarate = mmc5603_ns.enum("MMC5603Datarate") +MMC5603Datarates = { + 75: MMC5603Datarate.MMC5603_DATARATE_75_0_HZ, + 150: MMC5603Datarate.MMC5603_DATARATE_150_0_HZ, + 255: MMC5603Datarate.MMC5603_DATARATE_255_0_HZ, +} + + +field_strength_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, +) +heading_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=1, +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MMC5603Component), + cv.Optional(CONF_ADDRESS): cv.i2c_address, + 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, + cv.Optional(CONF_HEADING): heading_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x1E)) +) + + +def auto_data_rate(config): + interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds + interval_hz = 1000.0 / interval_msec + for datarate in sorted(MMC5603Datarates.keys()): + if float(datarate) >= interval_hz: + return MMC5603Datarates[datarate] + return MMC5603Datarates[75] + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_datarate(auto_data_rate(config))) + if CONF_FIELD_STRENGTH_X in config: + sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_X]) + cg.add(var.set_x_sensor(sens)) + if CONF_FIELD_STRENGTH_Y in config: + sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_Y]) + cg.add(var.set_y_sensor(sens)) + if CONF_FIELD_STRENGTH_Z in config: + sens = await sensor.new_sensor(config[CONF_FIELD_STRENGTH_Z]) + cg.add(var.set_z_sensor(sens)) + if CONF_HEADING in config: + sens = await sensor.new_sensor(config[CONF_HEADING]) + cg.add(var.set_heading_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 5be6395729..c2a2ed5c95 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -831,6 +831,15 @@ sensor: temperature: name: MPU6886 Temperature i2c_id: i2c_bus + - 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 + i2c_id: i2c_bus - platform: dps310 temperature: name: DPS310 Temperature From b56fa8c50aa302ec8bbfa4b7e29d65118636f79b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:34:36 +1200 Subject: [PATCH 764/838] Bump black from 23.1.0 to 23.3.0 (#4635) * Bump black from 23.1.0 to 23.3.0 Bumps [black](https://github.com/psf/black) from 23.1.0 to 23.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.1.0...23.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update black in pre-commit --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- requirements_test.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0de82cf2de..be82fc826b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,8 +2,8 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - - repo: https://github.com/ambv/black - rev: 23.1.0 + - repo: https://github.com/psf/black + rev: 23.3.0 hooks: - id: black args: diff --git a/requirements_test.txt b/requirements_test.txt index b2c60b3db3..cbd0a75d8f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.17.2 flake8==6.0.0 # also change in .pre-commit-config.yaml when updating -black==23.1.0 # also change in .pre-commit-config.yaml when updating +black==23.3.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating pre-commit From 421ebcc8b2467f1603bdde9a280559c484157037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 10 Apr 2023 23:20:02 +0100 Subject: [PATCH 765/838] use PRIx macros for printing u32/i32 ints (#4671) This fix compilation issues with the latest esp-idf. --- esphome/components/api/api_connection.cpp | 3 ++- esphome/components/api/proto.cpp | 19 ++++++++++--------- esphome/components/esp32/gpio.cpp | 3 ++- esphome/components/esp32/preferences.cpp | 3 ++- esphome/components/i2c/i2c_bus_esp_idf.cpp | 3 ++- esphome/components/logger/logger.cpp | 3 ++- esphome/components/wifi/wifi_component.cpp | 3 ++- .../wifi/wifi_component_esp_idf.cpp | 3 ++- esphome/core/scheduler.cpp | 5 +++-- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 77ba96291a..104560771e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,5 +1,6 @@ #include "api_connection.h" #include +#include #include "esphome/components/network/util.h" #include "esphome/core/entity_base.h" #include "esphome/core/hal.h" @@ -911,7 +912,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { this->helper_->set_log_info(client_info_); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; - ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(), + ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index ca7a4c0887..7af2e92534 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -1,4 +1,5 @@ #include "proto.h" +#include #include "esphome/core/log.h" namespace esphome { @@ -13,7 +14,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { uint32_t consumed; auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); if (!res.has_value()) { - ESP_LOGV(TAG, "Invalid field start at %u", i); + ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i); break; } @@ -25,12 +26,12 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { case 0: { // VarInt res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); if (!res.has_value()) { - ESP_LOGV(TAG, "Invalid VarInt at %u", i); + ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i); error = true; break; } if (!this->decode_varint(field_id, *res)) { - ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32()); + ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32()); } i += consumed; break; @@ -38,38 +39,38 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) { case 2: { // Length-delimited res = ProtoVarInt::parse(&buffer[i], length - i, &consumed); if (!res.has_value()) { - ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); + ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i); error = true; break; } uint32_t field_length = res->as_uint32(); i += consumed; if (field_length > length - i) { - ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); + ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i); error = true; break; } if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) { - ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id); + ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id); } i += field_length; break; } case 5: { // 32-bit if (length - i < 4) { - ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i); + ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i); error = true; break; } uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]); if (!this->decode_32bit(field_id, Proto32Bit(val))) { - ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val); + ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val); } i += 4; break; } default: - ESP_LOGV(TAG, "Invalid field type at %u", i); + ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i); error = true; break; } diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index aafdf80726..7896597d3e 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -2,6 +2,7 @@ #include "gpio.h" #include "esphome/core/log.h" +#include namespace esphome { namespace esp32 { @@ -74,7 +75,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi std::string ESP32InternalGPIOPin::dump_summary() const { char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast(pin_)); + snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast(pin_)); return buffer; } diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 6a6305cf87..f90b8a4603 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -5,6 +5,7 @@ #include "esphome/core/log.h" #include #include +#include #include #include @@ -101,7 +102,7 @@ class ESP32Preferences : public ESPPreferences { pref->nvs_handle = nvs_handle; uint32_t keyval = type; - pref->key = str_sprintf("%u", keyval); + pref->key = str_sprintf("%" PRIu32, keyval); return ESPPreferenceObject(pref); } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 1e2a7304f2..5178f6d4f2 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -5,6 +5,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" #include +#include namespace esphome { namespace i2c { @@ -47,7 +48,7 @@ void IDFI2CBus::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_); + ESP_LOGCONFIG(TAG, " Frequency: %" PRIu32 " Hz", this->frequency_); switch (this->recovery_result_) { case RECOVERY_COMPLETED: ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 8a3c6a951d..c77e280711 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,4 +1,5 @@ #include "logger.h" +#include #ifdef USE_ESP_IDF #include @@ -292,7 +293,7 @@ const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); - ESP_LOGCONFIG(TAG, " Log Baud Rate: %u", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_); ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); for (auto &it : this->log_levels_) { ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d4636c81cd..efb1af171d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1,4 +1,5 @@ #include "wifi_component.h" +#include #if defined(USE_ESP32) || defined(USE_ESP_IDF) #include @@ -370,7 +371,7 @@ void WiFiComponent::print_connect_params_() { if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } - ESP_LOGCONFIG(TAG, " Channel: %d", wifi_channel_()); + ESP_LOGCONFIG(TAG, " Channel: %" PRId32, wifi_channel_()); ESP_LOGCONFIG(TAG, " Subnet: %s", wifi_subnet_mask_().str().c_str()); ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 0f3c3a0ca8..1edde74743 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #ifdef USE_WIFI_WPA2_EAP @@ -666,7 +667,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_SCAN_DONE) { const auto &it = data->data.sta_scan_done; - ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + ESP_LOGV(TAG, "Event: WiFi Scan Done status=%" PRIu32 " number=%u scan_id=%u", it.status, it.number, it.scan_id); scan_result_.clear(); this->scan_done_ = true; diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 0cb148ec13..7c76c8490b 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -3,6 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/hal.h" #include +#include namespace esphome { @@ -194,8 +195,8 @@ void HOT Scheduler::call() { // The following should not happen unless I'm missing something if (to_remove_ != 0) { - ESP_LOGW(TAG, "to_remove_ was %u now: %u items where %zu now %zu. Please report this", to_remove_was, to_remove_, - items_was, items_.size()); + ESP_LOGW(TAG, "to_remove_ was %" PRIu32 " now: %" PRIu32 " items where %zu now %zu. Please report this", + to_remove_was, to_remove_, items_was, items_.size()); to_remove_ = 0; } } From 888ac2e1802165965d99e9bb7537b772bb8c4a98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:29:22 +1200 Subject: [PATCH 766/838] Bump zeroconf from 0.47.4 to 0.56.0 (#4674) Bumps [zeroconf](https://github.com/python-zeroconf/python-zeroconf) from 0.47.4 to 0.56.0. - [Release notes](https://github.com/python-zeroconf/python-zeroconf/releases) - [Changelog](https://github.com/python-zeroconf/python-zeroconf/blob/master/CHANGELOG.md) - [Commits](https://github.com/python-zeroconf/python-zeroconf/compare/0.47.4...0.56.0) --- updated-dependencies: - dependency-name: zeroconf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b37be15ae..5a57342189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.5.1 click==8.1.3 esphome-dashboard==20230214.0 aioesphomeapi==13.5.1 -zeroconf==0.47.4 +zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 80bc567c31f0b2e71e4b8750143e05d1a41de568 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Apr 2023 22:29:40 +0000 Subject: [PATCH 767/838] Bump pytest from 7.2.2 to 7.3.0 (#4673) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.2 to 7.3.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.2.2...7.3.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index cbd0a75d8f..b063dd2797 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.2.2 +pytest==7.3.0 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-asyncio==0.21.0 From b60c08dd2895b623a9a525cc540140ae84cdf4ff Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:45:10 +1200 Subject: [PATCH 768/838] Add push to talk voice assistant (#4648) * Add push to talk voice assistant * Refactor most code into voice_assistant * Make voice_assistant the component and remove push_to_talk (can be done in yaml) * Fix component setup * Always AF_INET to match serverside * Fix microphone and media player co-existence * Format * Update codeowners * Update test file * Fix endifs * nullptr not NULL * clang-tidy * Format * fixup: Add VA event data * Generate proto * Parse and log events * Add default to switch * Fix * Add mic/va to test5 --- CODEOWNERS | 4 + esphome/components/api/api.proto | 55 ++++++ esphome/components/api/api_connection.cpp | 30 +++ esphome/components/api/api_connection.h | 13 ++ esphome/components/api/api_frame_helper.h | 13 +- esphome/components/api/api_pb2.cpp | 185 ++++++++++++++++++ esphome/components/api/api_pb2.h | 71 +++++++ esphome/components/api/api_pb2_service.cpp | 60 ++++++ esphome/components/api/api_pb2_service.h | 18 ++ esphome/components/api/api_server.cpp | 13 ++ esphome/components/api/api_server.h | 5 + esphome/components/i2s_audio/__init__.py | 70 +++++++ esphome/components/i2s_audio/i2s_audio.cpp | 30 +++ esphome/components/i2s_audio/i2s_audio.h | 64 ++++++ .../__init__.py} | 53 +++-- .../i2s_audio_media_player.cpp | 95 ++++++--- .../i2s_audio_media_player.h | 29 ++- .../i2s_audio/microphone/__init__.py | 41 ++++ .../microphone/i2s_audio_microphone.cpp | 101 ++++++++++ .../microphone/i2s_audio_microphone.h | 37 ++++ .../components/improv_base/improv_base.cpp | 2 +- esphome/components/microphone/__init__.py | 91 +++++++++ esphome/components/microphone/automation.h | 32 +++ esphome/components/microphone/microphone.h | 33 ++++ .../components/socket/bsd_sockets_impl.cpp | 5 + .../components/socket/lwip_raw_tcp_impl.cpp | 4 + esphome/components/socket/socket.cpp | 20 +- esphome/components/socket/socket.h | 8 +- .../components/voice_assistant/__init__.py | 57 ++++++ .../voice_assistant/voice_assistant.cpp | 148 ++++++++++++++ .../voice_assistant/voice_assistant.h | 50 +++++ esphome/const.py | 1 + esphome/core/defines.h | 1 + esphome/core/helpers.h | 3 + tests/test4.yaml | 17 +- 35 files changed, 1384 insertions(+), 75 deletions(-) create mode 100644 esphome/components/i2s_audio/i2s_audio.cpp create mode 100644 esphome/components/i2s_audio/i2s_audio.h rename esphome/components/i2s_audio/{media_player.py => media_player/__init__.py} (68%) rename esphome/components/i2s_audio/{ => media_player}/i2s_audio_media_player.cpp (72%) rename esphome/components/i2s_audio/{ => media_player}/i2s_audio_media_player.h (81%) create mode 100644 esphome/components/i2s_audio/microphone/__init__.py create mode 100644 esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp create mode 100644 esphome/components/i2s_audio/microphone/i2s_audio_microphone.h create mode 100644 esphome/components/microphone/__init__.py create mode 100644 esphome/components/microphone/automation.h create mode 100644 esphome/components/microphone/microphone.h create mode 100644 esphome/components/voice_assistant/__init__.py create mode 100644 esphome/components/voice_assistant/voice_assistant.cpp create mode 100644 esphome/components/voice_assistant/voice_assistant.h diff --git a/CODEOWNERS b/CODEOWNERS index 76156db6e6..8e606d253a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -111,6 +111,8 @@ esphome/components/hte501/* @Stock-M esphome/components/hydreon_rgxx/* @functionpointer esphome/components/i2c/* @esphome/core esphome/components/i2s_audio/* @jesserockz +esphome/components/i2s_audio/media_player/* @jesserockz +esphome/components/i2s_audio/microphone/* @jesserockz esphome/components/ili9xxx/* @nielsnl68 esphome/components/improv_base/* @esphome/core esphome/components/improv_serial/* @esphome/core @@ -154,6 +156,7 @@ esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/media_player/* @jesserockz +esphome/components/microphone/* @jesserockz esphome/components/mics_4514/* @jesserockz esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov @@ -287,6 +290,7 @@ esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/vbus/* @ssieb esphome/components/version/* @esphome/core +esphome/components/voice_assistant/* @jesserockz esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e36f0581ca..f31ef3ffc0 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -55,6 +55,7 @@ service APIConnection { rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {} rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} + rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} } @@ -210,6 +211,8 @@ message DeviceInfoResponse { string manufacturer = 12; string friendly_name = 13; + + uint32 voice_assistant_version = 14; } message ListEntitiesRequest { @@ -1379,3 +1382,55 @@ message BluetoothDeviceClearCacheResponse { bool success = 2; int32 error = 3; } + +// ==================== PUSH TO TALK ==================== +message SubscribeVoiceAssistantRequest { + option (id) = 89; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + bool subscribe = 1; +} + +message VoiceAssistantRequest { + option (id) = 90; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + bool start = 1; +} + +message VoiceAssistantResponse { + option (id) = 91; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + uint32 port = 1; + bool error = 2; +} + +enum VoiceAssistantEvent { + VOICE_ASSISTANT_ERROR = 0; + VOICE_ASSISTANT_RUN_START = 1; + VOICE_ASSISTANT_RUN_END = 2; + VOICE_ASSISTANT_STT_START = 3; + VOICE_ASSISTANT_STT_END = 4; + VOICE_ASSISTANT_INTENT_START = 5; + VOICE_ASSISTANT_INTENT_END = 6; + VOICE_ASSISTANT_TTS_START = 7; + VOICE_ASSISTANT_TTS_END = 8; +} + +message VoiceAssistantEventData { + string name = 1; + string value = 2; +} + +message VoiceAssistantEventResponse { + option (id) = 92; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + VoiceAssistantEvent event_type = 1; + repeated VoiceAssistantEventData data = 2; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 104560771e..96fb3ea9fa 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -16,6 +16,9 @@ #ifdef USE_BLUETOOTH_PROXY #include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" #endif +#ifdef USE_VOICE_ASSISTANT +#include "esphome/components/voice_assistant/voice_assistant.h" +#endif namespace esphome { namespace api { @@ -893,6 +896,30 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_ } #endif +#ifdef USE_VOICE_ASSISTANT +bool APIConnection::request_voice_assistant(bool start) { + if (!this->voice_assistant_subscription_) + return false; + VoiceAssistantRequest msg; + msg.start = start; + return this->send_voice_assistant_request(msg); +} +void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { + if (voice_assistant::global_voice_assistant != nullptr) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + this->helper_->getpeername((struct sockaddr *) &storage, &len); + voice_assistant::global_voice_assistant->start(&storage, msg.port); + } +}; +void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { + if (voice_assistant::global_voice_assistant != nullptr) { + voice_assistant::global_voice_assistant->on_event(msg); + } +} + +#endif + bool APIConnection::send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; @@ -970,6 +997,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION : bluetooth_proxy::PASSIVE_ONLY_VERSION; +#endif +#ifdef USE_VOICE_ASSISTANT + resp.voice_assistant_version = 1; #endif return resp; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index c85c69a2b9..78ecbb98e6 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -6,6 +6,7 @@ #include "api_server.h" #include "esphome/core/application.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include @@ -123,6 +124,15 @@ class APIConnection : public APIServerConnection { } #endif +#ifdef USE_VOICE_ASSISTANT + void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { + this->voice_assistant_subscription_ = msg.subscribe; + } + bool request_voice_assistant(bool start); + void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; + void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; +#endif + void on_disconnect_response(const DisconnectResponse &value) override; void on_ping_response(const PingResponse &value) override { // we initiated ping @@ -203,6 +213,9 @@ class APIConnection : public APIServerConnection { bool service_call_subscription_{false}; #ifdef USE_BLUETOOTH_PROXY bool bluetooth_le_advertisement_subscription_{false}; +#endif +#ifdef USE_VOICE_ASSISTANT + bool voice_assistant_subscription_{false}; #endif bool next_close_ = false; APIServer *parent_; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 348a9b574f..bf4872d2d6 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -10,8 +10,8 @@ #include "noise/protocol.h" #endif -#include "esphome/components/socket/socket.h" #include "api_noise_context.h" +#include "esphome/components/socket/socket.h" namespace esphome { namespace api { @@ -67,6 +67,7 @@ class APIFrameHelper { virtual bool can_write_without_blocking() = 0; virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0; virtual std::string getpeername() = 0; + virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; virtual APIError close() = 0; virtual APIError shutdown(int how) = 0; // Give this helper a name for logging @@ -84,7 +85,10 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError read_packet(ReadPacketBuffer *buffer) override; bool can_write_without_blocking() override; APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; - std::string getpeername() override { return socket_->getpeername(); } + std::string getpeername() override { return this->socket_->getpeername(); } + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return this->socket_->getpeername(addr, addrlen); + } APIError close() override; APIError shutdown(int how) override; // Give this helper a name for logging @@ -144,7 +148,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError read_packet(ReadPacketBuffer *buffer) override; bool can_write_without_blocking() override; APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; - std::string getpeername() override { return socket_->getpeername(); } + std::string getpeername() override { return this->socket_->getpeername(); } + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return this->socket_->getpeername(addr, addrlen); + } APIError close() override; APIError shutdown(int how) override; // Give this helper a name for logging diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6260020064..334cde16b3 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -407,6 +407,32 @@ const char *proto_enum_to_string(enums::Bluet } } #endif +#ifdef HAS_PROTO_MESSAGE_DUMP +template<> const char *proto_enum_to_string(enums::VoiceAssistantEvent value) { + switch (value) { + case enums::VOICE_ASSISTANT_ERROR: + return "VOICE_ASSISTANT_ERROR"; + case enums::VOICE_ASSISTANT_RUN_START: + return "VOICE_ASSISTANT_RUN_START"; + case enums::VOICE_ASSISTANT_RUN_END: + return "VOICE_ASSISTANT_RUN_END"; + case enums::VOICE_ASSISTANT_STT_START: + return "VOICE_ASSISTANT_STT_START"; + case enums::VOICE_ASSISTANT_STT_END: + return "VOICE_ASSISTANT_STT_END"; + case enums::VOICE_ASSISTANT_INTENT_START: + return "VOICE_ASSISTANT_INTENT_START"; + case enums::VOICE_ASSISTANT_INTENT_END: + return "VOICE_ASSISTANT_INTENT_END"; + case enums::VOICE_ASSISTANT_TTS_START: + return "VOICE_ASSISTANT_TTS_START"; + case enums::VOICE_ASSISTANT_TTS_END: + return "VOICE_ASSISTANT_TTS_END"; + default: + return "UNKNOWN"; + } +} +#endif bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 2: { @@ -594,6 +620,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->bluetooth_proxy_version = value.as_uint32(); return true; } + case 14: { + this->voice_assistant_version = value.as_uint32(); + return true; + } default: return false; } @@ -654,6 +684,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->bluetooth_proxy_version); buffer.encode_string(12, this->manufacturer); buffer.encode_string(13, this->friendly_name); + buffer.encode_uint32(14, this->voice_assistant_version); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -712,6 +743,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(" friendly_name: "); out.append("'").append(this->friendly_name).append("'"); out.append("\n"); + + out.append(" voice_assistant_version: "); + sprintf(buffer, "%u", this->voice_assistant_version); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif @@ -6111,6 +6147,155 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { out.append("}"); } #endif +bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->subscribe = value.as_bool(); + return true; + } + default: + return false; + } +} +void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("SubscribeVoiceAssistantRequest {\n"); + out.append(" subscribe: "); + out.append(YESNO(this->subscribe)); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->start = value.as_bool(); + return true; + } + default: + return false; + } +} +void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); } +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantRequest {\n"); + out.append(" start: "); + out.append(YESNO(this->start)); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->port = value.as_uint32(); + return true; + } + case 2: { + this->error = value.as_bool(); + return true; + } + default: + return false; + } +} +void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->port); + buffer.encode_bool(2, this->error); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantResponse {\n"); + out.append(" port: "); + sprintf(buffer, "%u", this->port); + out.append(buffer); + out.append("\n"); + + out.append(" error: "); + out.append(YESNO(this->error)); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->name = value.as_string(); + return true; + } + case 2: { + this->value = value.as_string(); + return true; + } + default: + return false; + } +} +void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->name); + buffer.encode_string(2, this->value); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantEventData::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantEventData {\n"); + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" value: "); + out.append("'").append(this->value).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->event_type = value.as_enum(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->data.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_enum(1, this->event_type); + for (auto &it : this->data) { + buffer.encode_message(2, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantEventResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantEventResponse {\n"); + out.append(" event_type: "); + out.append(proto_enum_to_string(this->event_type)); + out.append("\n"); + + for (const auto &it : this->data) { + out.append(" data: "); + it.dump_to(out); + out.append("\n"); + } + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index ade9b9cc8f..9f71c07913 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -165,6 +165,17 @@ enum BluetoothDeviceRequestType : uint32_t { BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, }; +enum VoiceAssistantEvent : uint32_t { + VOICE_ASSISTANT_ERROR = 0, + VOICE_ASSISTANT_RUN_START = 1, + VOICE_ASSISTANT_RUN_END = 2, + VOICE_ASSISTANT_STT_START = 3, + VOICE_ASSISTANT_STT_END = 4, + VOICE_ASSISTANT_INTENT_START = 5, + VOICE_ASSISTANT_INTENT_END = 6, + VOICE_ASSISTANT_TTS_START = 7, + VOICE_ASSISTANT_TTS_END = 8, +}; } // namespace enums @@ -279,6 +290,7 @@ class DeviceInfoResponse : public ProtoMessage { uint32_t bluetooth_proxy_version{0}; std::string manufacturer{}; std::string friendly_name{}; + uint32_t voice_assistant_version{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1577,6 +1589,65 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class SubscribeVoiceAssistantRequest : public ProtoMessage { + public: + bool subscribe{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantRequest : public ProtoMessage { + public: + bool start{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantResponse : public ProtoMessage { + public: + uint32_t port{0}; + bool error{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantEventData : public ProtoMessage { + public: + std::string name{}; + std::string value{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; +}; +class VoiceAssistantEventResponse : public ProtoMessage { + public: + enums::VoiceAssistantEvent event_type{}; + std::vector data{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 7d019e1d3d..df36d0fdea 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -453,6 +453,20 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B return this->send_message_(msg, 88); } #endif +#ifdef USE_VOICE_ASSISTANT +#endif +#ifdef USE_VOICE_ASSISTANT +bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantRequest &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_voice_assistant_request: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 90); +} +#endif +#ifdef USE_VOICE_ASSISTANT +#endif +#ifdef USE_VOICE_ASSISTANT +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -827,6 +841,39 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); #endif this->on_unsubscribe_bluetooth_le_advertisements_request(msg); +#endif + break; + } + case 89: { +#ifdef USE_VOICE_ASSISTANT + SubscribeVoiceAssistantRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); +#endif + this->on_subscribe_voice_assistant_request(msg); +#endif + break; + } + case 91: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantResponse msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_response(msg); +#endif + break; + } + case 92: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantEventResponse msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_event_response(msg); #endif break; } @@ -1226,6 +1273,19 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( this->unsubscribe_bluetooth_le_advertisements(msg); } #endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->subscribe_voice_assistant(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 457a3d28a9..3808f128a4 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -224,6 +224,18 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BLUETOOTH_PROXY bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg); +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; +#endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_request(const VoiceAssistantRequest &msg); +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -306,6 +318,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_BLUETOOTH_PROXY virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -384,6 +399,9 @@ class APIServerConnection : public APIServerConnectionBase { void on_unsubscribe_bluetooth_le_advertisements_request( const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; #endif +#ifdef USE_VOICE_ASSISTANT + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c60766b364..fbef4b253f 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -427,5 +427,18 @@ void APIServer::on_shutdown() { delay(10); } +#ifdef USE_VOICE_ASSISTANT +void APIServer::start_voice_assistant() { + for (auto &c : this->clients_) { + c->request_voice_assistant(true); + } +} +void APIServer::stop_voice_assistant() { + for (auto &c : this->clients_) { + c->request_voice_assistant(false); + } +} +#endif + } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index db87affdb8..30103b2e3f 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -95,6 +95,11 @@ class APIServer : public Component, public Controller { void request_time(); #endif +#ifdef USE_VOICE_ASSISTANT + void start_voice_assistant(); + void stop_voice_assistant(); +#endif + bool is_connected() const; struct HomeAssistantStateSubscription { diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index e69de29bb2..1773d3082f 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -0,0 +1,70 @@ +import esphome.config_validation as cv +import esphome.final_validate as fv +import esphome.codegen as cg + +from esphome import pins +from esphome.const import CONF_ID +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["esp32"] +MULTI_CONF = True + +CONF_I2S_DOUT_PIN = "i2s_dout_pin" +CONF_I2S_DIN_PIN = "i2s_din_pin" +CONF_I2S_BCLK_PIN = "i2s_bclk_pin" +CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" + +CONF_I2S_AUDIO = "i2s_audio" +CONF_I2S_AUDIO_ID = "i2s_audio_id" + +i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") +I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) +I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) +I2SAudioOut = i2s_audio_ns.class_( + "I2SAudioOut", cg.Parented.template(I2SAudioComponent) +) + +# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h +I2S_PORTS = { + VARIANT_ESP32: 2, + VARIANT_ESP32S2: 1, + VARIANT_ESP32S3: 2, + VARIANT_ESP32C3: 1, +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(I2SAudioComponent), + cv.Required(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, + } +) + + +def _final_validate(_): + i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO] + variant = get_esp32_variant() + if variant not in I2S_PORTS: + raise cv.Invalid(f"Unsupported variant {variant}") + if len(i2s_audio_configs) > I2S_PORTS[variant]: + raise cv.Invalid( + f"Only {I2S_PORTS[variant]} I2S audio ports are supported on {variant}" + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) + cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) diff --git a/esphome/components/i2s_audio/i2s_audio.cpp b/esphome/components/i2s_audio/i2s_audio.cpp new file mode 100644 index 0000000000..c1a608c064 --- /dev/null +++ b/esphome/components/i2s_audio/i2s_audio.cpp @@ -0,0 +1,30 @@ +#include "i2s_audio.h" + +#ifdef USE_ESP32 + +#include "esphome/core/log.h" + +namespace esphome { +namespace i2s_audio { + +static const char *const TAG = "i2s_audio"; + +void I2SAudioComponent::setup() { + static i2s_port_t next_port_num = I2S_NUM_0; + + if (next_port_num >= I2S_NUM_MAX) { + ESP_LOGE(TAG, "Too many I2S Audio components!"); + this->mark_failed(); + return; + } + + this->port_ = next_port_num; + next_port_num = (i2s_port_t) (next_port_num + 1); + + ESP_LOGCONFIG(TAG, "Setting up I2S Audio..."); +} + +} // namespace i2s_audio +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/i2s_audio/i2s_audio.h b/esphome/components/i2s_audio/i2s_audio.h new file mode 100644 index 0000000000..6b3fa10f3c --- /dev/null +++ b/esphome/components/i2s_audio/i2s_audio.h @@ -0,0 +1,64 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace i2s_audio { + +class I2SAudioComponent; + +class I2SAudioIn : public Parented {}; + +class I2SAudioOut : public Parented {}; + +class I2SAudioComponent : public Component { + public: + void setup() override; + + void register_audio_in(I2SAudioIn *in) { + this->audio_in_ = in; + in->set_parent(this); + } + void register_audio_out(I2SAudioOut *out) { + this->audio_out_ = out; + out->set_parent(this); + } + + i2s_pin_config_t get_pin_config() const { + return { + .mck_io_num = I2S_PIN_NO_CHANGE, + .bck_io_num = this->bclk_pin_, + .ws_io_num = this->lrclk_pin_, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = I2S_PIN_NO_CHANGE, + }; + } + + void set_bclk_pin(uint8_t pin) { this->bclk_pin_ = pin; } + void set_lrclk_pin(uint8_t pin) { this->lrclk_pin_ = pin; } + + void lock() { this->lock_.lock(); } + bool try_lock() { return this->lock_.try_lock(); } + void unlock() { this->lock_.unlock(); } + + i2s_port_t get_port() const { return this->port_; } + + protected: + Mutex lock_; + + I2SAudioIn *audio_in_{nullptr}; + I2SAudioOut *audio_out_{nullptr}; + + uint8_t bclk_pin_; + uint8_t lrclk_pin_; + i2s_port_t port_{}; +}; + +} // namespace i2s_audio +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/i2s_audio/media_player.py b/esphome/components/i2s_audio/media_player/__init__.py similarity index 68% rename from esphome/components/i2s_audio/media_player.py rename to esphome/components/i2s_audio/media_player/__init__.py index 43a48a721e..4ccb9cfc0a 100644 --- a/esphome/components/i2s_audio/media_player.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -5,22 +5,25 @@ import esphome.config_validation as cv from esphome import pins from esphome.const import CONF_ID, CONF_MODE -from esphome.core import CORE + +from .. import ( + i2s_audio_ns, + I2SAudioComponent, + I2SAudioOut, + CONF_I2S_AUDIO_ID, + CONF_I2S_DOUT_PIN, +) CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["esp32"] - -i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") +DEPENDENCIES = ["i2s_audio"] I2SAudioMediaPlayer = i2s_audio_ns.class_( - "I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer + "I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer, I2SAudioOut ) i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") -CONF_I2S_DOUT_PIN = "i2s_dout_pin" -CONF_I2S_BCLK_PIN = "i2s_bclk_pin" -CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" + CONF_MUTE_PIN = "mute_pin" CONF_AUDIO_ID = "audio_id" CONF_DAC_TYPE = "dac_type" @@ -48,34 +51,26 @@ def validate_esp32_variant(config): CONFIG_SCHEMA = cv.All( cv.typed_schema( { - "internal": cv.Schema( + "internal": media_player.MEDIA_PLAYER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), + cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), } - ) - .extend(media_player.MEDIA_PLAYER_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), - "external": cv.Schema( + ).extend(cv.COMPONENT_SCHEMA), + "external": media_player.MEDIA_PLAYER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), + cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required( CONF_I2S_DOUT_PIN ): pins.internal_gpio_output_pin_number, - cv.Required( - CONF_I2S_BCLK_PIN - ): pins.internal_gpio_output_pin_number, - cv.Required( - CONF_I2S_LRCLK_PIN - ): pins.internal_gpio_output_pin_number, cv.Optional(CONF_MUTE_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MODE, default="mono"): cv.one_of( *EXTERNAL_DAC_OPTIONS, lower=True ), } - ) - .extend(media_player.MEDIA_PLAYER_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), + ).extend(cv.COMPONENT_SCHEMA), }, key=CONF_DAC_TYPE, ), @@ -89,19 +84,19 @@ async def to_code(config): await cg.register_component(var, config) await media_player.register_media_player(var, config) + parent = await cg.get_variable(config[CONF_I2S_AUDIO_ID]) + cg.add(parent.register_audio_out(var)) + if config[CONF_DAC_TYPE] == "internal": cg.add(var.set_internal_dac_mode(config[CONF_MODE])) else: cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) - cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) - cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) if CONF_MUTE_PIN in config: pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN]) cg.add(var.set_mute_pin(pin)) cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) - if CORE.is_esp32: - cg.add_library("WiFiClientSecure", None) - cg.add_library("HTTPClient", None) - cg.add_library("esphome/ESP32-audioI2S", "2.0.6") - cg.add_build_flag("-DAUDIO_NO_SD_FS") + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) + cg.add_library("esphome/ESP32-audioI2S", "2.0.6") + cg.add_build_flag("-DAUDIO_NO_SD_FS") diff --git a/esphome/components/i2s_audio/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp similarity index 72% rename from esphome/components/i2s_audio/i2s_audio_media_player.cpp rename to esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp index 2b00a5ec26..64f83a5ea6 100644 --- a/esphome/components/i2s_audio/i2s_audio_media_player.cpp +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp @@ -11,11 +11,19 @@ static const char *const TAG = "audio"; void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { if (call.get_media_url().has_value()) { - if (this->audio_->isRunning()) - this->audio_->stopSong(); - this->high_freq_.start(); - this->audio_->connecttohost(call.get_media_url().value().c_str()); - this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + this->current_url_ = call.get_media_url(); + + if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) { + if (this->audio_->isRunning()) { + this->audio_->stopSong(); + } + this->audio_->connecttohost(this->current_url_.value().c_str()); + } else { + this->start(); + } + } + if (this->i2s_state_ != I2S_STATE_RUNNING) { + return; } if (call.get_volume().has_value()) { this->volume = call.get_volume().value(); @@ -35,7 +43,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; break; case media_player::MEDIA_PLAYER_COMMAND_STOP: - this->stop_(); + this->stop(); break; case media_player::MEDIA_PLAYER_COMMAND_MUTE: this->mute_(); @@ -94,22 +102,51 @@ void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { this->volume = volume; } -void I2SAudioMediaPlayer::stop_() { - if (this->audio_->isRunning()) - this->audio_->stopSong(); - this->high_freq_.stop(); +void I2SAudioMediaPlayer::setup() { + ESP_LOGCONFIG(TAG, "Setting up Audio..."); this->state = media_player::MEDIA_PLAYER_STATE_IDLE; } -void I2SAudioMediaPlayer::setup() { - ESP_LOGCONFIG(TAG, "Setting up Audio..."); +void I2SAudioMediaPlayer::loop() { + switch (this->i2s_state_) { + case I2S_STATE_STARTING: + this->start_(); + break; + case I2S_STATE_RUNNING: + this->play_(); + break; + case I2S_STATE_STOPPING: + this->stop_(); + break; + case I2S_STATE_STOPPED: + break; + } +} + +void I2SAudioMediaPlayer::play_() { + this->audio_->loop(); + if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) { + this->stop(); + } +} + +void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; } +void I2SAudioMediaPlayer::start_() { + if (this->parent_->try_lock()) { + return; // Waiting for another i2s to return lock + } + #if SOC_I2S_SUPPORTS_DAC if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { - this->audio_ = make_unique